From c4da3df01d3bf2ae6aa5abfd078a89a409eeddab Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sat, 1 Nov 2025 12:02:33 -0700 Subject: [PATCH] Filter out unsupported codex tool type (#1748) --- extensions/copilot/package-lock.json | 10 ++-- extensions/copilot/package.json | 2 +- .../node/oaiLanguageModelServer.ts | 17 ++++++- .../platform/endpoint/node/responsesApi.ts | 50 ++++++++++++++++--- 4 files changed, 66 insertions(+), 13 deletions(-) diff --git a/extensions/copilot/package-lock.json b/extensions/copilot/package-lock.json index 77b5e3de57b..cfe065f90c4 100644 --- a/extensions/copilot/package-lock.json +++ b/extensions/copilot/package-lock.json @@ -104,7 +104,7 @@ "monaco-editor": "0.44.0", "npm-run-all": "^4.1.5", "open": "^10.1.2", - "openai": "^5.11.0", + "openai": "^6.7.0", "outdent": "^0.8.0", "picomatch": "^4.0.2", "playwright": "^1.56.1", @@ -14526,9 +14526,9 @@ } }, "node_modules/openai": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-5.11.0.tgz", - "integrity": "sha512-+AuTc5pVjlnTuA9zvn8rA/k+1RluPIx9AD4eDcnutv6JNwHHZxIhkFy+tmMKCvmMFDQzfA/r1ujvPWB19DQkYg==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.7.0.tgz", + "integrity": "sha512-mgSQXa3O/UXTbA8qFzoa7aydbXBJR5dbLQXCRapAOtoNT+v69sLdKMZzgiakpqhclRnhPggPAXoniVGn2kMY2A==", "dev": true, "license": "Apache-2.0", "bin": { @@ -14536,7 +14536,7 @@ }, "peerDependencies": { "ws": "^8.18.0", - "zod": "^3.23.8" + "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { "ws": { diff --git a/extensions/copilot/package.json b/extensions/copilot/package.json index 70608f8ff84..c901f6a6dff 100644 --- a/extensions/copilot/package.json +++ b/extensions/copilot/package.json @@ -4478,7 +4478,7 @@ "monaco-editor": "0.44.0", "npm-run-all": "^4.1.5", "open": "^10.1.2", - "openai": "^5.11.0", + "openai": "^6.7.0", "outdent": "^0.8.0", "picomatch": "^4.0.2", "playwright": "^1.56.1", diff --git a/extensions/copilot/src/extension/externalAgents/node/oaiLanguageModelServer.ts b/extensions/copilot/src/extension/externalAgents/node/oaiLanguageModelServer.ts index e5201d6ab94..2f18012a882 100644 --- a/extensions/copilot/src/extension/externalAgents/node/oaiLanguageModelServer.ts +++ b/extensions/copilot/src/extension/externalAgents/node/oaiLanguageModelServer.ts @@ -7,7 +7,7 @@ import { RequestMetadata } from '@vscode/copilot-api'; import { Raw } from '@vscode/prompt-tsx'; import * as http from 'http'; import { ClientHttp2Stream } from 'http2'; -import OpenAI from 'openai'; +import type OpenAI from 'openai'; import { IChatMLFetcher, Source } from '../../../platform/chat/common/chatMLFetcher'; import { ChatLocation, ChatResponse } from '../../../platform/chat/common/commonTypes'; import { CustomModel, EndpointEditToolName, IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider'; @@ -132,6 +132,16 @@ export class OpenAILanguageModelServer extends Disposable { try { const requestBody: OpenAI.Responses.ResponseCreateParams = JSON.parse(bodyString); + if (Array.isArray(requestBody.tools)) { + requestBody.tools = requestBody.tools.filter(tool => { + if (typeof tool?.type === 'string' && tool.type.startsWith('web_search')) { + this.warn(`Filtering out unsupported tool type: ${JSON.stringify(tool)}`); + return false; + } + + return true; + }); + } const lastMessage = requestBody.input?.at(-1); const isUserInitiatedMessage = typeof lastMessage === 'string' || lastMessage?.type === 'message' && lastMessage.role === 'user'; @@ -274,6 +284,11 @@ export class OpenAILanguageModelServer extends Disposable { const messageWithClassName = `[OpenAILanguageModelServer] ${message}`; this.logService.trace(messageWithClassName); } + + private warn(message: string): void { + const messageWithClassName = `[OpenAILanguageModelServer] ${message}`; + this.logService.warn(messageWithClassName); + } } class StreamingPassThroughEndpoint implements IChatEndpoint { diff --git a/extensions/copilot/src/platform/endpoint/node/responsesApi.ts b/extensions/copilot/src/platform/endpoint/node/responsesApi.ts index ccebd218a02..75c06b5ba30 100644 --- a/extensions/copilot/src/platform/endpoint/node/responsesApi.ts +++ b/extensions/copilot/src/platform/endpoint/node/responsesApi.ts @@ -251,14 +251,18 @@ export function responseApiInputToRawMessagesForLogging(body: OpenAI.Responses.R } }); break; - case 'function_call_output': + case 'function_call_output': { flushPendingFunctionCalls(); - messages.push({ - role: Raw.ChatRole.Tool, - content: [{ type: Raw.ChatCompletionContentPartKind.Text, text: item.output }], - toolCallId: item.call_id - }); + if (isResponseFunctionCallOutputItem(item)) { + const content = responseFunctionOutputToRawContents(item.output); + messages.push({ + role: Raw.ChatRole.Tool, + content, + toolCallId: item.call_id + }); + } break; + } case 'reasoning': // We can't perfectly reconstruct the original thinking data // but we can add a placeholder for logging @@ -295,6 +299,10 @@ function isResponseInputItemMessage(item: OpenAI.Responses.ResponseInputItem): i return 'role' in item && item.role === 'assistant' && (!('type' in item) || item.type !== 'message'); } +function isResponseFunctionCallOutputItem(item: OpenAI.Responses.ResponseInputItem): item is OpenAI.Responses.ResponseInputItem.FunctionCallOutput { + return 'type' in item && item.type === 'function_call_output'; +} + function ensureContentArray(content: string | OpenAI.Responses.ResponseInputMessageContentList): OpenAI.Responses.ResponseInputMessageContentList { if (typeof content === 'string') { return [{ type: 'input_text', text: content }]; @@ -329,6 +337,36 @@ function responseOutputToRawContent(part: OpenAI.Responses.ResponseOutputText | } } +function responseFunctionOutputItemToRawContent(part: OpenAI.Responses.ResponseFunctionCallOutputItem): Raw.ChatCompletionContentPart | undefined { + if (part.type === 'input_text') { + return { type: Raw.ChatCompletionContentPartKind.Text, text: part.text }; + } + if (part.type === 'input_image') { + const detail = part.detail && part.detail !== 'auto' ? part.detail : undefined; + return { + type: Raw.ChatCompletionContentPartKind.Image, + imageUrl: { + url: part.image_url || '', + detail + } + }; + } + if (part.type === 'input_file') { + return { + type: Raw.ChatCompletionContentPartKind.Opaque, + value: `[File Output - Filename: ${part.filename || 'unknown'}]` + }; + } + return undefined; +} + +function responseFunctionOutputToRawContents(output: string | OpenAI.Responses.ResponseFunctionCallOutputItemList): Raw.ChatCompletionContentPart[] { + if (typeof output === 'string') { + return [{ type: Raw.ChatCompletionContentPartKind.Text, text: output }]; + } + return coalesce(output.map(responseFunctionOutputItemToRawContent)); +} + export async function processResponseFromChatEndpoint(instantiationService: IInstantiationService, telemetryService: ITelemetryService, logService: ILogService, response: Response, expectedNumChoices: number, finishCallback: FinishedCallback, telemetryData: TelemetryData): Promise> { const body = (await response.body()) as ClientHttp2Stream; return new AsyncIterableObject(async feed => {