fix(ai): keep root object type on OpenAI-completions tool parameters - #711
fix(ai): keep root object type on OpenAI-completions tool parameters#711wingwogus wants to merge 1 commit into
Conversation
Combiner normalization moves a function schema root type into anyOf/oneOf/ allOf branches, so tools such as the ast-grep MCP scan tool (type: object plus oneOf) are sent without a root type. Gemini-backed OpenAI-compatible gateways (OpenCode Go / Zen proxying to Console) reject that with "schema must be a JSON Schema of 'type: \"object\"', got 'type: null'". Restore the root type: "object" marker at the wire boundary in convertTools and normalizeRequestToolSchemas while leaving the Moonshot-flavored normalizer behavior untouched. Constraint: OpenAI Chat Completions requires function parameters to be a JSON Schema object rooted at type object Rejected: changing the shared normalizer | pinned Moonshot-flavored behavior and tests Confidence: high Scope-risk: narrow Directive: keep the wire-boundary guard next to convertTools if future normalizers are added Tested: vitest packages/ai (openai-completions-tool-schema-compat), full repo tsc, biome check Not-tested: live Console Go request (no API key in this environment)
|
[sisyphus-bot] Thanks so much for this contribution, @wingwogus — the instinct to guard the root object type was exactly right, and this PR correctly identified a real failure mode when you wrote it. 🙏 Closing as superseded by #718, which landed a fix at the normalizer layer rather than the wire boundary. Here's the concrete state on current
Because that fix lives in the shared normalizer, both OpenAI-completions entry points this PR patched ( It's also strictly weaker than what landed: it stamps The one genuinely-still-open gap in this area is the Responses path: |
Closes #710
Summary
Function tools whose parameters schema uses a combiner (
anyOf/oneOf/allOf) are sent to OpenAI-compatible backends without the roottype: "object"marker, becausenormalizeToolParametersForOpenAICompatmoves the roottypeinto combiner branches and deletes it. Gemini-backed gateways (OpenCode Go / Zen proxying to Console) then reject the request withInvalid schema for function 'mcp__ast_grep_scan': schema must be a JSON Schema of 'type: "object"', got 'type: null'.This change restores the root
type: "object"marker at the OpenAI-completions wire boundary:convertTools(initial tool conversion)normalizeRequestToolSchemas(re-normalization afteronPayload/ injected tools)The Moonshot-flavored normalizer behavior (
toolSchemaFlavor: "moonshot-mfjs") is left untouched.Test plan
packages/ai/test/openai-completions-tool-schema-compat.test.ts: new wire-boundary tests cover both conversion paths with a scan-styletype: "object"+oneOfschema, asserting the roottypeandoneOfsurvive to the actual HTTP request body.npx vitest runinpackages/ai: 9/9 in the compat file, 1773 passed overall (1 unrelated pre-existing import failure inpackages/coding-agent/src/core/tools/diff-render.ts).npm run check) passed: Biome, pinned deps, ts-imports, shrinkwrap, install lock, fulltsc --noEmit, browser smoke.Summary by cubic
Restores the root
type: "object"on function tool parameters sent to OpenAI Chat Completions, so schemas withanyOf/oneOf/allOfare accepted by Gemini-backed OpenAI-compatible gateways.ensureRootObjectTypeand applied it inconvertToolsandnormalizeRequestToolSchemas.$ref-rooted schemas and Moonshot-flavored normalization unchanged.typeand combiners persist for both initial tools andonPayload-injected tools.Written for commit b6a1ace. Summary will update on new commits.