Skip to content

Commit ddaf514

Browse files
committed
Add agentic CLI support to the Code at Cursor and Commit Message flows and refactor CLI agent and prompt builder configurations
- to code at cursor, add option 'Invoke CLI' - 'perform_code_at_cursor' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. - eliminate code repetition around showing ghost text - `apps/editor/src/commands/code-at-cursor-command/utils/show-ghost-text.ts` should have just a single function - rename [Fragment] to 'show_ghost_text' - rename 'invoke cli' option to invoke agent - PromptBuilder should accept keys (instead of system instructions): output formatting and requirements, and place these headings by itself not hold them in other places like `apps/editor/src/constants/instructions.ts` - in [Fragment] [Fragment] should be detached into two keys (then separated list items) so that `apps/editor/src/commands/code-at-cursor-command/utils/perform-code-at-cursor.ts` doesn't use [Fragment] - use `apps/editor/src/constants/state-keys.ts` for last_selected_workspace_state_key - [Fragment] should not be passed via param - functionality behind [Fragment] should be expressed in a more generic way (other params) - `apps/editor/src/commands/code-at-cursor-command/utils/perform-code-at-cursor.ts` when running agentic cli, should isolate in temp dir, just like 'edit' - edit/ask args should have more generic names, maybe isolated_dir_args/integrated_terminal_args - in code at cursor, place 'invoke agent' below 'send request' - similarly to 'code at cursor', implement 'invoke agent' in commit message generation - invoke_agentic_cli.title param should have better name: notification_title - keys on invoke_agentic_cli that are always used should not be optional - similarly to places like commit message generation, 'invoke agent' should be implemented to 'intelligent search' as well └── apps └── editor └── src ├── commands │ ├── code-at-cursor-command │ │ ├── code-at-cursor-commands.ts │ │ ├── index.ts │ │ └── utils │ │ ├── get-code-at-cursor-config.ts │ │ ├── perform-code-at-cursor.ts │ │ └── show-ghost-text.ts │ ├── copy-markdown-command.ts │ └── generate-commit-message-commands │ ├── actions │ │ ├── get-prompt-data.ts │ │ └── run-generate-action.ts │ ├── generate-commit-message-commands.ts │ ├── index.ts │ └── utils │ ├── build-commit-message-prompt.ts │ ├── generate-commit-message-with-api.ts │ ├── get-commit-message-config.ts │ └── strip-wrapping-quotes.ts ├── constants │ ├── instructions.ts │ └── state-keys.ts ├── features │ ├── agentic-search │ │ └── agentic-search.ts │ └── search-files │ ├── index.ts │ ├── search-files.ts │ ├── search-modes │ │ ├── perform-intelligent-search-mode.ts │ │ ├── perform-keywords-search-mode.ts │ │ └── perform-phrase-search-mode.ts │ └── utils │ ├── analyze-files.ts │ ├── check-matches-condition.ts │ ├── create-search-regex.ts │ ├── prompt-for-config.ts │ ├── prompt-for-intelligent-search-results.ts │ ├── prompt-for-keywords-match-mode.ts │ ├── prompt-for-keywords-target.ts │ ├── prompt-for-search-mode.ts │ ├── prompt-for-search-results.ts │ ├── prompt-for-search-term.ts │ ├── prompt-for-shrink-mode.ts │ ├── search-files-by-intelligent.ts │ ├── search-files-by-keywords.ts │ ├── search-files-by-phrase.ts │ └── show-search-results-quick-pick.ts ├── i18n │ └── translations │ ├── common.ts │ └── utils │ └── agentic-cli-invocation.ts ├── services │ └── providers-manager.ts ├── utils │ ├── agentic-cli-invocation │ │ ├── agents │ │ │ ├── antigravity.ts │ │ │ ├── claude.ts │ │ │ ├── codex.ts │ │ │ ├── cursor.ts │ │ │ ├── grok.ts │ │ │ ├── index.ts │ │ │ └── opencode.ts │ │ ├── index.ts │ │ ├── invoke-agentic-cli.ts │ │ ├── types.ts │ │ └── utils │ │ ├── check-command-exists.ts │ │ ├── format-duration.ts │ │ ├── generate-diff-markdown.ts │ │ └── get-progress-dots.ts │ ├── apply-reasoning-effort.ts │ ├── build-user-content.ts │ ├── get-api-configuration.ts │ ├── git-repository-utils.ts │ ├── prompt-builder.ts │ └── send-llm-message.ts └── views └── prompt └── backend └── message-handlers ├── handle-autofill.ts ├── handle-copy-prompt.ts ├── handle-invoke-agentic-cli.ts ├── handle-make-api-call.ts ├── handle-preview-prompt.ts ├── handle-preview-web-configuration.ts └── utils └── build-cli-prompt.ts
1 parent b58da6f commit ddaf514

