Skip to content

fix(opencode): omit tool_choice for zai providers when tools are empty - #7245

Open
AceRothstein71 wants to merge 1 commit into
code-yeongyu:devfrom
AceRothstein71:issue/6753-glm52-empty-tools-choice
Open

fix(opencode): omit tool_choice for zai providers when tools are empty#7245
AceRothstein71 wants to merge 1 commit into
code-yeongyu:devfrom
AceRothstein71:issue/6753-glm52-empty-tools-choice

Conversation

@AceRothstein71

@AceRothstein71 AceRothstein71 commented Aug 25, 2026

Copy link
Copy Markdown

What

Injects a sanitizing fetch wrapper into the options.fetch of the four Z.AI/Zhipu provider entries (zai, zai-coding-plan, zhipuai, zhipuai-coding-plan) during the plugin config hook, so that tool_choice is dropped from chat-completion request bodies when the effective tools list is empty/absent or when tool_choice names a function not present in tools. Valid requests and SSE responses pass through byte-identical.

New module: packages/omo-opencode/src/features/zai-tool-choice-guard/ wired into config-handler.ts next to applyOpenGatewayProviderConfig.

Why

The bundled @ai-sdk/openai-compatible provider serializes tool_choice unconditionally. Z.AI/GLM endpoints reject such bodies with HTTP 400 code 1210 (AI_APICallError: API 调用参数有误), so Atlas/Sisyphus turns that force tool_choice:"required" fail intermittently on zai-coding-plan/glm-5.2 whenever MCP/provider-defined tools leave the function-tool list empty (#6753).

OMO cannot patch the vendored SDK, but opencode's provider pipeline makes an in-process fix possible (verified against opencode dev source and the installed 1.18.16 binary):

  • plugin config() hooks run before cfg.provider is read (packages/opencode/src/provider/provider.ts ~1420-1424)
  • config provider entries are deep-merged over the models.dev catalog entry, preserving its models (~1462-1471, re-applied ~1628-1634)
  • the openai-compatible loader passes options.fetch through to createOpenAICompatible

So the guard covers every request path for those providers without a local proxy, and stays inert for all other providers.

Verified

  • Failing-first: regression tests written before the implementation; red confirmed (Cannot find module './index'), then green after implementing.
  • New co-located suite (19 given/when/then tests): tool_choice stripped for absent/empty tools and missing named function; byte-identical passthrough for valid choices and choice-less bodies; non-JSON body verbatim; SSE Response passthrough; user fetch composed not replaced; idempotent brand prevents double-wrap; Request-object inputs handled.
  • Wiring tests: creates missing zai entries with options.fetch, preserves existing models/options on them, leaves anthropic/github-copilot untouched, second pass is a no-op.
  • Scoped suites: guard + config-handler.test.ts = 71 pass / 0 fail; wider net (plugin-handlers + opengateway-provider) = 266 pass / 0 fail.
  • bun run typecheck exit 0 (tsgo root + script + packages).
  • Evidence recorded under .omo/evidence/20260824-6753-glm52-tool-choice/ (WHAT TESTED / OBSERVED / WHY ENOUGH / OMITTED), committed with git add -f.

Risk

Low and contained: only zai-family provider entries gain an options.fetch wrapper; every other provider is untouched. If a future opencode stops honoring options.fetch, the wrapper becomes inert rather than breaking requests. The upstream SDK-level fix remains the complete remedy for non-zai openai-compatible providers with the same validation behavior.

Fixes #6753


Summary by cubic

Drops invalid tool_choice from Z.AI/Zhipu chat-completion requests to prevent GLM 400 (code 1210) when tools are empty or missing. Previously we always sent tool_choice from @ai-sdk/openai-compatible; now a sanitizing fetch runs only for zai-family providers, leaving valid requests and streaming unchanged.

  • Injects applyZaiToolChoiceGuard in config-handler.ts to wrap options.fetch for zai, zai-coding-plan, zhipuai, and zhipuai-coding-plan. User-defined fetch is wrapped, not replaced; wrapper is idempotent and passes non-JSON bodies and SSE through unchanged.
  • Adds packages/omo-opencode/src/features/zai-tool-choice-guard/ (sanitizer + tests). Only these providers are mutated; existing models/options are preserved; other providers are untouched. Verified by scoped tests (71 pass), broader suites (266 pass), and repo typecheck.

Written for commit fc943f2. Summary will update on new commits.

Review in cubic

Z.AI/GLM endpoints reject chat-completion requests that carry tool_choice
with an empty or missing tools array (HTTP 400, code 1210, 'API 调用参数有误').
The bundled @ai-sdk/openai-compatible provider serializes tool_choice
unconditionally, so Atlas/Sisyphus turns that force tool_choice:"required"
fail intermittently on zai-coding-plan/glm-5.2 when MCP/provider-defined
tools leave the function list empty.

Inject a sanitizing fetch wrapper into the options.fetch of the four
zai-family provider entries (zai, zai-coding-plan, zhipuai,
zhipuai-coding-plan) during the plugin config hook. opencode reads provider
entries after plugin config hooks run and forwards options.fetch to
createOpenAICompatible, so request bodies are sanitized in-process:
tool_choice is dropped when tools are absent/empty or name a missing
function; valid requests and SSE responses pass through byte-identical.
User-set fetch functions are wrapped, never replaced, and the wrapper is
idempotent.

Verified: 19 new failing-first regression tests green (scoped suite 71 pass),
wider scoped net 266 pass, bun run typecheck exit 0. Evidence under
.omo/evidence/20260824-6753-glm52-tool-choice/.

Fixes code-yeongyu#6753
@github-actions github-actions Bot added the opencode OpenCode edition: packages/omo-opencode label Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

opencode OpenCode edition: packages/omo-opencode

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: zai-coding-plan/glm-5.2 intermittent AI_APICallError: API 调用参数有误 — AI SDK sends tool_choice with empty tools array

1 participant