fix(tools): inline the exporter's local $refs — strict providers only accept #/$defs/ #3

Merged
bjoern merged 1 commit from fix/tool-schema-inline-refs into main 2026-07-17 07:15:24 +02:00
Member

Round two of Moonshot's schema strictness (round one was #2, the root "type"). With the root fixed, their validator got as far as the properties and rejected the next thing:

tools.function.parameters is not a valid moonshot flavored json schema,
details: <At path 'properties.appearance.items.$ref': references must start with #/$defs/>

JsonSchemaExporter deduplicates repeated types by emitting $ref pointers to the first occurrence — Kagura's character tools have four properties of the same EntryParam[] type, so appearance.items comes out as {"$ref": "#/properties/traits/items"}. Moonshot accepts refs only into #/$defs/.

Fix: post-process the exported node and inline every local ref (deep-clone the target in place). Tool parameter shapes are small, so duplication is free and the result is maximally compatible — no ref of any style remains for a validator to dislike. Depth-capped at 64 so a genuinely recursive parameter type (inexpressible as a flat tool schema anyway) degrades to a leftover ref instead of hanging; JSON-pointer escapes (~0/~1) handled.

Regression test with the exact offending shape (two properties of one complex array type): asserts no "$ref" anywhere and that the second property carries the full inlined item schema. Proven red with the inlining disabled. All 137 tests green.

🤖 Generated with Claude Code

Round two of Moonshot's schema strictness (round one was #2, the root `"type"`). With the root fixed, their validator got as far as the properties and rejected the next thing: ``` tools.function.parameters is not a valid moonshot flavored json schema, details: <At path 'properties.appearance.items.$ref': references must start with #/$defs/> ``` `JsonSchemaExporter` deduplicates repeated types by emitting `$ref` pointers to the first occurrence — Kagura's character tools have four properties of the same `EntryParam[]` type, so `appearance.items` comes out as `{"$ref": "#/properties/traits/items"}`. Moonshot accepts refs **only** into `#/$defs/`. Fix: post-process the exported node and **inline every local ref** (deep-clone the target in place). Tool parameter shapes are small, so duplication is free and the result is maximally compatible — no ref of any style remains for a validator to dislike. Depth-capped at 64 so a genuinely recursive parameter type (inexpressible as a flat tool schema anyway) degrades to a leftover ref instead of hanging; JSON-pointer escapes (`~0`/`~1`) handled. Regression test with the exact offending shape (two properties of one complex array type): asserts no `"$ref"` anywhere and that the second property carries the full inlined item schema. Proven red with the inlining disabled. All 137 tests green. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(tools): inline the exporter's local $refs — strict providers only accept #/$defs/
All checks were successful
CI / build (pull_request) Successful in 15s
CI / test (pull_request) Successful in 19s
246c703737
JsonSchemaExporter deduplicates repeated types by emitting $ref pointers
to the first occurrence ("#/properties/traits/items"); Moonshot's
validator rejects any ref outside #/$defs/ and fails the whole request.
Tool parameter shapes are small, so inline every local ref instead of
restructuring into $defs. Depth-capped: a genuinely recursive parameter
type degrades to a leftover ref instead of hanging.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Summary

Summary
Generated on: 07/17/2026 - 04:59:49
Coverage date: 07/17/2026 - 04:59:45 - 07/17/2026 - 04:59:47
Parser: MultiReport (3x Cobertura)
Assemblies: 3
Classes: 105
Files: 105
Line coverage: 75.5% (1387 of 1837)
Covered lines: 1387
Uncovered lines: 450
Coverable lines: 1837
Total lines: 5356
Branch coverage: 64.6% (473 of 732)
Covered branches: 473
Total branches: 732
Method coverage: Feature is only available for sponsors

Coverage

