Skip to content

Commit 70d6aba

Browse files
committed
Add agentic search settings and support for multiple invocations
- in settings, below 'Voice Input' add group 'Agentic Search', in it add ability to customize `agenticSearchInstructions` - now below it, add setting 'Invocation count' with options: 1 (set by default), 2, 3. hold it in package.json as `agenticSearchInvocationCount` - handle agenticSearchInvocationCount setting. it specifiec concurrency in agentic search invocations. each has it's own progress notification. system waits for all to finish before showing consolidated results. - don't index notifications: (1/2) 2/2, etc. ├── apps │ └── editor │ ├── package.json │ └── src │ ├── commands │ │ └── agentic-search-command │ │ └── agentic-search-command.ts │ ├── extension.ts │ ├── features │ │ └── agentic-search │ │ ├── agentic-search-state.ts │ │ ├── agentic-search.ts │ │ ├── index.ts │ │ └── utils │ │ ├── build-agent-prompt.ts │ │ └── prompt-for-search-term.ts │ ├── utils │ │ └── agentic-cli-invocation │ │ ├── index.ts │ │ ├── invoke-agentic-cli.ts │ │ └── types.ts │ └── views │ ├── prompt │ │ ├── backend │ │ │ ├── message-handlers │ │ │ │ └── handle-agentic-search.ts │ │ │ └── prompt-view-provider.ts │ │ └── types │ │ └── messages.ts │ └── settings │ ├── backend │ │ ├── message-handlers │ │ │ ├── handle-get-agentic-search-instructions.ts │ │ │ ├── handle-get-agentic-search-invocation-count.ts │ │ │ ├── handle-update-agentic-search-instructions.ts │ │ │ ├── handle-update-agentic-search-invocation-count.ts │ │ │ └── index.ts │ │ └── settings-view-provider.ts │ ├── frontend │ │ ├── Home │ │ │ ├── Home.tsx │ │ │ └── sections │ │ │ └── GeneralSection.tsx │ │ ├── Settings.tsx │ │ ├── hooks │ │ │ └── use-settings.ts │ │ └── i18n │ │ └── translations │ │ └── general.ts │ └── types │ └── messages.ts └── packages └── ui └── src └── components └── editor └── common └── Dropdown └── Dropdown.tsx
1 parent b712110 commit 70d6aba

15 files changed

Lines changed: 527 additions & 133 deletions

File tree

