fix(skill+gemini): forgive Skill schema confusion + sanitize JSON-Schema for Gemini#162
Open
setterapp wants to merge 2 commits into
Open
fix(skill+gemini): forgive Skill schema confusion + sanitize JSON-Schema for Gemini#162setterapp wants to merge 2 commits into
setterapp wants to merge 2 commits into
Conversation
added 2 commits
May 7, 2026 14:30
The agent harness exposed the internal `skill_manage` tool name to models
without remap, causing model calls like `skill_manage(action="load")` with
no `name` to fail with "'name' is required for load action".
Three changes:
1. Map public `Skill` (and lowercase `skill`) tool name to internal
`skill_manage` in resolve_tool_name (mod.rs)
2. Translate `{skill: "X"}` -> `{action: "load", name: "X"}` in
transform_input so the public Skill schema works upstream
3. Add `serde(alias = "skill")` to SkillInput.name and improve error
message in load_skill so direct calls to skill_manage are forgiving
Also added missing `json` import in mod.rs.
Gemini's generateContent endpoint uses an OpenAPI 3.0 schema subset,
not full JSON Schema. It rejects requests with HTTP 400 when tool
parameters contain `$defs`, `$ref`, `$schema`, etc. — which is exactly
what most MCP servers (notion, supabase, etc.) emit, since they generate
schemas from typed code (Pydantic, Zod, etc.) that produces $defs+$ref.
Reproduce: configure any MCP server whose tools have nested object types
(e.g. supabase, notion), then `jcode --provider gemini run "hi"` —
fails with errors like:
Unknown name "$defs" at 'request.tools[0].function_declarations[17].parameters'
Unknown name "$ref" at 'request.tools[0].function_declarations[18]...'
Unknown name "$schema" at 'request.tools[0].function_declarations[40]...'
Fix: extend `gemini_compatible_schema` to:
1. Extract $defs (or `definitions` for older drafts) from the root.
2. Recursively walk the schema, resolving $ref to the corresponding $def
and inlining the target.
3. Strip JSON-Schema metadata keywords ($defs, $schema, $id, $comment,
$anchor, definitions, title) at every level.
4. Cap recursion depth at 24 to prevent blow-up on circular schemas.
Preserves the existing `const → enum` rewrite. No public API changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR fixes two model-side / provider-side bugs surfaced when running v0.12.0 against MCP servers (notion, supabase) on macOS.
Fix 1:
skill_manageschema confusion (commit f477bd4)Models invoking jcode produce malformed calls like
skill_manage(action="load")(missingname) and error with'name' is required for load action. Root cause: the public-facing tool isSkillwith schema{skill}, internal tool isskill_managewith schema{action, name}. The translation layer insrc/tool/mod.rswas half-applied at v0.12.0 (mapping draft +transform_inputwere committed but thejsonimport was missing — the file did not compile).This PR:
jsonimport tosrc/tool/mod.rs.Skill | skill -> skill_manageresolve and{skill: X} -> {action: load, name: X}transform.#[serde(alias = "skill")]toSkillInput.namesoskill_managecalled directly with{action: load, skill: brain}also works.nameis genuinely missing.Fix 2: Gemini rejects MCP tool schemas (commit on same branch)
Gemini's
generateContentendpoint uses an OpenAPI 3.0 schema subset and rejects requests with HTTP 400 when tool parameters contain$defs,$ref, or$schema— which is exactly what most MCP servers emit (notion, supabase, etc.) because they generate schemas from typed code (Pydantic / Zod) that produces$defs+$ref.Repro: configure notion or supabase MCP, then
jcode --provider gemini run "hi"— fails with hundreds of errors like:This PR extends
gemini_compatible_schemainsrc/provider/gemini.rsto:$defs(ordefinitions) from the root.$refto the corresponding$defand inlining the target.$defs,$schema,$id,$comment,$anchor,definitions,title) at every level.Preserves the existing
const → enumrewrite.Tested
cargo check -p jcode --bin jcode— passescargo build --release -p jcode --bin jcode— passes (4m24s first build, 1m08s incremental)jcodeskill end-to-end with Claude Sonnet OAuth in TUI.jcode --provider gemini --model gemini-2.5-flash run "Reply only OK"from setterapp dir (notion + supabase-dev + supabase-prod MCPs configured) — works after fix, was failing with HTTP 400 before.Diff stat
No public API changes.