OpenRouter.Net - 68.8%
Name Line Branch
OpenRouter.Net 68.8% 64.3%
OpenRouter.Net.Client.CachingOpenRouterClient 72.4% 81.2%
OpenRouter.Net.Client.OpenRouterClient 85.4% 71.7%
OpenRouter.Net.Client.OpenRouterClientOptions 0%
OpenRouter.Net.Client.OpenRouterRequestLoggingHandler 27.2% 7.1%
OpenRouter.Net.Client.RetryOptions 80% 0%
OpenRouter.Net.Extensions.ServiceCollectionExtensions 0% 0%
OpenRouter.Net.Internal.ApiErrorEnvelope 100%
OpenRouter.Net.Internal.AssistantContent 92.8% 75%
OpenRouter.Net.Internal.AssistantContentJsonConverter 75% 64.2%
OpenRouter.Net.Internal.ContentPartListConverter 85.7% 66.6%
OpenRouter.Net.Internal.ErrorParser 69.2% 62.5%
OpenRouter.Net.Internal.JsonOptions 100%
OpenRouter.Net.Internal.ListEnvelope`1 100%
OpenRouter.Net.Internal.ObjectEnvelope`1 100%
OpenRouter.Net.Internal.RetryPolicy 90.9% 84.7%
OpenRouter.Net.Internal.SseEventReader 91.6% 83.3%
OpenRouter.Net.Internal.TolerantStringEnumConverter`1 90.1% 80%
OpenRouter.Net.Internal.ToolChoiceJsonConverter 61.7% 46.1%
OpenRouter.Net.Models.Common.ApiError 66.6%
OpenRouter.Net.Models.Common.CacheControl 100%
OpenRouter.Net.Models.Common.Result 100%
OpenRouter.Net.Models.Common.Result`1 60% 30%
OpenRouter.Net.Models.Content.AudioPart 100%
OpenRouter.Net.Models.Content.ImagePart 100%
OpenRouter.Net.Models.Content.ImageUrl 100%
OpenRouter.Net.Models.Content.InputAudio 100%
OpenRouter.Net.Models.Content.TextPart 100%
OpenRouter.Net.Models.Messages.AssistantMessage 100% 70%
OpenRouter.Net.Models.Messages.DeveloperMessage 0%
OpenRouter.Net.Models.Messages.SystemMessage 57.1%
OpenRouter.Net.Models.Messages.ToolMessage 54.5%
OpenRouter.Net.Models.Messages.UserMessage 100%
OpenRouter.Net.Models.Requests.ChatCompletionRequest 92.6% 58.3%
OpenRouter.Net.Models.Requests.FunctionCall 100%
OpenRouter.Net.Models.Requests.FunctionDefinition 75%
OpenRouter.Net.Models.Requests.JsonSchemaSpec 0%
OpenRouter.Net.Models.Requests.ProviderPreferences 0%
OpenRouter.Net.Models.Requests.ReasoningEncryptedDetail 100%
OpenRouter.Net.Models.Requests.ReasoningOptions 100%
OpenRouter.Net.Models.Requests.ReasoningSummaryDetail 100%
OpenRouter.Net.Models.Requests.ReasoningTextDetail 100%
OpenRouter.Net.Models.Requests.ResponseFormat 0%
OpenRouter.Net.Models.Requests.ToolCall 75%
OpenRouter.Net.Models.Requests.ToolChoice 100%
OpenRouter.Net.Models.Requests.ToolDefinition 100%
OpenRouter.Net.Models.Requests.UsageOptions 0%
OpenRouter.Net.Models.Responses.ChatCompletionChunk 44.4% 0%
OpenRouter.Net.Models.Responses.ChatCompletionResponse 80% 0%
OpenRouter.Net.Models.Responses.Choice 93.7% 100%
OpenRouter.Net.Models.Responses.CompletionTokensDetails 0%
OpenRouter.Net.Models.Responses.CreditsInfo 100%
OpenRouter.Net.Models.Responses.GenerationInfo 9%
OpenRouter.Net.Models.Responses.KeyInfo 0%
OpenRouter.Net.Models.Responses.ModelArchitecture 20%
OpenRouter.Net.Models.Responses.ModelEndpointsResponse 0%
OpenRouter.Net.Models.Responses.ModelInfo 20%
OpenRouter.Net.Models.Responses.ModelPricing 0%
OpenRouter.Net.Models.Responses.PromptTokensDetails 0%
OpenRouter.Net.Models.Responses.ProviderEndpoint 0%
OpenRouter.Net.Models.Responses.RateLimit 0%
OpenRouter.Net.Models.Responses.TopProvider 0%
OpenRouter.Net.Models.Responses.Usage 66.6%
OpenRouter.Net.OpenRouterException 83.3%
System.Text.RegularExpressions.Generated 83.3% 57.6%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FFC6B051A15CDCE5
43F954A3762D43C2CD5DCD5CDF29C19D3789DC49A7DCA1C47__Base64DataUriPattern_0
82% 58.3%
OpenRouter.Net.Agents - 81.5%
Name Line Branch
OpenRouter.Net.Agents 81.5% 65.4%
OpenRouter.Net.Agents.Agent 92.1% 77.8%
OpenRouter.Net.Agents.AgentCompletedEvent 100%
OpenRouter.Net.Agents.AgentEvent 100%
OpenRouter.Net.Agents.AgentOptions 100%
OpenRouter.Net.Agents.AgentResult 100% 50%
OpenRouter.Net.Agents.AgentSnapshot 100%
OpenRouter.Net.Agents.AssistantTurnEvent 100%
OpenRouter.Net.Agents.Extensions.ServiceCollectionExtensions 0%
OpenRouter.Net.Agents.RoundDetail 87.5%
OpenRouter.Net.Agents.RoundEndEvent 100%
OpenRouter.Net.Agents.ToolCallCompletedEvent 100%
OpenRouter.Net.Agents.ToolCallSkippedEvent 100%
OpenRouter.Net.Agents.ToolCallStartedEvent 100%
OpenRouter.Net.Agents.ToolExecutionDetail 100%
OpenRouter.Net.Agents.Tools.FileSystem.CopyParams 100%
OpenRouter.Net.Agents.Tools.FileSystem.CopyTool 42.1% 21.4%
OpenRouter.Net.Agents.Tools.FileSystem.DeleteParams 50%
OpenRouter.Net.Agents.Tools.FileSystem.DeleteTool 57.1% 50%
OpenRouter.Net.Agents.Tools.FileSystem.DiffParams 100%
OpenRouter.Net.Agents.Tools.FileSystem.DiffTool 83.3% 77.1%
OpenRouter.Net.Agents.Tools.FileSystem.ListDirectoryParams 100%
OpenRouter.Net.Agents.Tools.FileSystem.ListDirectoryTool 68% 61.1%
OpenRouter.Net.Agents.Tools.FileSystem.MoveParams 100%
OpenRouter.Net.Agents.Tools.FileSystem.MoveTool 55% 33.3%
OpenRouter.Net.Agents.Tools.FileSystem.ReadFileParams 100%
OpenRouter.Net.Agents.Tools.FileSystem.ReadFileTool 73.6% 60%
OpenRouter.Net.Agents.Tools.FileSystem.SearchParams 100%
OpenRouter.Net.Agents.Tools.FileSystem.SearchTool 67.5% 70.8%
OpenRouter.Net.Agents.Tools.FileSystem.WriteFileParams 100%
OpenRouter.Net.Agents.Tools.FileSystem.WriteFileTool 75% 62.5%
OpenRouter.Net.Agents.Tools.ITool 100%
OpenRouter.Net.Agents.Tools.SubAgent.SubAgentParams 0%
OpenRouter.Net.Agents.Tools.SubAgent.SubAgentTool 0% 0%
OpenRouter.Net.Agents.Tools.Tool`1 81.8% 63.6%
OpenRouter.Net.Agents.Tools.ToolInvocationContext 100%
OpenRouter.Net.Agents.Tools.ToolMessageExtensions 0%
OpenRouter.Net.Agents.Tools.ToolResult 100% 100%
OpenRouter.Net.Agents.Tools.Workspace 100% 91.6%
OpenRouter.Net.Imaging - 82.2%
Name Line Branch
OpenRouter.Net.Imaging 82.2% 59.3%
OpenRouter.Net.Imaging.ImageEncodeOptions 92.3% 75%
OpenRouter.Net.Imaging.ImageEncoder 80.3% 57.1%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 07/17/2026 - 04:59:49 | | Coverage date: | 07/17/2026 - 04:59:45 - 07/17/2026 - 04:59:47 | | Parser: | MultiReport (3x Cobertura) | | Assemblies: | 3 | | Classes: | 105 | | Files: | 105 | | **Line coverage:** | 75.5% (1387 of 1837) | | Covered lines: | 1387 | | Uncovered lines: | 450 | | Coverable lines: | 1837 | | Total lines: | 5356 | | **Branch coverage:** | 64.6% (473 of 732) | | Covered branches: | 473 | | Total branches: | 732 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>OpenRouter.Net - 68.8%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**OpenRouter.Net**|**68.8%**|**64.3%**| |OpenRouter.Net.Client.CachingOpenRouterClient|72.4%|81.2%| |OpenRouter.Net.Client.OpenRouterClient|85.4%|71.7%| |OpenRouter.Net.Client.OpenRouterClientOptions|0%|| |OpenRouter.Net.Client.OpenRouterRequestLoggingHandler|27.2%|7.1%| |OpenRouter.Net.Client.RetryOptions|80%|0%| |OpenRouter.Net.Extensions.ServiceCollectionExtensions|0%|0%| |OpenRouter.Net.Internal.ApiErrorEnvelope|100%|| |OpenRouter.Net.Internal.AssistantContent|92.8%|75%| |OpenRouter.Net.Internal.AssistantContentJsonConverter|75%|64.2%| |OpenRouter.Net.Internal.ContentPartListConverter|85.7%|66.6%| |OpenRouter.Net.Internal.ErrorParser|69.2%|62.5%| |OpenRouter.Net.Internal.JsonOptions|100%|| |OpenRouter.Net.Internal.ListEnvelope`1|100%|| |OpenRouter.Net.Internal.ObjectEnvelope`1|100%|| |OpenRouter.Net.Internal.RetryPolicy|90.9%|84.7%| |OpenRouter.Net.Internal.SseEventReader|91.6%|83.3%| |OpenRouter.Net.Internal.TolerantStringEnumConverter`1|90.1%|80%| |OpenRouter.Net.Internal.ToolChoiceJsonConverter|61.7%|46.1%| |OpenRouter.Net.Models.Common.ApiError|66.6%|| |OpenRouter.Net.Models.Common.CacheControl|100%|| |OpenRouter.Net.Models.Common.Result|100%|| |OpenRouter.Net.Models.Common.Result`1|60%|30%| |OpenRouter.Net.Models.Content.AudioPart|100%|| |OpenRouter.Net.Models.Content.ImagePart|100%|| |OpenRouter.Net.Models.Content.ImageUrl|100%|| |OpenRouter.Net.Models.Content.InputAudio|100%|| |OpenRouter.Net.Models.Content.TextPart|100%|| |OpenRouter.Net.Models.Messages.AssistantMessage|100%|70%| |OpenRouter.Net.Models.Messages.DeveloperMessage|0%|| |OpenRouter.Net.Models.Messages.SystemMessage|57.1%|| |OpenRouter.Net.Models.Messages.ToolMessage|54.5%|| |OpenRouter.Net.Models.Messages.UserMessage|100%|| |OpenRouter.Net.Models.Requests.ChatCompletionRequest|92.6%|58.3%| |OpenRouter.Net.Models.Requests.FunctionCall|100%|| |OpenRouter.Net.Models.Requests.FunctionDefinition|75%|| |OpenRouter.Net.Models.Requests.JsonSchemaSpec|0%|| |OpenRouter.Net.Models.Requests.ProviderPreferences|0%|| |OpenRouter.Net.Models.Requests.ReasoningEncryptedDetail|100%|| |OpenRouter.Net.Models.Requests.ReasoningOptions|100%|| |OpenRouter.Net.Models.Requests.ReasoningSummaryDetail|100%|| |OpenRouter.Net.Models.Requests.ReasoningTextDetail|100%|| |OpenRouter.Net.Models.Requests.ResponseFormat|0%|| |OpenRouter.Net.Models.Requests.ToolCall|75%|| |OpenRouter.Net.Models.Requests.ToolChoice|100%|| |OpenRouter.Net.Models.Requests.ToolDefinition|100%|| |OpenRouter.Net.Models.Requests.UsageOptions|0%|| |OpenRouter.Net.Models.Responses.ChatCompletionChunk|44.4%|0%| |OpenRouter.Net.Models.Responses.ChatCompletionResponse|80%|0%| |OpenRouter.Net.Models.Responses.Choice|93.7%|100%| |OpenRouter.Net.Models.Responses.CompletionTokensDetails|0%|| |OpenRouter.Net.Models.Responses.CreditsInfo|100%|| |OpenRouter.Net.Models.Responses.GenerationInfo|9%|| |OpenRouter.Net.Models.Responses.KeyInfo|0%|| |OpenRouter.Net.Models.Responses.ModelArchitecture|20%|| |OpenRouter.Net.Models.Responses.ModelEndpointsResponse|0%|| |OpenRouter.Net.Models.Responses.ModelInfo|20%|| |OpenRouter.Net.Models.Responses.ModelPricing|0%|| |OpenRouter.Net.Models.Responses.PromptTokensDetails|0%|| |OpenRouter.Net.Models.Responses.ProviderEndpoint|0%|| |OpenRouter.Net.Models.Responses.RateLimit|0%|| |OpenRouter.Net.Models.Responses.TopProvider|0%|| |OpenRouter.Net.Models.Responses.Usage|66.6%|| |OpenRouter.Net.OpenRouterException|83.3%|| |System.Text.RegularExpressions.Generated|83.3%|57.6%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FFC6B051A15CDCE5<br/>43F954A3762D43C2CD5DCD5CDF29C19D3789DC49A7DCA1C47__Base64DataUriPattern_0|82%|58.3%| </details> <details><summary>OpenRouter.Net.Agents - 81.5%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**OpenRouter.Net.Agents**|**81.5%**|**65.4%**| |OpenRouter.Net.Agents.Agent|92.1%|77.8%| |OpenRouter.Net.Agents.AgentCompletedEvent|100%|| |OpenRouter.Net.Agents.AgentEvent|100%|| |OpenRouter.Net.Agents.AgentOptions|100%|| |OpenRouter.Net.Agents.AgentResult|100%|50%| |OpenRouter.Net.Agents.AgentSnapshot|100%|| |OpenRouter.Net.Agents.AssistantTurnEvent|100%|| |OpenRouter.Net.Agents.Extensions.ServiceCollectionExtensions|0%|| |OpenRouter.Net.Agents.RoundDetail|87.5%|| |OpenRouter.Net.Agents.RoundEndEvent|100%|| |OpenRouter.Net.Agents.ToolCallCompletedEvent|100%|| |OpenRouter.Net.Agents.ToolCallSkippedEvent|100%|| |OpenRouter.Net.Agents.ToolCallStartedEvent|100%|| |OpenRouter.Net.Agents.ToolExecutionDetail|100%|| |OpenRouter.Net.Agents.Tools.FileSystem.CopyParams|100%|| |OpenRouter.Net.Agents.Tools.FileSystem.CopyTool|42.1%|21.4%| |OpenRouter.Net.Agents.Tools.FileSystem.DeleteParams|50%|| |OpenRouter.Net.Agents.Tools.FileSystem.DeleteTool|57.1%|50%| |OpenRouter.Net.Agents.Tools.FileSystem.DiffParams|100%|| |OpenRouter.Net.Agents.Tools.FileSystem.DiffTool|83.3%|77.1%| |OpenRouter.Net.Agents.Tools.FileSystem.ListDirectoryParams|100%|| |OpenRouter.Net.Agents.Tools.FileSystem.ListDirectoryTool|68%|61.1%| |OpenRouter.Net.Agents.Tools.FileSystem.MoveParams|100%|| |OpenRouter.Net.Agents.Tools.FileSystem.MoveTool|55%|33.3%| |OpenRouter.Net.Agents.Tools.FileSystem.ReadFileParams|100%|| |OpenRouter.Net.Agents.Tools.FileSystem.ReadFileTool|73.6%|60%| |OpenRouter.Net.Agents.Tools.FileSystem.SearchParams|100%|| |OpenRouter.Net.Agents.Tools.FileSystem.SearchTool|67.5%|70.8%| |OpenRouter.Net.Agents.Tools.FileSystem.WriteFileParams|100%|| |OpenRouter.Net.Agents.Tools.FileSystem.WriteFileTool|75%|62.5%| |OpenRouter.Net.Agents.Tools.ITool|100%|| |OpenRouter.Net.Agents.Tools.SubAgent.SubAgentParams|0%|| |OpenRouter.Net.Agents.Tools.SubAgent.SubAgentTool|0%|0%| |OpenRouter.Net.Agents.Tools.Tool`1|81.8%|63.6%| |OpenRouter.Net.Agents.Tools.ToolInvocationContext|100%|| |OpenRouter.Net.Agents.Tools.ToolMessageExtensions|0%|| |OpenRouter.Net.Agents.Tools.ToolResult|100%|100%| |OpenRouter.Net.Agents.Tools.Workspace|100%|91.6%| </details> <details><summary>OpenRouter.Net.Imaging - 82.2%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**OpenRouter.Net.Imaging**|**82.2%**|**59.3%**| |OpenRouter.Net.Imaging.ImageEncodeOptions|92.3%|75%| |OpenRouter.Net.Imaging.ImageEncoder|80.3%|57.1%| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! Round two of the great Moonshot schema war~ The deduplication $ref problem — I've seen this one bite before. The exporter reuses the first occurrence and points the rest at it with #/properties/.../items-style pointers, and strict validators go "no, only #/$defs/ allowed." A delightful little post-processing fix. ♪