‎apps/editor/package.json‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,16 @@
10221022
"scope": "resource",
10231023
"default": "In the project, find the complete set of primary and structural files relevant to the query."
10241024
},
1025+
"codeWebChat.agenticSearchInvocationCount": {
1026+
"type": "integer",
1027+
"scope": "resource",
1028+
"enum": [
1029+
1,
1030+
2,
1031+
3
1032+
],
1033+
"default": 1
1034+
},
10251035
"codeWebChat.editFilesSystemInstructions": {
10261036
"type": "string",
10271037
"scope": "resource",

‎apps/editor/src/features/agentic-search/agentic-search.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ export const agentic_search = async (params: {
3838
let query = params.query
3939

4040
while (true) {
41+
const config = vscode.workspace.getConfiguration('codeWebChat')
42+
const invocation_count = config.get<number>('agenticSearchInvocationCount')
43+
4144
const result = await invoke_agentic_cli({
4245
workspace_provider: params.workspace_provider,
4346
extension_context: params.extension_context,
@@ -47,6 +50,7 @@ export const agentic_search = async (params: {
4750
LAST_SELECTED_WORKSPACE_IN_AGENTIC_SEARCH_STATE_KEY,
4851
default_agent_key: 'isDefaultForAgenticSearch',
4952
show_back_button: false,
53+
invocation_count,
5054
build_prompt: async () => {
5155
while (true) {
5256
const query_result = await prompt_for_search_term(query, {

‎apps/editor/src/utils/agentic-cli-invocation/invoke-agentic-cli.ts‎

Lines changed: 158 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const invoke_agentic_cli = async (params: {
3737
cli_configuration_name?: string
3838
use_quick_pick?: boolean
3939
default_agent_key?: keyof ConfigAgentConfigurationFormat
40+
invocation_count?: number
4041
}): Promise<
4142
{ agent_output: string; selected_root: string } | undefined | 'back'
4243
> => {
@@ -588,165 +589,181 @@ export const invoke_agentic_cli = async (params: {
588589
return { agent_output: '', selected_root: selected_root! }
589590
}
590591

591-
let agent_output = ''
592-
let raw_stream_output = ''
592+
const invocation_count = params.invocation_count || 1
593593
let is_cancelled = false
594+
const agent_outputs: string[] = []
594595

595596
output_channel.clear()
596597
const start_time = Date.now()
597598

598599
try {
599-
await vscode.window.withProgress(
600-
{
601-
location: vscode.ProgressLocation.Notification,
602-
title: params.notification_title,
603-
cancellable: true
604-
},
605-
async (progress, token) => {
606-
progress.report({
607-
message: t('utils.agentic-cli-invocation.agent.waiting-for-agent')
608-
})
600+
await Promise.all(
601+
Array.from({ length: invocation_count }).map(async (_, index) => {
602+
return vscode.window.withProgress(
603+
{
604+
location: vscode.ProgressLocation.Notification,
605+
title: params.notification_title,
606+
cancellable: true
607+
},
608+
async (progress, token) => {
609+
progress.report({
610+
message: t(
611+
'utils.agentic-cli-invocation.agent.waiting-for-agent'
612+
)
613+
})
609614

610-
if (token.isCancellationRequested) {
611-
is_cancelled = true
612-
return
613-
}
615+
if (token.isCancellationRequested) {
616+
is_cancelled = true
617+
return
618+
}
614619

615-
const base_args = agent_info.get_isolated_dir_args({
616-
cwd: selected_root
617-
})
620+
const base_args = agent_info.get_isolated_dir_args({
621+
cwd: selected_root
622+
})
618623

619-
let args = [...base_args, ...custom_args]
624+
let args = [...base_args, ...custom_args]
620625

621-
if (agent_info.get_prompt_file_args) {
622-
args = [
623-
...args,
624-
...agent_info.get_prompt_file_args(temp_prompt_path)
625-
]
626-
}
626+
if (agent_info.get_prompt_file_args) {
627+
args = [
628+
...args,
629+
...agent_info.get_prompt_file_args(temp_prompt_path)
630+
]
631+
}
627632

628-
return new Promise<void>((resolve, reject) => {
629-
const child = spawn(executable, args, {
630-
cwd: selected_root,
631-
shell: false,
632-
stdio: agent_info.get_prompt_file_args
633-
? ['ignore', 'pipe', 'pipe']
634-
: ['pipe', 'pipe', 'pipe']
635-
})
633+
return new Promise<void>((resolve, reject) => {
634+
let current_agent_output = ''
635+
let current_raw_stream_output = ''
636+
const child = spawn(executable, args, {
637+
cwd: selected_root,
638+
shell: false,
639+
stdio: agent_info.get_prompt_file_args
640+
? ['ignore', 'pipe', 'pipe']
641+
: ['pipe', 'pipe', 'pipe']
642+
})
636643

637-
if (!agent_info.get_prompt_file_args && child.stdin) {
638-
const read_stream = fs.createReadStream(temp_prompt_path)
639-
read_stream.pipe(child.stdin)
644+
if (!agent_info.get_prompt_file_args && child.stdin) {
645+
const read_stream = fs.createReadStream(temp_prompt_path)
646+
read_stream.pipe(child.stdin)
640647

641-
read_stream.on('error', (err) => {
642-
Logger.error({
643-
function_name: 'invoke_agentic_cli',
644-
message: 'Failed to read temp prompt file',
645-
data: err
646-
})
647-
})
648-
}
648+
read_stream.on('error', (err) => {
649+
Logger.error({
650+
function_name: 'invoke_agentic_cli',
651+
message: 'Failed to read temp prompt file',
652+
data: err
653+
})
654+
})
655+
}
649656

650-
child.stdout?.on('data', (data) => {
651-
const chunk = data.toString()
652-
output_channel.append(chunk)
653-
if (agent_info.parse_stream_line) {
654-
raw_stream_output += chunk
655-
const lines = raw_stream_output.split('\n')
656-
raw_stream_output = lines.pop() || ''
657-
for (const line of lines) {
658-
const trimmed = line.trim()
659-
if (!trimmed) continue
660-
try {
661-
const parsed = JSON.parse(trimmed)
662-
const result = agent_info.parse_stream_line(
663-
parsed,
664-
(msg) => {
665-
progress.report({ message: msg })
657+
child.stdout?.on('data', (data) => {
658+
const chunk = data.toString()
659+
output_channel.append(chunk)
660+
if (agent_info.parse_stream_line) {
661+
current_raw_stream_output += chunk
662+
const lines = current_raw_stream_output.split('\n')
663+
current_raw_stream_output = lines.pop() || ''
664+
for (const line of lines) {
665+
const trimmed = line.trim()
666+
if (!trimmed) continue
667+
try {
668+
const parsed = JSON.parse(trimmed)
669+
const result = agent_info.parse_stream_line(
670+
parsed,
671+
(msg) => {
672+
progress.report({ message: msg })
673+
}
674+
)
675+
676+
if (result?.output !== undefined) {
677+
current_agent_output = result.output
678+
}
679+
} catch (e) {
680+
// Ignore parse errors for partial chunks
666681
}
667-
)
668-
669-
if (result?.output !== undefined) {
670-
agent_output = result.output
671682
}
672-
} catch (e) {
673-
// Ignore parse errors for partial chunks
683+
} else {
684+
current_agent_output += chunk
674685
}
675-
}
676-
} else {
677-
agent_output += chunk
678-
}
679-
})
686+
})
680687

681-
child.stderr?.on('data', (data) => {
682-
const chunk = data.toString()
683-
output_channel.append(chunk)
684-
})
688+
child.stderr?.on('data', (data) => {
689+
const chunk = data.toString()
690+
output_channel.append(chunk)
691+
})
685692

686-
token.onCancellationRequested(() => {
687-
is_cancelled = true
688-
child.kill()
689-
resolve()
690-
})
693+
token.onCancellationRequested(() => {
694+
is_cancelled = true
695+
child.kill()
696+
resolve()
697+
})
691698

692-
child.on('close', async () => {
693-
if (agent_info.parse_final_output && raw_stream_output.trim()) {
694-
try {
695-
const parsed = JSON.parse(raw_stream_output.trim())
696-
agent_output =
697-
agent_info.parse_final_output(parsed, agent_output) ??
698-
agent_output
699-
} catch (e) {
700-
// Ignore
701-
}
702-
}
699+
child.on('close', async () => {
700+
if (
701+
agent_info.parse_final_output &&
702+
current_raw_stream_output.trim()
703+
) {
704+
try {
705+
const parsed = JSON.parse(
706+
current_raw_stream_output.trim()
707+
)
708+
current_agent_output =
709+
agent_info.parse_final_output(
710+
parsed,
711+
current_agent_output
712+
) ?? current_agent_output
713+
} catch (e) {
714+
// Ignore
715+
}
716+
}
703717

704-
if (params.generate_diff_for_temp_dir && temp_dir_path) {
705-
try {
706-
const diff_markdown = await generate_diff_markdown(
707-
temp_dir_path,
708-
file_mappings
709-
)
710-
711-
const trimmed_output = agent_output
712-
.trim()
713-
.replace(/\[([^\]]+)\]\([^)]+\)/g, '`$1`')
714-
.replace(/```[\s\S]*?```/g, '')
715-
.trim()
716-
const trimmed_diff = diff_markdown.trim()
717-
718-
if (trimmed_output && trimmed_diff) {
719-
agent_output = trimmed_output + '\n\n' + trimmed_diff
720-
} else {
721-
agent_output = trimmed_diff || trimmed_output
718+
if (
719+
params.generate_diff_for_temp_dir &&
720+
temp_dir_path &&
721+
invocation_count === 1
722+
) {
723+
try {
724+
const diff_markdown = await generate_diff_markdown(
725+
temp_dir_path,
726+
file_mappings
727+
)
728+
729+
const trimmed_output = current_agent_output
730+
.trim()
731+
.replace(/\[([^\]]+)\]\([^)]+\)/g, '`$1`')
732+
.replace(/```[\s\S]*?```/g, '')
733+
.trim()
734+
const trimmed_diff = diff_markdown.trim()
735+
736+
if (trimmed_output && trimmed_diff) {
737+
current_agent_output =
738+
trimmed_output + '\n\n' + trimmed_diff
739+
} else {
740+
current_agent_output = trimmed_diff || trimmed_output
741+
}
742+
} catch (e) {
743+
Logger.error({
744+
function_name: 'invoke_agentic_cli',
745+
message: 'Failed to generate diff',
746+
data: e
747+
})
748+
}
722749
}
723-
} catch (e) {
750+
751+
agent_outputs[index] = current_agent_output
752+
resolve()
753+
})
754+
755+
child.on('error', (err) => {
724756
Logger.error({
725757
function_name: 'invoke_agentic_cli',
726-
message: 'Failed to generate diff',
727-
data: e
758+
message: 'Agent execution failed',
759+
data: err
728760
})
729-
}
730-
}
731-
732-
Logger.info({
733-
function_name: 'invoke_agentic_cli',
734-
message: "Agent's response",
735-
data: agent_output
736-
})
737-
resolve()
738-
})
739-
740-
child.on('error', (err) => {
741-
Logger.error({
742-
function_name: 'invoke_agentic_cli',
743-
message: 'Agent execution failed',
744-
data: err
761+
reject(err)
762+
})
745763
})
746-
reject(err)
747-
})
748-
})
749-
}
764+
}
765+
)
766+
})
750767
)
751768
} catch (err) {
752769
vscode.window.showErrorMessage(
@@ -763,6 +780,14 @@ export const invoke_agentic_cli = async (params: {
763780
break
764781
}
765782

783+
const agent_output = agent_outputs.join('\n\n')
784+
785+
Logger.info({
786+
function_name: 'invoke_agentic_cli',
787+
message: "Agent's response",
788+
data: agent_output
789+
})
790+
766791
vscode.window.showInformationMessage(
767792
t('utils.agentic-cli-invocation.agent.finished', {
768793
duration: format_duration(Date.now() - start_time)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as vscode from 'vscode'
2+
import { SettingsViewProvider } from '../settings-view-provider'
3+
4+
export const handle_get_agentic_search_instructions = async (
5+
provider: SettingsViewProvider
6+
) => {
7+
const config = vscode.workspace.getConfiguration('codeWebChat')
8+
const instructions = config.get<string>('agenticSearchInstructions')
9+
const default_instructions = config.inspect<string>(
10+
'agenticSearchInstructions'
11+
)?.defaultValue
12+
13+
provider.postMessage({
14+
command: 'AGENTIC_SEARCH_INSTRUCTIONS',
15+
instructions: instructions || '',
16+
default_instructions: default_instructions || ''
17+
})
18+
}

0 commit comments

Comments
 (0)