Repository navigation
fix(zed): map developer role to system and drop unsupported built-in tools for the OpenAI delegate #7210
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(zed): map developer role to system and drop unsupported built-in tools for the OpenAI delegate #7210
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -53,7 +53,9 @@ function delegateProvider(provider: OcxProviderConfig, zedProvider: ZedProvider) | |||||||||||||||||||||||||||||||
| return { ...common, adapter: "google", baseUrl: "https://generativelanguage.googleapis.com", googleMode: "ai-studio" }; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| if (zedProvider === "open_ai") { | ||||||||||||||||||||||||||||||||
| return { ...common, adapter: "openai-responses", baseUrl: "https://api.openai.com/v1" }; | ||||||||||||||||||||||||||||||||
| // The builder targets Zed's /completions envelope. Keep its destination set to Zed as well, | ||||||||||||||||||||||||||||||||
| // so OpenAI-private Responses fields are stripped instead of being sent through to Zed. | ||||||||||||||||||||||||||||||||
| return { ...common, adapter: "openai-responses", baseUrl: provider.baseUrl }; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| return { ...common, adapter: "openai-chat", baseUrl: "https://api.x.ai/v1" }; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
@@ -300,6 +302,21 @@ export function createZedAdapter(provider: OcxProviderConfig): ProviderAdapter { | |||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| if (!isRecord(providerRequest)) throw new Error("Zed delegate produced a non-object request body"); | ||||||||||||||||||||||||||||||||
| if (zedProvider === "open_ai" && Array.isArray(providerRequest.input)) { | ||||||||||||||||||||||||||||||||
| // Zed's OpenAI Responses parser accepts user/assistant/system/tool roles but rejects | ||||||||||||||||||||||||||||||||
| // OpenAI's newer developer role. System is the closest supported role for instructions. | ||||||||||||||||||||||||||||||||
| providerRequest.input = providerRequest.input.map(item => | ||||||||||||||||||||||||||||||||
| isRecord(item) && item.type === "message" && item.role === "developer" | ||||||||||||||||||||||||||||||||
| ? { ...item, role: "system" } | ||||||||||||||||||||||||||||||||
| : item, | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| if (zedProvider === "open_ai" && Array.isArray(providerRequest.tools)) { | ||||||||||||||||||||||||||||||||
| // Zed's Responses decoder supports function/custom tools, not Codex's built-in web_search. | ||||||||||||||||||||||||||||||||
| providerRequest.tools = providerRequest.tools.filter(tool => | ||||||||||||||||||||||||||||||||
| isRecord(tool) && (tool.type === "function" || tool.type === "custom"), | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
+314
to
+319
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: rg -n 'tool_choice|stripUnsupportedHostedTools|buildRequest|providerRequest' src/adapters/zed.ts src/adapters/openai-responses/passthrough.ts src/adapters/openai-responses/tool-schema.ts src/chat/inbound.ts
sed -n '280,320p' src/chat/inbound.ts
sed -n '270,305p' src/adapters/openai-responses/tool-schema.tsRepository: lidge-jun/opencodex Length of output: 7962 🏁 Script executed: set -eu
printf '%s\n' '--- zed adapter ---'
nl -ba src/adapters/zed.ts | sed -n '210,345p'
printf '%s\n' '--- passthrough build and normalization ---'
nl -ba src/adapters/openai-responses/passthrough.ts | sed -n '200,245p'
nl -ba src/adapters/openai-responses/passthrough.ts | sed -n '470,565p'
printf '%s\n' '--- tool schema policy and selector contract ---'
nl -ba src/adapters/openai-responses/tool-schema.ts | sed -n '1,90p'
nl -ba src/adapters/openai-responses/tool-schema.ts | sed -n '220,315p'
printf '%s\n' '--- inbound Responses/tool parsing ---'
rg -n -F --glob '*.ts' -e 'web_search' -e 'tool_choice' -e 'parseResponses' -e 'responses' src/chat src/adapters/openai-responses src/providers | head -200
nl -ba src/chat/inbound.ts | sed -n '560,650p'
printf '%s\n' '--- Zed provider registration/configuration ---'
rg -n -F --glob '*.ts' -e 'zedProvider' -e 'cloud.zed.dev' -e 'unsupportedHostedTools' -e 'selected.buildRequest' src | head -200Repository: lidge-jun/opencodex Length of output: 40404 🏁 Script executed: set -eu
printf '%s\n' '--- Zed delegate configuration ---'
nl -ba src/adapters/zed.ts | sed -n '1,75p'
printf '%s\n' '--- hosted-tool filtering implementation ---'
nl -ba src/adapters/openai-responses/tool-schema.ts | sed -n '225,310p'
printf '%s\n' '--- hosted-tool policy ---'
nl -ba src/responses/hosted-tool-policy.ts | sed -n '1,105p'
printf '%s\n' '--- Zed registry entry ---'
nl -ba src/providers/registry/entries-core.ts | sed -n '190,220p'Repository: lidge-jun/opencodex Length of output: 14377 Normalize A Responses request with only Set Suggested fix providerRequest.tools = providerRequest.tools.filter(tool =>
isRecord(tool) && (tool.type === "function" || tool.type === "custom"),
);
+ if (providerRequest.tools.length === 0 && providerRequest.tool_choice === "required") {
+ providerRequest.tool_choice = "none";
+ }
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| delegate = { provider: zedProvider, adapter: selected }; | ||||||||||||||||||||||||||||||||
| const requestUrl = `${provider.baseUrl.replace(/\/+$/, "")}/completions`; | ||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -269,7 +269,7 @@ describe("Zed Hosted AI provider", () => { | |
| content: [{ type: "text", text: "hello zed" }], | ||
| }); | ||
| }); | ||
| test("normalizes OpenAI string input to sequence blocks with input_text for Zed backend", async () => { | ||
| test("normalizes OpenAI input and removes Codex-only fields for Zed backend", async () => { | ||
| const responses = [ | ||
| jsonResponse({ default_organization_id: "org-1" }), | ||
| jsonResponse({ token: "llm-token-1" }), | ||
|
|
@@ -287,18 +287,43 @@ describe("Zed Hosted AI provider", () => { | |
| authMode: "oauth", | ||
| apiKey: "access-token", | ||
| }; | ||
| const parsed = parseRequest({ model: "gpt-6.1-sol", input: "hello zed openai", stream: true }); | ||
| const parsed = parseRequest({ | ||
| model: "gpt-6.1-sol", | ||
| input: [ | ||
| { type: "message", role: "developer", content: [{ type: "input_text", text: "Follow the user request." }] }, | ||
| { type: "message", role: "user", content: [{ type: "input_text", text: "hello zed openai" }] }, | ||
| ], | ||
| stream: true, | ||
| access_programs: { cyber: ["standard"] }, | ||
| tools: [ | ||
| { type: "web_search", search_context_size: "medium" }, | ||
| { type: "function", name: "synthetic_tool", parameters: { type: "object", properties: {} } }, | ||
| ], | ||
| }); | ||
| parsed._zedAuthContext = { userId: "user-1" }; | ||
| const adapter = withTestTranslatorBudget(createZedAdapter(provider)); | ||
| const request = await adapter.buildRequest(parsed); | ||
| const body = JSON.parse(request.body) as { provider: string; provider_request: { input: Array<{ type: string; role: string; content: unknown }> } }; | ||
| const body = JSON.parse(request.body) as { | ||
| provider: string; | ||
| provider_request: { | ||
| input: Array<{ type: string; role: string; content: unknown }>; | ||
| tools: Array<{ type: string }>; | ||
| }; | ||
| }; | ||
|
|
||
| expect(body.provider).toBe("open_ai"); | ||
| expect(body.provider_request.input[0]).toEqual({ | ||
| type: "message", | ||
| role: "system", | ||
| content: [{ type: "input_text", text: "Follow the user request." }], | ||
| }); | ||
| expect(body.provider_request.input[1]).toEqual({ | ||
| type: "message", | ||
| role: "user", | ||
| content: [{ type: "input_text", text: "hello zed openai" }], | ||
| }); | ||
| expect(body.provider_request).not.toHaveProperty("access_programs"); | ||
| expect(body.provider_request.tools).toEqual([{ type: "function", name: "synthetic_tool", parameters: { type: "object", properties: {} } }]); | ||
|
Comment on lines
272
to
+326
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value Add test cases for the filter edge cases. The test covers one 🤖 Prompt for AI Agents |
||
| }); | ||
| }); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2] Reconcile tool_choice when dropping built-in tools
For a request with tools: [{type: "web_search"}] and tool_choice: {type: "web_search"}, this filter emits tools: [] while leaving the delegate's forced tool choice unchanged. With tool_choice: "required", removing the only tool similarly makes the request impossible to satisfy. The new regression covers only an automatic choice with a surviving function, so it misses these cases. Validate/reconcile forced and allowed-tools choices against the retained declarations, and return an explicit unsupported-selection refusal where the original choice cannot be honored; do not silently broaden a forced request. Add built-in-only, mixed allowed-tools and retained-function/custom cases.