diff --git a/extensions/copilot/package.json b/extensions/copilot/package.json index 0c11c68b3518..438405256b32 100644 --- a/extensions/copilot/package.json +++ b/extensions/copilot/package.json @@ -2160,6 +2160,10 @@ "command": "github.copilot.chat.debug.hideNesRequests", "title": "Hide NES Requests" }, + { + "command": "github.copilot.chat.debug.showRawRequestBody", + "title": "Show Raw Request Body" + }, { "command": "github.copilot.chat.debug.exportLogItem", "title": "Export as...", @@ -3347,6 +3351,10 @@ { "command": "github.copilot.chat.mcp.setup.flow", "when": "false" + }, + { + "command": "github.copilot.chat.debug.showRawRequestBody", + "when": "false" } ], "view/title": [ @@ -3372,6 +3380,11 @@ } ], "view/item/context": [ + { + "command": "github.copilot.chat.debug.showRawRequestBody", + "when": "view == copilot-chat && viewItem == request", + "group": "export@0" + }, { "command": "github.copilot.chat.debug.exportLogItem", "when": "view == copilot-chat && (viewItem == toolcall || viewItem == request)", @@ -4182,4 +4195,4 @@ "string_decoder": "npm:string_decoder@1.2.0", "node-gyp": "npm:node-gyp@10.3.1" } -} +} \ No newline at end of file diff --git a/extensions/copilot/src/extension/log/vscode-node/requestLogTree.ts b/extensions/copilot/src/extension/log/vscode-node/requestLogTree.ts index 066e6cc7c04f..300db06b314f 100644 --- a/extensions/copilot/src/extension/log/vscode-node/requestLogTree.ts +++ b/extensions/copilot/src/extension/log/vscode-node/requestLogTree.ts @@ -26,6 +26,7 @@ const exportPromptArchiveCommand = 'github.copilot.chat.debug.exportPromptArchiv const exportPromptLogsAsJsonCommand = 'github.copilot.chat.debug.exportPromptLogsAsJson'; const exportAllPromptLogsAsJsonCommand = 'github.copilot.chat.debug.exportAllPromptLogsAsJson'; const saveCurrentMarkdownCommand = 'github.copilot.chat.debug.saveCurrentMarkdown'; +const showRawRequestBodyCommand = 'github.copilot.chat.debug.showRawRequestBody'; export class RequestLogTree extends Disposable implements IExtensionContribution { readonly id = 'requestLogTree'; @@ -488,6 +489,15 @@ export class RequestLogTree extends Disposable implements IExtensionContribution vscode.window.showErrorMessage(`Failed to export all prompt logs as JSON: ${error}`); } })); + + this._register(vscode.commands.registerCommand(showRawRequestBodyCommand, async (arg?: ChatPromptItem) => { + const requestId = arg?.id; + if (!requestId) { + return; + } + + await vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(ChatRequestScheme.buildUri({ kind: 'request', id: requestId }, 'rawrequest'))); + })); } } diff --git a/extensions/copilot/src/extension/prompt/vscode-node/requestLoggerImpl.ts b/extensions/copilot/src/extension/prompt/vscode-node/requestLoggerImpl.ts index 6c8c727d8d5f..d603c0d73b51 100644 --- a/extensions/copilot/src/extension/prompt/vscode-node/requestLoggerImpl.ts +++ b/extensions/copilot/src/extension/prompt/vscode-node/requestLoggerImpl.ts @@ -287,6 +287,8 @@ export class RequestLogger extends AbstractRequestLogger { if (format === 'json') { return this._renderToJson(entry); + } else if (format === 'rawrequest') { + return this._renderRawRequestToJson(entry); } else { // Existing markdown logic switch (entry.kind) { @@ -708,4 +710,21 @@ export class RequestLogger extends AbstractRequestLogger { return result.join('\n'); } + + private _renderRawRequestToJson(entry: LoggedInfo): string { + if (entry.kind !== LoggedInfoKind.Request) { + return 'Not available'; + } + + const req = entry.entry; + if (req.type === LoggedRequestKind.MarkdownContentRequest || !('body' in req.chatParams) || !req.chatParams.body) { + return 'Not available'; + } + + try { + return JSON.stringify(req.chatParams.body, null, 2); + } catch (e) { + return `Failed to render body: ${e}`; + } + } } diff --git a/extensions/copilot/src/platform/requestLogger/node/requestLogger.ts b/extensions/copilot/src/platform/requestLogger/node/requestLogger.ts index 5d46485eeea0..f77ff3121ac9 100644 --- a/extensions/copilot/src/platform/requestLogger/node/requestLogger.ts +++ b/extensions/copilot/src/platform/requestLogger/node/requestLogger.ts @@ -29,8 +29,15 @@ export type UriData = { kind: 'request'; id: string } | { kind: 'latest' }; export class ChatRequestScheme { public static readonly chatRequestScheme = 'ccreq'; - public static buildUri(data: UriData, format: 'markdown' | 'json' = 'markdown'): string { - const extension = format === 'json' ? 'json' : 'copilotmd'; + public static buildUri(data: UriData, format: 'markdown' | 'json' | 'rawrequest' = 'markdown'): string { + let extension: string; + if (format === 'markdown') { + extension = 'copilotmd'; + } else if (format === 'json') { + extension = 'json'; + } else { // rawrequest + extension = 'request.json'; + } if (data.kind === 'latest') { return `${ChatRequestScheme.chatRequestScheme}:latest.${extension}`; } else { @@ -38,7 +45,7 @@ export class ChatRequestScheme { } } - public static parseUri(uri: string): { data: UriData; format: 'markdown' | 'json' } | undefined { + public static parseUri(uri: string): { data: UriData; format: 'markdown' | 'json' | 'rawrequest' } | undefined { // Check for latest markdown if (uri === this.buildUri({ kind: 'latest' }, 'markdown')) { return { data: { kind: 'latest' }, format: 'markdown' }; @@ -47,6 +54,10 @@ export class ChatRequestScheme { if (uri === this.buildUri({ kind: 'latest' }, 'json')) { return { data: { kind: 'latest' }, format: 'json' }; } + // Check for latest rawrequest + if (uri === this.buildUri({ kind: 'latest' }, 'rawrequest')) { + return { data: { kind: 'latest' }, format: 'rawrequest' }; + } // Check for specific request markdown const mdMatch = uri.match(/ccreq:([^\s]+)\.copilotmd/); @@ -54,6 +65,12 @@ export class ChatRequestScheme { return { data: { kind: 'request', id: mdMatch[1] }, format: 'markdown' }; } + // specific raw body json + const bodyJsonMatch = uri.match(/ccreq:([^\s]+)\.request\.json/); + if (bodyJsonMatch) { + return { data: { kind: 'request', id: bodyJsonMatch[1] }, format: 'rawrequest' }; + } + // Check for specific request JSON const jsonMatch = uri.match(/ccreq:([^\s]+)\.json/); if (jsonMatch) { @@ -64,7 +81,7 @@ export class ChatRequestScheme { } public static findAllUris(text: string): { uri: string; range: OffsetRange }[] { - const linkRE = /(ccreq:[^\s]+\.(copilotmd|json))/g; + const linkRE = /(ccreq:[^\s]+\.(copilotmd|json|request\.json))/g; return [...text.matchAll(linkRE)].map( (m) => { const identifier = m[1];