Filter out unsupported codex tool type (#1748)

This commit is contained in:
Rob Lourens
2025-11-01 12:02:33 -07:00
committed by GitHub
parent 9ae8037d24
commit c4da3df01d
4 changed files with 66 additions and 13 deletions
+5 -5
View File
@@ -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": {
+1 -1
View File
@@ -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",
@@ -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 {
@@ -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<AsyncIterableObject<ChatCompletion>> {
const body = (await response.body()) as ClientHttp2Stream;
return new AsyncIterableObject<ChatCompletion>(async feed => {