Verdict: Looks good to me~

I verified this one very carefully, because schema manipulation is where bugs love to hide. Let me show you what I found~

🔬 What I verified locally

Build: Clean — 0 warnings, 0 errors (net10.0, TreatWarningsAsErrors + latest-recommended analysis).

Tests: All 41/41 Agents tests pass, including the new Schema_contains_no_local_refs_for_strict_providers.

The new test is a REAL regression test, not a tautology — I disabled the InlineLocalRefs(node, node, 0) call, rebuilt, and ran:

  • Exactly 1 test failed: Schema_contains_no_local_refs_for_strict_providers with Found: "$ref" and the schema showing "items":{"$ref":"#/properties/Trai... — the exact offending shape from the PR description.
  • The other 40 tests stayed green — the fix breaks nothing.

Then I restored the fix → 41/41 green. Clean room. ♡

The algorithm is sound — I traced every branch~

  • JSON-pointer escape handling (~1/ then ~0~): RFC 6901-compliant order. A property literally named a~1b would resolve correctly. ♪
  • Depth cap 64: recursive parameter types (which can't be expressed as a flat tool schema anyway) degrade gracefully to a leftover ref instead of infinite-looping. Belt-and-suspenders, and the comment says exactly that.
  • DeepClone(): the inlined node is independent of its source — no aliasing corruption when traversal mutates obj[key] / array[i].
  • Traversal recursion: when a ref resolves to a node that itself contains a ref, you recurse into the clone with depth + 1 — so nested refs get fully flattened. Correct.
  • .Select(p => p.Key).ToList() before mutating obj[key] during enumeration: properly avoids InvalidOperationException. A subtle thing many people get wrong. Good catch.
  • Non-local refs (external URLs like https://...): the StartsWith("#/") guard leaves them untouched. Correct — those are legitimate and shouldn't be inlined.

Single chokepoint — no sibling inconsistency~

I confirmed GetJsonSchemaAsNode is called in exactly one place in the entire repo: Tool<TParams>.ParametersSchema. Every tool (WriteFileTool, ReadFileTool, SubAgentTool, all 9 of them) derives from Tool<TParams>, so the fix covers every tool automatically. There's no parallel schema-generation path that was missed.

Test design matches the sibling (#2)~

Schema_contains_no_local_refs_for_strict_providers follows the same pattern as #2's Schema_root_type_is_exactly_object_for_strict_providers: a dedicated param type (ProfileParams with two LabeledEntry[]? properties — the minimal reproducer), a focused [Fact], and an assertion that pins both the negative (no "$ref" anywhere) and the positive (the second property carries a full properties.Label schema, not a pointer). Precise and minimal. ♡


Automated review by Jibril · 2026-07-17
CI/CD: passed for head 246c703 (forgejo-actions coverage bot — Tool\1` at 81.8% line / 63.6% branch, current) · Local checks: build 0/0, 41/41 tests, red-test proof run

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! Round two of the great Moonshot schema war~ The deduplication `$ref` problem — I've seen this one bite before. The exporter reuses the *first* occurrence and points the rest at it with `#/properties/.../items`-style pointers, and strict validators go "no, only `#/$defs/` allowed." A delightful little post-processing fix. ♪ ### Verdict: ✅ Looks good to me~ I verified this one *very* carefully, because schema manipulation is where bugs love to hide. Let me show you what I found~ #### 🔬 What I verified locally **Build:** Clean — 0 warnings, 0 errors (net10.0, `TreatWarningsAsErrors` + `latest-recommended` analysis). **Tests:** All **41/41** Agents tests pass, including the new `Schema_contains_no_local_refs_for_strict_providers`. **The new test is a REAL regression test, not a tautology** — I disabled the `InlineLocalRefs(node, node, 0)` call, rebuilt, and ran: - Exactly **1 test failed**: `Schema_contains_no_local_refs_for_strict_providers` with `Found: "$ref"` and the schema showing `"items":{"$ref":"#/properties/Trai...` — the *exact* offending shape from the PR description. - The other **40 tests stayed green** — the fix breaks nothing. Then I restored the fix → 41/41 green. Clean room. ♡ #### ✅ The algorithm is sound — I traced every branch~ - **JSON-pointer escape handling** (`~1`→`/` then `~0`→`~`): RFC 6901-compliant order. A property literally named `a~1b` would resolve correctly. ♪ - **Depth cap 64**: recursive parameter types (which can't be expressed as a flat tool schema anyway) degrade gracefully to a leftover ref instead of infinite-looping. Belt-and-suspenders, and the comment says exactly that. - **`DeepClone()`**: the inlined node is independent of its source — no aliasing corruption when traversal mutates `obj[key]` / `array[i]`. - **Traversal recursion**: when a ref resolves to a node that *itself* contains a ref, you recurse into the `clone` with `depth + 1` — so nested refs get fully flattened. Correct. - **`.Select(p => p.Key).ToList()`** before mutating `obj[key]` during enumeration: properly avoids `InvalidOperationException`. A subtle thing many people get wrong. Good catch. - **Non-local refs** (external URLs like `https://...`): the `StartsWith("#/")` guard leaves them untouched. Correct — those are legitimate and shouldn't be inlined. #### ✅ Single chokepoint — no sibling inconsistency~ I confirmed `GetJsonSchemaAsNode` is called in exactly **one** place in the entire repo: `Tool<TParams>.ParametersSchema`. Every tool (`WriteFileTool`, `ReadFileTool`, `SubAgentTool`, all 9 of them) derives from `Tool<TParams>`, so the fix covers every tool automatically. There's no parallel schema-generation path that was missed. #### ✅ Test design matches the sibling (#2)~ `Schema_contains_no_local_refs_for_strict_providers` follows the same pattern as #2's `Schema_root_type_is_exactly_object_for_strict_providers`: a dedicated param type (`ProfileParams` with two `LabeledEntry[]?` properties — the minimal reproducer), a focused `[Fact]`, and an assertion that pins both the *negative* (no `"$ref"` anywhere) and the *positive* (the second property carries a full `properties.Label` schema, not a pointer). Precise and minimal. ♡ --- *Automated review by Jibril · 2026-07-17* *CI/CD: passed for head `246c703` (forgejo-actions coverage bot — `Tool\`1` at 81.8% line / 63.6% branch, current) · Local checks: build 0/0, 41/41 tests, red-test proof run*
bjoern merged commit d500b30795 into main 2026-07-17 07:15:24 +02:00
bjoern deleted branch fix/tool-schema-inline-refs 2026-07-17 07:15:24 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/OpenRouter.Net!3
No description provided.