29 files changed

Lines changed: 546 additions & 260 deletions

‎apps/editor/src/commands/code-at-cursor-command/code-at-cursor-commands.ts‎

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import * as fs from 'fs'
2-
import * as path from 'path'
32
import * as vscode from 'vscode'
43
import { create_safe_path } from '@/utils/path-sanitizer'
54
import { show_ghost_text } from './utils/show-ghost-text'
6-
import { normalize_path } from '@/utils/normalize-path'
75
import { WorkspaceProvider } from '@/context/providers/workspace/workspace-provider'
86
import { OpenEditorsProvider } from '@/context/providers/open-editors/open-editors-provider'
97
import { CommitMessageDetails } from '@/utils/commit-message-details'
@@ -81,35 +79,12 @@ export const code_at_cursor_commands = (params: {
8179

8280
const decoded_completion = args.content
8381

84-
const selected_files: string[] = []
85-
const checked_files = params.workspace_provider.get_checked_files()
86-
for (const file of checked_files) {
87-
const file_workspace_root =
88-
params.workspace_provider.get_workspace_root_for_file(file)
89-
if (file_workspace_root === workspace_root) {
90-
const relative_path = normalize_path(
91-
path.relative(workspace_root, file)
92-
)
93-
selected_files.push(relative_path)
94-
}
95-
}
96-
9782
await show_ghost_text({
9883
editor,
9984
position,
100-
ghost_text: decoded_completion,
101-
command: {
102-
title: 'Code at Cursor Accepted',
103-
command: 'codeWebChat.internal.codeAtCursorAccepted',
104-
arguments: [
105-
{
106-
workspace_root,
107-
prompt: '',
108-
file_path: safe_path,
109-
selected_files
110-
}
111-
]
112-
}
85+
decoded_completion,
86+
workspace_provider: params.workspace_provider,
87+
active_file_path_fs: safe_path
11388
})
11489
}
11590
),

‎apps/editor/src/commands/code-at-cursor-command/utils/perform-code-at-cursor.ts‎

Lines changed: 142 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import * as path from 'path'
21
import * as vscode from 'vscode'
32
import axios from 'axios'
43
import he from 'he'
54
import { send_llm_message } from '../../../utils/send-llm-message'
65
import {
7-
code_at_cursor_system_instructions,
6+
code_at_cursor_output_formatting,
87
code_at_cursor_user_instructions,
9-
code_at_cursor_instructions_for_chatbots
8+
code_at_cursor_output_formatting_for_chatbots,
9+
cli_edit_ask_requirements
1010
} from '../../../constants/instructions'
11+
import { invoke_agentic_cli } from '../../../utils/agentic-cli-invocation'
1112
import { FilesCollector } from '../../../utils/files-collector'
1213
import { ProvidersManager } from '../../../services/providers-manager'
1314
import { Logger } from '@shared/utils/logger'
@@ -19,10 +20,15 @@ import { show_ghost_text } from './show-ghost-text'
1920
import { PromptBuilder } from '../../../utils/prompt-builder'
2021
import { WorkspaceProvider } from '@/context/providers/workspace/workspace-provider'
2122
import { OpenEditorsProvider } from '@/context/providers/open-editors/open-editors-provider'
22-
import { normalize_path } from '@/utils/normalize-path'
2323
import { WebSocketManager } from '@/services/websocket-manager'
2424
import { show_configurations_quick_pick } from '@/utils/show-configurations-quick-pick'
25-
import { get_last_used_web_configuration_key } from '@/constants/state-keys'
25+
import {
26+
get_last_used_web_configuration_key,
27+
LAST_COMPLETION_INSTRUCTIONS_STATE_KEY,
28+
LAST_USED_CODE_AT_CURSOR_ACTION_STATE_KEY,
29+
LAST_SELECTED_WORKSPACE_FOR_CODE_AT_CURSOR_STATE_KEY,
30+
LAST_USED_AGENT_FOR_CODE_AT_CURSOR_STATE_KEY
31+
} from '@/constants/state-keys'
2632
import { show_incomplete_setup_warning } from '@/utils/show-missing-configuration-notification'
2733
import { ConfigWebConfigurationFormat } from '@/utils/web-configuration-format-converters'
2834

