Skip to content

fix(skill+gemini): forgive Skill schema confusion + sanitize JSON-Schema for Gemini#162

Open
setterapp wants to merge 2 commits into
1jehuang:masterfrom
setterapp:fix-skill-name-alias
Open

fix(skill+gemini): forgive Skill schema confusion + sanitize JSON-Schema for Gemini#162
setterapp wants to merge 2 commits into
1jehuang:masterfrom
setterapp:fix-skill-name-alias

Conversation

@setterapp
Copy link
Copy Markdown

@setterapp setterapp commented May 7, 2026

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_manage schema confusion (commit f477bd4)

Models invoking jcode produce malformed calls like skill_manage(action="load") (missing name) and error with 'name' is required for load action. Root cause: the public-facing tool is Skill with schema {skill}, internal tool is skill_manage with schema {action, name}. The translation layer in src/tool/mod.rs was half-applied at v0.12.0 (mapping draft + transform_input were committed but the json import was missing — the file did not compile).

This PR:

  1. Adds the missing json import to src/tool/mod.rs.
  2. Keeps the Skill | skill -> skill_manage resolve and {skill: X} -> {action: load, name: X} transform.
  3. Adds #[serde(alias = "skill")] to SkillInput.name so skill_manage called directly with {action: load, skill: brain} also works.
  4. Improves the error message when name is genuinely missing.

Fix 2: Gemini rejects MCP tool schemas (commit on same branch)

Gemini's generateContent endpoint 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:

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]...'

This PR extends gemini_compatible_schema in src/provider/gemini.rs to:

  1. Extract $defs (or definitions) from the root.
  2. Recursively walk the schema, resolving $ref to the corresponding $def and inlining the target.
  3. Strip JSON-Schema metadata ($defs, $schema, $id, $comment, $anchor, definitions, title) at every level.
  4. Cap recursion depth at 24 to handle circular schemas safely.

Preserves the existing const → enum rewrite.


Tested

  • cargo check -p jcode --bin jcode — passes
  • cargo build --release -p jcode --bin jcode — passes (4m24s first build, 1m08s incremental)
  • Smoke 1: Skill tool loads jcode skill end-to-end with Claude Sonnet OAuth in TUI.
  • Smoke 2: 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

 src/tool/mod.rs       | 28 ++++++++++++++++++++++++++++-
 src/tool/skill.rs     | 13 +++++++++++--
 src/provider/gemini.rs | 64 +++++++++++++++++++++++++++++++++++++--
 3 files changed, 100 insertions(+), 5 deletions(-)

No public API changes.

Marcos Pozzetti 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.
@setterapp setterapp changed the title fix(skill): expose Skill alias + accept skill alias in skill_manage fix(skill+gemini): forgive Skill schema confusion + sanitize JSON-Schema for Gemini May 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant