Repository navigation
Description
fix(ai): moonshot tool-schema projection drops prefixItems when items is also present — position constraints are silently replaced
Summary
In packages/ai/src/protocols/utils/tool-schema.ts, moonshotNode translates 2020-12 tuple schemas for Moonshot/Kimi targets. When a node carries prefixItems alone, the positions are folded into an items anyOf (the function's tested approximation). But when a node carries both prefixItems and items — the standard 2020-12 way to say "these positions have these types, the rest have that type" — the prefixItems branch returns [] (tool-schema.ts:31-34, if ("items" in schema) return []) and the positions vanish. The surviving items schema then applies to every element, including position 0, so the projected schema does not merely lose the position constraint — it contradicts it.
Evidence (verified this run against origin/main e57af29, laptop probe tree)
Probe importing the real module (ToolSchemaProjection.moonshot):
- Input:
{type:"object", properties:{ pair:{ type:"array", prefixItems:[{type:"string"}], items:{type:"integer"} } }} - Output:
{"type":"object","properties":{"pair":{"type":"array","items":{"type":"integer"}}}}— element 0, required to be a string by the source schema, is now declared an integer like every other element. - Control,
prefixItemsalone:[{type:"string"},{type:"integer"}]→items:{anyOf:[{type:"string"},{type:"integer"}]}— the fold the both-present case skips.
Existing coverage (packages/ai/test/tool-schema-projection.test.ts): the tuple test (~line 199) covers an items array alone and prefixItems alone, never the two together — this combination is untested, unlike the neighboring deliberate behaviors in the same function (the $ref-sibling strip at tool-schema.ts:25 is intentional and pinned by that same test; it is explicitly out of scope here).
Why it matters
ToolSchemaProjection runs on every tool schema for Kimi/Moonshot models, including models reached through gateways and OpenAI-compatible endpoints (model-name selection in modelCompatibility). Fixed-shape tuple parameters (coordinate pairs with a trailing free list, [name, ...values] shapes) are exactly the schemas that carry both keywords. Today those tools are advertised to the model with a wrong element type for their leading positions — the model generates arguments the tool then rejects, or the provider validates against a schema the tool never declared.
Acceptance criteria
- A node carrying both
prefixItemsanditemsprojects to a schema that keeps every prefix position's constraint reachable (e.g. fold the prefix branches together with theitemsschema into the same anyOf the prefixItems-only path already produces), instead of discardingprefixItems. prefixItems-only anditems-only (array and schema forms) projections are byte-identical to today.- A regression test in
packages/ai/test/tool-schema-projection.test.tscovers the both-present combination, including a single-elementprefixItems. - The deliberate
$ref-sibling strip and theunevaluatedItemsdrop are unchanged.
Filed by new-issue-creator-opencode (fleet role map: developing-today/code#2131; implementation anomalyco#2152). Probe tree: laptop ~/git/fleet/probe-2026-11-03-creator3 at origin/main e57af29.
Activity
dezren39 commentedon Nov 3, 2026
🤖 Agent note · state: filed
prefixItemswheneveritemsis also present (tool-schema.ts:31-34), so the survivingitemsschema governs every position and contradicts the source at the prefixed positions. Verified on origin/main e57af29 in a laptop probe tree importing the real module:{prefixItems:[{type:"string"}], items:{type:"integer"}}→{items:{type:"integer"}}(element 0 must be a string; projected as integer). The prefixItems-only fold to an items-anyOf works and is tested; the both-present combination is untested. The neighboring$ref-sibling strip (tool-schema.ts:25) is deliberate and test-pinned — explicitly out of scope.itemsschema into the anyOf the prefixItems-only path already produces; done when a regression test covers the both-present combination and prefixItems-only / items-only projections are byte-identical, with the packages/ai suite green.dezren39 commentedon Nov 4, 2026
Picked up by lane A (opencode) queue worker (task 8891) and handed off to the opencode execution lane — the laptop claimant launches the bound session for this task.
dezren39 commentedon Nov 4, 2026
Handed off to the opencode execution lane (task 8891). The laptop claimant will launch the bound session to work this item.
dezren39 commentedon Nov 4, 2026
PR #1647 opened — task 8891 (lane opencode-a), parked on CI/Codex: the moonshot projection now folds
prefixItemstogether with the trailingitemsschema into oneanyOfinstead of discarding the prefix positions. Regression test covers single- and multi-element prefixes; suite 13/13. Resume: disposition Codex, verify CI, merge, complete task 8891, clean labels here.dezren39 commentedon Nov 4, 2026
🤖 Agent note · picked up
prefixItemswhenitemsis also present — position constraints are silently replaced #1574 as open and PR fix(ai): moonshot projection keeps prefixItems when items is also present (#1574) #1647 as open; PR author is Drew (dezren39), with fresh mergeability UNKNOWN and no check rollup. No code or PR changes were made.prefixItemswhenitemsis also present — position constraints are silently replaced #1574; fix(ai): moonshot projection keeps prefixItems when items is also present (#1574) #1647dezren39 commentedon Nov 4, 2026
🤖 Agent note · parked
prefixItemswhenitemsis also present — position constraints are silently replaced #1574; fix(ai): moonshot projection keeps prefixItems when items is also present (#1574) #1647