Skip to content

fix(gemini): sanitize JSON-Schema metadata and inline $ref for Gemini (#126)#184

Open
quangdang46 wants to merge 1 commit into
masterfrom
fix/gemini-schema-and-skill-routing
Open

fix(gemini): sanitize JSON-Schema metadata and inline $ref for Gemini (#126)#184
quangdang46 wants to merge 1 commit into
masterfrom
fix/gemini-schema-and-skill-routing

Conversation

@quangdang46
Copy link
Copy Markdown
Owner

What

Gemini's generateContent uses an OpenAPI 3.0 schema subset and rejects standard JSON-Schema metadata ($defs, $ref, $schema, $id, $comment, $anchor, title). MCP servers like notion and supabase emit schemas that combine $defs+$ref to keep their tool surface compact, which crashed the Gemini call when forwarded verbatim.

This addresses issue #126: #126

Changes

  • src/provider/gemini.rs: replace the previous shallow gemini_compatible_schema (only mapped constenum) with a real sanitizer:
    • Extracts $defs / definitions from the root before recursion.
    • For each object node:
      • If it has a $ref, resolve it against the extracted defs map and recurse on the target. Unresolvable refs degrade to a permissive empty object.
      • Otherwise, strip metadata keys Gemini doesn't accept and recurse on each value.
    • Bounds recursion at depth 24 so circular schemas terminate instead of overflowing the stack.
  • src/provider/gemini_tests.rs: 4 regression tests:
    • gemini_schema_inlines_refs_and_strips_metadata
    • gemini_schema_resolves_definitions_alias_too
    • gemini_schema_falls_back_to_empty_object_on_unresolved_ref
    • gemini_schema_does_not_recurse_forever_on_cycles

Tests

$ cargo test -p jcode --lib gemini_schema
test result: ok. 5 passed; 0 failed

(The 5th is the pre-existing build_tools_rewrites_const_for_gemini_schema_compatibility, which keeps passing because the new sanitizer preserves the constenum mapping.)

Notes / scope deviation from upstream

Upstream PR 1jehuang#162 also adds:

  • "Skill" | "skill" => "skill_manage" routing in Registry::canonical_tool_name (src/tool/mod.rs)
  • #[serde(alias = "skill")] on SkillInput::name (src/tool/skill.rs)

Both are already present in this fork — the canonical-tool-name routing is at src/tool/mod.rs:330 and the serde alias is at src/tool/skill.rs:31. So this PR ports only the gemini schema piece, which is the genuinely new behavior for our fork.

Gemini's generateContent uses an OpenAPI 3.0 schema subset and rejects
standard JSON-Schema metadata ($defs, $ref, $schema, $id, $comment,
$anchor, title). MCP servers like notion and supabase emit schemas
that combine $defs+$ref to keep their tool surface compact, which
crashed the Gemini call when forwarded verbatim.

Replace the previous shallow gemini_compatible_schema (only mapped
const→enum) with a real sanitizer:

- Extract $defs / definitions from the root before recursion.
- For each object node:
  - If it has a $ref, resolve it against the extracted defs map and
    recurse on the target. Unresolvable refs degrade to a permissive
    empty object.
  - Otherwise, strip metadata keys Gemini doesn't accept and recurse
    on each value.
- Bound recursion at depth 24 so circular schemas (head -> Loop ->
  head -> …) terminate instead of overflowing the stack.

Add 4 regression tests in src/provider/gemini_tests.rs covering:
- ref inlining + metadata stripping + const→enum after inlining
- legacy 'definitions' alias
- unresolvable $ref → permissive empty object
- circular schema does not recurse forever

The skill-side hunks of upstream PR 1jehuang#162
(API name 'Skill'/'skill' → skill_manage routing + serde alias on
`name`) are NOT applied here — local code already provides both via
`canonical_tool_name` in src/tool/mod.rs:330 and #[serde(alias = "skill")]
on SkillInput.name in src/tool/skill.rs:31.

Closes #126
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