@@ -35,7 +41,7 @@ export const perform_code_at_cursor = async (params: {
3541
show_quick_pick?: boolean
3642
completion_instructions?: string
3743
api_configuration_id?: string
38-
}) => {
44+
}): Promise<void> => {
3945
const providers_manager = new ProvidersManager(params.extension_context)
4046

4147
const editor = vscode.window.activeTextEditor
@@ -61,7 +67,7 @@ export const perform_code_at_cursor = async (params: {
6167
) {
6268
const last_value =
6369
params.extension_context.workspaceState.get<string>(
64-
'last-completion-instructions'
70+
LAST_COMPLETION_INSTRUCTIONS_STATE_KEY
6571
) || ''
6672

6773
completion_instructions = await new Promise<string | undefined>(
@@ -111,7 +117,7 @@ export const perform_code_at_cursor = async (params: {
111117
if (completion_instructions === undefined) return
112118

113119
await params.extension_context.workspaceState.update(
114-
'last-completion-instructions',
120+
LAST_COMPLETION_INSTRUCTIONS_STATE_KEY,
115121
completion_instructions || ''
116122
)
117123
}
@@ -126,6 +132,10 @@ export const perform_code_at_cursor = async (params: {
126132
label: t('common.action.send-request'),
127133
id: 'make-api'
128134
},
135+
{
136+
label: t('common.action.invoke-agent'),
137+
id: 'invoke-agent'
138+
},
129139
...(params.websocket_manager.is_connected_with_browser()
130140
? [
131141
{
@@ -142,7 +152,7 @@ export const perform_code_at_cursor = async (params: {
142152

143153
const last_action_id =
144154
params.extension_context.workspaceState.get<string>(
145-
'last_used_code_at_cursor_action'
155+
LAST_USED_CODE_AT_CURSOR_ACTION_STATE_KEY
146156
)
147157

148158
const active_item = last_action_id
@@ -207,7 +217,7 @@ export const perform_code_at_cursor = async (params: {
207217
if (!action) return
208218

209219
params.extension_context.workspaceState.update(
210-
'last_used_code_at_cursor_action',
220+
LAST_USED_CODE_AT_CURSOR_ACTION_STATE_KEY,
211221
action
212222
)
213223
}
@@ -237,7 +247,7 @@ export const perform_code_at_cursor = async (params: {
237247
})
238248

239249
if (action == 'copy' || action == 'autofill') {
240-
const chatbot_instructions = code_at_cursor_instructions_for_chatbots({
250+
const chatbot_instructions = code_at_cursor_output_formatting_for_chatbots({
241251
file_path: active_file_path,
242252
row,
243253
column
@@ -254,7 +264,7 @@ export const perform_code_at_cursor = async (params: {
254264
: '<missing_text>'
255265
}${text_after_cursor}`
256266
},
257-
system_instructions: chatbot_instructions,
267+
output_formatting: chatbot_instructions,
258268
user_instructions: code_at_cursor_user_instructions
259269
})
260270

@@ -333,6 +343,121 @@ export const perform_code_at_cursor = async (params: {
333343
}
334344
}
335345

346+
if (action == 'invoke-agent') {
347+
const { full_prompt: cli_prompt } = PromptBuilder.build_prompt({
348+
files_context_part1: collected.other_files,
349+
files_context_part2: collected.recent_files,
350+
active_file: {
351+
filepath: active_file_path,
352+
content: `${text_before_cursor}${
353+
completion_instructions
354+
? `<missing_text>${completion_instructions}</missing_text>`
355+
: '<missing_text>'
356+
}${text_after_cursor}`
357+
},
358+
output_formatting: code_at_cursor_output_formatting,
359+
requirements: cli_edit_ask_requirements.restrict_shell_commands,
360+
user_instructions: code_at_cursor_user_instructions
361+
})
362+
363+
const invoke_cli_result = await invoke_agentic_cli({
364+
workspace_provider: params.workspace_provider,
365+
extension_context: params.extension_context,
366+
build_prompt: async () => cli_prompt,
367+
notification_title: t('command.code-at-cursor-command.progress.title'),
368+
last_selected_workspace_state_key:
369+
LAST_SELECTED_WORKSPACE_FOR_CODE_AT_CURSOR_STATE_KEY,
370+
last_used_agent_config_name:
371+
params.extension_context.workspaceState.get<string>(
372+
LAST_USED_AGENT_FOR_CODE_AT_CURSOR_STATE_KEY
373+
),
374+
on_agent_selected: (name) => {
375+
params.extension_context.workspaceState.update(
376+
LAST_USED_AGENT_FOR_CODE_AT_CURSOR_STATE_KEY,
377+
name
378+
)
379+
},
380+
show_back_button: true,
381+
isolate_in_temp_dir: true,
382+
agent_args_type: 'isolated-dir'
383+
})
384+
385+
if (invoke_cli_result === 'back') {
386+
return perform_code_at_cursor({
387+
workspace_provider: params.workspace_provider,
388+
open_editors_provider: params.open_editors_provider,
389+
extension_context: params.extension_context,
390+
websocket_manager: params.websocket_manager,
391+
with_completion_instructions: params.with_completion_instructions,
392+
show_quick_pick: true,
393+
completion_instructions: completion_instructions
394+
})
395+
}
396+
397+
if (!invoke_cli_result) {
398+
return
399+
}
400+
401+
const response_text = invoke_cli_result.agent_output
402+
const start_match = response_text.match(/<replacement>/i)
403+
404+
let extracted_content = ''
405+
if (start_match) {
406+
const content_start = start_match.index! + start_match[0].length
407+
const remaining_text = response_text.substring(content_start)
408+
const end_match = remaining_text.match(/<\/replacement>/i)
409+
410+
if (end_match) {
411+
extracted_content = remaining_text.substring(0, end_match.index)
412+
} else {
413+
extracted_content = remaining_text
414+
}
415+
} else {
416+
const code_block_match = response_text.match(/```[^\n]*\n([\s\S]*?)```/)
417+
if (code_block_match) {
418+
extracted_content = code_block_match[1]
419+
} else {
420+
extracted_content = response_text
421+
}
422+
}
423+
424+
let decoded_completion = he.decode(extracted_content.trim())
425+
426+
if (decoded_completion.startsWith('```')) {
427+
const first_newline = decoded_completion.indexOf('\n')
428+
if (first_newline !== -1) {
429+
decoded_completion = decoded_completion.substring(first_newline + 1)
430+
}
431+
}
432+
if (decoded_completion.endsWith('```')) {
433+
const last_newline = decoded_completion.lastIndexOf('\n')
434+
if (
435+
last_newline !== -1 &&
436+
last_newline > decoded_completion.indexOf('\n')
437+
) {
438+
decoded_completion = decoded_completion.substring(0, last_newline)
439+
} else {
440+
decoded_completion = decoded_completion.substring(
441+
0,
442+
decoded_completion.length - 3
443+
)
444+
}
445+
}
446+
447+
decoded_completion = decoded_completion.trim()
448+
449+
await show_ghost_text({
450+
editor,
451+
position,
452+
decoded_completion,
453+
workspace_provider: params.workspace_provider,
454+
active_file_path_fs,
455+
completion_instructions
456+
})
457+
458+
return
459+
}
460+
336461
let show_quick_pick = params.show_quick_pick || false
337462
let current_api_configuration_id = params.api_configuration_id
338463

@@ -391,7 +516,7 @@ export const perform_code_at_cursor = async (params: {
391516
: '<missing_text>'
392517
}${text_after_cursor}`
393518
},
394-
system_instructions: code_at_cursor_system_instructions,
519+
output_formatting: code_at_cursor_output_formatting,
395520
user_instructions: code_at_cursor_user_instructions
396521
})
397522

@@ -499,44 +624,13 @@ export const perform_code_at_cursor = async (params: {
499624

500625
decoded_completion = decoded_completion.trim()
501626

502-
const workspace_root =
503-
params.workspace_provider.get_workspace_root_for_file(
504-
active_file_path_fs
505-
)
506-
const selected_files: string[] = []
507-
508-
if (workspace_root) {
509-
const checked_files = params.workspace_provider.get_checked_files()
510-
for (const file of checked_files) {
511-
const file_workspace_root =
512-
params.workspace_provider.get_workspace_root_for_file(file)
513-
if (file_workspace_root === workspace_root) {
514-
const relative_path = normalize_path(
515-
path.relative(workspace_root, file)
516-
)
517-
selected_files.push(relative_path)
518-
}
519-
}
520-
}
521-
522627
await show_ghost_text({
523628
editor,
524629
position,
525-
ghost_text: decoded_completion,
526-
command: workspace_root
527-
? {
528-
title: 'Code at Cursor Accepted',
529-
command: 'codeWebChat.internal.codeAtCursorAccepted',
530-
arguments: [
531-
{
532-
workspace_root,
533-
prompt: completion_instructions,
534-
file_path: active_file_path_fs,
535-
selected_files
536-
}
537-
]
538-
}
539-
: undefined
630+
decoded_completion,
631+
workspace_provider: params.workspace_provider,
632+
active_file_path_fs,
633+
completion_instructions
540634
})
541635
}
542636
break

0 commit comments

Comments
 (0)