Skip to content

Commit 75bbb9f

Browse files
rebornixosortega
andauthored
experiment chat editor integration (#7373)
* experiment chat editor integration * provide better data for tool calls * refactor and share types * chatrequestturn2 * chat session content provider * fix typings * Initial streaming approach * enhance CopilotRemoteAgentManager for session handling * 💄 * 💄 * safer setting check * organize imports. * 💄 * hide prefix for now. * refactoring. * update setting check --------- Co-authored-by: Osvaldo Ortega <[email protected]>
1 parent 6b35751 commit 75bbb9f

11 files changed

+994
-77
lines changed

common/sessionParsing.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
export interface SessionResponseLogChunk {
7+
choices: Array<{
8+
finish_reason: string;
9+
delta: {
10+
content?: string;
11+
role: string;
12+
tool_calls?: Array<{
13+
function: {
14+
arguments: string;
15+
name: string;
16+
};
17+
id: string;
18+
type: string;
19+
index: number;
20+
}>;
21+
};
22+
}>;
23+
created: number;
24+
id: string;
25+
usage: {
26+
completion_tokens: number;
27+
prompt_tokens: number;
28+
prompt_tokens_details: {
29+
cached_tokens: number;
30+
};
31+
total_tokens: number;
32+
};
33+
model: string;
34+
object: string;
35+
}
36+
37+
export interface ParsedToolCall {
38+
type: 'str_replace_editor' | 'think' | 'bash' | 'report_progress' | 'unknown';
39+
name: string;
40+
args: any;
41+
content: string;
42+
command?: string; // For str_replace_editor
43+
}
44+
45+
export interface ParsedChoice {
46+
type: 'assistant_content' | 'tool_call' | 'pr_title';
47+
content?: string;
48+
toolCall?: ParsedToolCall;
49+
finishReason?: string;
50+
}
51+
52+
export interface ParsedToolCallDetails {
53+
toolName: string;
54+
invocationMessage: string;
55+
pastTenseMessage?: string;
56+
originMessage?: string;
57+
toolSpecificData?: any;
58+
}
59+
60+
/**
61+
* Parse tool call arguments and return normalized tool details
62+
*/
63+
export function parseToolCallDetails(
64+
toolCall: {
65+
function: { name: string; arguments: string };
66+
id: string;
67+
type: string;
68+
index: number;
69+
},
70+
content: string
71+
): ParsedToolCallDetails {
72+
let args: any = {};
73+
try {
74+
args = toolCall.function.arguments ? JSON.parse(toolCall.function.arguments) : {};
75+
} catch {
76+
// fallback to empty args
77+
}
78+
79+
const name = toolCall.function.name;
80+
81+
if (name === 'str_replace_editor') {
82+
if (args.command === 'view') {
83+
return {
84+
toolName: args.path ? `View ${args.path}` : 'View repository',
85+
invocationMessage: `View ${args.path}`,
86+
pastTenseMessage: `View ${args.path}`
87+
};
88+
} else {
89+
return {
90+
toolName: 'Edit',
91+
invocationMessage: `Edit: ${args.path}`,
92+
pastTenseMessage: `Edit: ${args.path}`
93+
};
94+
}
95+
} else if (name === 'think') {
96+
return {
97+
toolName: 'Thought',
98+
invocationMessage: content
99+
};
100+
} else if (name === 'report_progress') {
101+
const details: ParsedToolCallDetails = {
102+
toolName: 'Progress Update',
103+
invocationMessage: args.prDescription || content
104+
};
105+
if (args.commitMessage) {
106+
details.originMessage = `Commit: ${args.commitMessage}`;
107+
}
108+
return details;
109+
} else if (name === 'bash') {
110+
const command = args.command ? `$ ${args.command}` : undefined;
111+
const bashContent = [command, content].filter(Boolean).join('\n');
112+
const details: ParsedToolCallDetails = {
113+
toolName: 'Run Bash command',
114+
invocationMessage: bashContent
115+
};
116+
117+
// Use the terminal-specific data for bash commands
118+
if (args.command) {
119+
details.toolSpecificData = {
120+
commandLine: {
121+
original: args.command,
122+
},
123+
language: 'bash'
124+
};
125+
}
126+
return details;
127+
} else {
128+
// Unknown tool type
129+
return {
130+
toolName: name || 'unknown',
131+
invocationMessage: content
132+
};
133+
}
134+
}
135+
136+
/**
137+
* Parse raw session logs text into structured log chunks
138+
*/
139+
export function parseSessionLogs(rawText: string): SessionResponseLogChunk[] {
140+
const parts = rawText
141+
.split(/\r?\n/)
142+
.filter(part => part.startsWith('data: '))
143+
.map(part => part.slice('data: '.length).trim())
144+
.map(part => JSON.parse(part));
145+
146+
return parts as SessionResponseLogChunk[];
147+
}

src/@types/vscode.proposed.chatParticipantAdditions.d.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,29 @@ declare module 'vscode' {
8484
constructor(toolName: string);
8585
}
8686

87+
export interface ChatTerminalToolInvocationData {
88+
commandLine: {
89+
original: string;
90+
userEdited?: string;
91+
toolEdited?: string;
92+
};
93+
language: string;
94+
}
95+
96+
export class ChatToolInvocationPart {
97+
toolName: string;
98+
toolCallId: string;
99+
isError?: boolean;
100+
invocationMessage?: string | MarkdownString;
101+
originMessage?: string | MarkdownString;
102+
pastTenseMessage?: string | MarkdownString;
103+
isConfirmed?: boolean;
104+
isComplete?: boolean;
105+
toolSpecificData?: ChatTerminalToolInvocationData;
106+
107+
constructor(toolName: string, toolCallId: string, isError?: boolean);
108+
}
109+
87110
export type ExtendedChatResponsePart = ChatResponsePart | ChatResponseTextEditPart | ChatResponseNotebookEditPart | ChatResponseConfirmationPart | ChatResponseCodeCitationPart | ChatResponseReferencePart2 | ChatResponseMovePart | ChatResponseExtensionsPart | ChatPrepareToolInvocationPart;
88111

89112
export class ChatResponseWarningPart {

src/@types/vscode.proposed.chatParticipantPrivate.d.ts

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,31 @@ declare module 'vscode' {
137137
/**
138138
* @hidden
139139
*/
140-
private constructor(prompt: string, command: string | undefined, references: ChatPromptReference[], participant: string, toolReferences: ChatLanguageModelToolReference[], editedFileEvents: ChatRequestEditedFileEvent[] | undefined);
140+
constructor(prompt: string, command: string | undefined, references: ChatPromptReference[], participant: string, toolReferences: ChatLanguageModelToolReference[], editedFileEvents: ChatRequestEditedFileEvent[] | undefined);
141+
}
142+
143+
export class ChatResponseTurn2 {
144+
/**
145+
* The content that was received from the chat participant. Only the stream parts that represent actual content (not metadata) are represented.
146+
*/
147+
readonly response: ReadonlyArray<ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart | ExtendedChatResponsePart | ChatToolInvocationPart>;
148+
149+
/**
150+
* The result that was received from the chat participant.
151+
*/
152+
readonly result: ChatResult;
153+
154+
/**
155+
* The id of the chat participant that this response came from.
156+
*/
157+
readonly participant: string;
158+
159+
/**
160+
* The name of the command that this response came from.
161+
*/
162+
readonly command?: string;
163+
164+
constructor(response: ReadonlyArray<ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart | ExtendedChatResponsePart>, result: ChatResult, participant: string);
141165
}
142166

143167
export interface ChatParticipant {
@@ -205,24 +229,6 @@ declare module 'vscode' {
205229
presentation?: 'hidden' | undefined;
206230
}
207231

208-
export interface LanguageModelTool<T> {
209-
prepareInvocation2?(options: LanguageModelToolInvocationPrepareOptions<T>, token: CancellationToken): ProviderResult<PreparedTerminalToolInvocation>;
210-
}
211-
212-
export class PreparedTerminalToolInvocation {
213-
readonly command: string;
214-
readonly language: string;
215-
readonly confirmationMessages?: LanguageModelToolConfirmationMessages;
216-
readonly presentation?: 'hidden' | undefined;
217-
218-
constructor(
219-
command: string,
220-
language: string,
221-
confirmationMessages?: LanguageModelToolConfirmationMessages,
222-
presentation?: 'hidden'
223-
);
224-
}
225-
226232
export class ExtendedLanguageModelToolResult extends LanguageModelToolResult {
227233
toolResultMessage?: string | MarkdownString;
228234
toolResultDetails?: Array<Uri | Location>;

src/@types/vscode.proposed.chatSessionsProvider.d.ts

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,28 @@ declare module 'vscode' {
1111
/**
1212
* Label of the extension that registers the provider.
1313
*/
14-
readonly label: string;
14+
readonly label: string; // TODO: move to contribution or registration
1515

1616
/**
1717
* Event that the provider can fire to signal that chat sessions have changed.
1818
*/
1919
readonly onDidChangeChatSessionItems: Event<void>;
2020

21+
// /**
22+
// * Create a new chat session item
23+
// */
24+
// provideNewChatSessionItem(context: {
25+
// // This interface should be extracted
26+
// readonly triggerChat?: {
27+
// readonly prompt: string;
28+
// readonly history: ReadonlyArray<ChatRequestTurn | ChatResponseTurn>;
29+
// };
30+
// }, token: CancellationToken): Thenable<ChatSessionItem> | ChatSessionItem;
31+
2132
/**
2233
* Provides a list of chat sessions.
34+
*
35+
* TODO: Do we need a flag to try auth if needed?
2336
*/
2437
provideChatSessionItems(token: CancellationToken): ProviderResult<ChatSessionItem[]>;
2538
}
@@ -41,7 +54,67 @@ declare module 'vscode' {
4154
iconPath?: IconPath;
4255
}
4356

57+
export interface ChatSession {
58+
59+
/**
60+
* The full history of the session
61+
*
62+
* This should not include any currently active responses
63+
*
64+
* TODO: Are these the right types to use?
65+
* TODO: link request + response to encourage correct usage?
66+
*/
67+
readonly history: ReadonlyArray<ChatRequestTurn | ChatResponseTurn2>;
68+
69+
/**
70+
* Callback invoked by the editor for a currently running response. This allows the session to push items for the
71+
* current response and stream these in as them come in. The current response will be considered complete once the
72+
* callback resolved.
73+
*
74+
* If not provided, the chat session is assumed to not currently be running.
75+
*/
76+
readonly activeResponseCallback?: (stream: ChatResponseStream, token: CancellationToken) => Thenable<void>;
77+
78+
/**
79+
* Handles new request for the session.
80+
*
81+
* If not set, then the session will be considered read-only and no requests can be made.
82+
*
83+
* TODO: Should we introduce our own type for `ChatRequestHandler` since not all field apply to chat sessions?
84+
*/
85+
readonly requestHandler: ChatRequestHandler | undefined;
86+
}
87+
88+
export interface ChatSessionContentProvider {
89+
/**
90+
* Resolves a chat session into a full `ChatSession` object.
91+
*
92+
* @param uri The URI of the chat session to open. Uris as structured as `vscode-chat-session:<chatSessionType>/id`
93+
* @param token A cancellation token that can be used to cancel the operation.
94+
*/
95+
provideChatSessionContent(id: string, token: CancellationToken): Thenable<ChatSession>;
96+
}
97+
4498
export namespace chat {
4599
export function registerChatSessionItemProvider(chatSessionType: string, provider: ChatSessionItemProvider): Disposable;
100+
101+
/**
102+
* @param chatSessionType A unique identifier for the chat session type. This is used to differentiate between different chat session providers.
103+
*/
104+
export function registerChatSessionContentProvider(chatSessionType: string, provider: ChatSessionContentProvider): Disposable;
105+
}
106+
107+
export interface ChatSessionShowOptions {
108+
/**
109+
* The editor view column to show the chat session in.
110+
*
111+
* If not provided, the chat session will be shown in the chat panel instead.
112+
*/
113+
readonly viewColumn?: ViewColumn;
114+
}
115+
116+
export namespace window {
117+
118+
export function showChatSession(chatSessionType: string, id: string, options: ChatSessionShowOptions): Thenable<void>;
46119
}
47120
}

src/extension.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -412,16 +412,25 @@ async function deferredActivate(context: vscode.ExtensionContext, showPRControll
412412
const copilotRemoteAgentManager = new CopilotRemoteAgentManager(credentialStore, reposManager, telemetry);
413413
context.subscriptions.push(copilotRemoteAgentManager);
414414
if (vscode.chat?.registerChatSessionItemProvider) {
415+
const provider = new class implements vscode.ChatSessionContentProvider, vscode.ChatSessionItemProvider {
416+
label = vscode.l10n.t('GitHub Copilot Coding Agent');
417+
provideChatSessionItems = async (token) => {
418+
return await copilotRemoteAgentManager.provideChatSessions(token);
419+
};
420+
provideChatSessionContent = async (id, token) => {
421+
return await copilotRemoteAgentManager.provideChatSessionContent(id, token);
422+
};
423+
onDidChangeChatSessionItems = copilotRemoteAgentManager.onDidChangeChatSessions;
424+
}();
425+
415426
context.subscriptions.push(vscode.chat?.registerChatSessionItemProvider(
416427
'copilot-swe-agent',
417-
{
418-
label: vscode.l10n.t('GitHub Copilot Coding Agent'),
419-
provideChatSessionItems: async (token) => {
420-
return await copilotRemoteAgentManager.provideChatSessions(token);
421-
},
422-
// Events not used yet, but required by interface.
423-
onDidChangeChatSessionItems: copilotRemoteAgentManager.onDidChangeChatSessions
424-
}
428+
provider
429+
));
430+
431+
context.subscriptions.push(vscode.chat?.registerChatSessionContentProvider(
432+
'copilot-swe-agent',
433+
provider
425434
));
426435
}
427436

0 commit comments

Comments
 (0)