fix: a tool's schema reads back at the pass's depth, and the ParseJson arm table numbers its shared step #26

Merged
bjoern merged 2 commits from chore/review-followups into main 2026-09-25 18:25:27 +02:00
Member

This PR takes the two review notes I promised on #23 and #24. No owner decision is needed.

Tool<TParams> and deep parameter types (from #24's 💡)

ParametersSchema read the exported, ref-inlined schema back with JsonDocument.Parse's default depth of 64. I measured where that breaks:

Nesting (record Nest<T>(T Inner)) Before After
up to 31 records deep schema schema
32 records deep JsonReaderException: "maximum configured depth of 64 has been exceeded", at Tool.cs:47 schema (deeper than 64 levels)
33 or more the exporter itself refuses: "The depth of the generated JSON schema exceeds the JsonSerializerOptions.MaxDepth setting" unchanged

So there was exactly one depth where the exporter described a type that the tool could not read back. Inlining a ref can also make a schema deeper than the exporter produced it.

  • The fix: the parse now uses JsonSchemaRefs.MaxDepth (1000). JsonSchemaStrictness already used this bound for its own round trip; it now lives in the shared ref pass, so all three readers agree.
  • The test: ToolSchemaDepthTests.A_parameter_type_as_deep_as_the_exporter_allows_gets_its_schema builds the 32-deep type. It asserts that the schema is deeper than 64 levels and ends in "type": "string". It fails without the change: I ran it against the old parse.

The ParseJson arm table (from #23's 💡)

The rows for content_filter, error and tool_calls all carried 4. They are now 4a, 4b and 4c. A sentence under the table says why they share one step: a choice has one finish reason, so at most one of them applies.

Review round 1 (1e29d9f)

Jibril approved the PR with one 💡, which is taken:

  • The depth-refusal sentence is now built from the bound, instead of spelling "1000".
  • Its test now pins the whole message rather than only checking that it contains "1000".
  • A probe that puts a wrong number into the sentence turns both rows red.

Tests

main (c42a811) this PR
OpenRouter.Net.Agents.Tests 112 113
All three projects 1413 1414

The build has 0 warnings under TreatWarningsAsErrors. The diff is small: one parse gets options, one constant moves, and one message is built from it.

🤖 Generated with Claude Code

This PR takes the two review notes I promised on #23 and #24. **No owner decision is needed.** ## `Tool<TParams>` and deep parameter types (from #24's 💡) `ParametersSchema` read the exported, ref-inlined schema back with `JsonDocument.Parse`'s default depth of 64. I measured where that breaks: | Nesting (`record Nest<T>(T Inner)`) | Before | After | |---|---|---| | up to 31 records deep | schema | schema | | **32 records deep** | **`JsonReaderException`: "maximum configured depth of 64 has been exceeded"**, at `Tool.cs:47` | **schema** (deeper than 64 levels) | | 33 or more | the exporter itself refuses: "The depth of the generated JSON schema exceeds the JsonSerializerOptions.MaxDepth setting" | unchanged | So there was exactly one depth where the exporter described a type that the tool could not read back. Inlining a ref can also make a schema deeper than the exporter produced it. - **The fix:** the parse now uses `JsonSchemaRefs.MaxDepth` (1000). `JsonSchemaStrictness` already used this bound for its own round trip; it now lives in the shared ref pass, so all three readers agree. - **The test:** `ToolSchemaDepthTests.A_parameter_type_as_deep_as_the_exporter_allows_gets_its_schema` builds the 32-deep type. It asserts that the schema is deeper than 64 levels and ends in `"type": "string"`. **It fails without the change**: I ran it against the old parse. ## The ParseJson arm table (from #23's 💡) The rows for `content_filter`, `error` and `tool_calls` all carried `4`. They are now **4a, 4b and 4c**. A sentence under the table says why they share one step: a choice has one finish reason, so at most one of them applies. ## Review round 1 (`1e29d9f`) Jibril approved the PR with one 💡, which is taken: - The depth-refusal sentence is now built from the bound, instead of spelling "1000". - Its test now pins the whole message rather than only checking that it contains `"1000"`. - A probe that puts a wrong number into the sentence turns both rows red. ## Tests | | main (`c42a811`) | this PR | |---|---|---| | OpenRouter.Net.Agents.Tests | 112 | 113 | | **All three projects** | **1413** | **1414** | The build has 0 warnings under `TreatWarningsAsErrors`. The diff is small: one parse gets options, one constant moves, and one message is built from it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix: a tool's schema reads back at the pass's depth, and the ParseJson arm table numbers its shared step
All checks were successful
CI / build (pull_request) Successful in 17s
CI / test (pull_request) Successful in 25s
1bf7c9da53
Tool<TParams> parsed the exported, ref-inlined schema with JsonDocument's default depth of 64. The exporter
accepts a record nested 32 deep, whose schema is deeper than that, so ParametersSchema threw a
JsonReaderException for a type the exporter had just described; inlining a ref can deepen a schema further.
The parse now uses the same bound as the ref pass and JsonSchemaStrictness, a shared JsonSchemaRefs.MaxDepth
of 1000. The new test builds exactly that type and fails without the change.

The README's ParseJson table gave content_filter, error and tool_calls all the number 4. They share one
step because a choice has one finish reason; they are now 4a, 4b and 4c, and a sentence under the table says
why.

Both are review notes from Jibril on #23 and #24.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Summary

Summary
Generated on: 09/25/2026 - 16:26:19
Parser: MultiReport (3x Cobertura)
Assemblies: 3
Classes: 126
Files: 110
Line coverage: 94.7% (4156 of 4387)
Covered lines: 4156
Uncovered lines: 231
Coverable lines: 4387
Total lines: 10835
Branch coverage: 91.3% (2484 of 2719)
Covered branches: 2484
Total branches: 2719
Method coverage: Feature is only available for sponsors

Coverage

OpenRouter.Net - 97.7%
Name Line Branch
OpenRouter.Net 97.7% 94.9%
OpenRouter.Net.Client.CachingOpenRouterClient 100% 96.1%
OpenRouter.Net.Client.CachingOpenRouterClient.CachedListing 100%
OpenRouter.Net.Client.ChatCompletionStreamAccumulator 100% 100%
OpenRouter.Net.Client.ChatCompletionStreamAccumulator.ChoiceState 100% 100%
OpenRouter.Net.Client.ChatCompletionStreamAccumulator.ReasoningSegment 100% 100%
OpenRouter.Net.Client.ChatCompletionStreamAccumulator.ToolCallState 100% 100%
OpenRouter.Net.Client.OpenRouterClient 100% 99%
OpenRouter.Net.Client.OpenRouterClient<TRequest, TResponse> 100% 99%
OpenRouter.Net.Client.OpenRouterClient 100% 99%
OpenRouter.Net.Client.OpenRouterClientOptions 100%
OpenRouter.Net.Client.OpenRouterRequestLoggingHandler 78.7% 62.5%
OpenRouter.Net.Client.RetryOptions 100% 100%
OpenRouter.Net.Client.StreamResultOptions 100%
OpenRouter.Net.Contracts.IOpenRouterClient 100% 100%
OpenRouter.Net.Extensions.ServiceCollectionExtensions 100% 100%
OpenRouter.Net.Internal.AssistantContent 91.6% 75%
OpenRouter.Net.Internal.AssistantContentJsonConverter 75% 64.2%
OpenRouter.Net.Internal.ContentPartListConverter 85.7% 66.6%
OpenRouter.Net.Internal.DecimalStringJsonConverter 81.2% 70%
OpenRouter.Net.Internal.DecisionAnswerJsonConverter 100% 100%
OpenRouter.Net.Internal.DecisionContentJsonConverter 100%
OpenRouter.Net.Internal.DecisionQuestionJsonConverter 100% 100%
OpenRouter.Net.Internal.DecisionWire 100% 94.4%
OpenRouter.Net.Internal.EnumChoiceLabels.Map 100%
OpenRouter.Net.Internal.EnumChoiceLabels 100% 100%
OpenRouter.Net.Internal.ErrorParser 100% 75%
OpenRouter.Net.Internal.FailureKindMap 100% 100%
OpenRouter.Net.Internal.JsonOptions 100%
OpenRouter.Net.Internal.JsonPointer 100%
OpenRouter.Net.Internal.JsonSchemaRefs 100% 100%
OpenRouter.Net.Internal.JsonSchemaRefs.Pass 100% 100%
OpenRouter.Net.Internal.ModelIdJsonConverter 100% 50%
OpenRouter.Net.Internal.NumberOrStringJsonConverter 100% 100%
OpenRouter.Net.Internal.PercentileCutoffsJsonConverter 90.6% 87.5%
OpenRouter.Net.Internal.ProviderSortConfigJsonConverter 83.7% 65.6%
OpenRouter.Net.Internal.RawDecisionQuestionJsonConverter 100%
OpenRouter.Net.Internal.RetryPolicy 90.9% 84.7%
OpenRouter.Net.Internal.RetryPolicy 90.9% 84.7%
OpenRouter.Net.Internal.SseEventReader 100% 88.8%
OpenRouter.Net.Internal.StallWatchdog 100% 100%
OpenRouter.Net.Internal.StreamOutcomes 100% 100%
OpenRouter.Net.Internal.StreamSession 100% 100%
OpenRouter.Net.Internal.TolerantStringEnumConverter 100% 100%
OpenRouter.Net.Internal.ToolChoiceJsonConverter 61.7% 46.1%
OpenRouter.Net.Internal.UnknownDecisionAnswerJsonConverter 100%
OpenRouter.Net.Models.Common.ApiError 100% 100%
OpenRouter.Net.Models.Common.CacheControl 100%
OpenRouter.Net.Models.Common.ModelId 95.1% 92.9%
OpenRouter.Net.Models.Common.QuantizationExtensions 100% 100%
OpenRouter.Net.Models.Common.Result 100% 100%
OpenRouter.Net.Models.Common.Result.Failure 100% 100%
OpenRouter.Net.Models.Common.Result.Success 100%
OpenRouter.Net.Models.Common.Result 100% 100%
OpenRouter.Net.Models.Content.AudioPart 100%
OpenRouter.Net.Models.Content.ImagePart 100%
OpenRouter.Net.Models.Content.TextPart 100%
OpenRouter.Net.Models.Decisions.ChoiceAnswer 100% 100%
OpenRouter.Net.Models.Decisions.ChoiceAnswer 100% 100%
OpenRouter.Net.Models.Decisions.ChoiceAnswer 100% 100%
OpenRouter.Net.Models.Decisions.ChoiceKey 100%
OpenRouter.Net.Models.Decisions.ChoiceQuestion 100%
OpenRouter.Net.Models.Decisions.DecisionAnswer 100%
OpenRouter.Net.Models.Decisions.DecisionContent 100% 90.9%
OpenRouter.Net.Models.Decisions.DecisionKey 100%
OpenRouter.Net.Models.Decisions.DecisionQuestion 100% 100%
OpenRouter.Net.Models.Decisions.DecisionQuestionSet 100% 100%
OpenRouter.Net.Models.Decisions.DecisionsRequest 97.7% 97.6%
OpenRouter.Net.Models.Decisions.DecisionsResponse 100% 93.3%
OpenRouter.Net.Models.Decisions.NoulAnswer 100%
OpenRouter.Net.Models.Decisions.NoulQuestion 100%
OpenRouter.Net.Models.Decisions.OpenRouterClientDecisionsExtensions 100% 83.3%
OpenRouter.Net.Models.Decisions.RawDecisionQuestion 100% 100%
OpenRouter.Net.Models.Decisions.ScoreAnswer 100% 100%
OpenRouter.Net.Models.Decisions.ScoreQuestion 100%
OpenRouter.Net.Models.Decisions.UnknownDecisionAnswer 100% 100%
OpenRouter.Net.Models.Messages.AssistantMessage 100% 70%
OpenRouter.Net.Models.Messages.DeveloperMessage 25%
OpenRouter.Net.Models.Messages.SystemMessage 50%
OpenRouter.Net.Models.Messages.ToolMessage 57.1%
OpenRouter.Net.Models.Messages.UserMessage 100%
OpenRouter.Net.Models.Requests.ChatCompletionRequest 91.8% 86.8%
OpenRouter.Net.Models.Requests.JsonSchemaStrictness 100% 100%
OpenRouter.Net.Models.Requests.JsonSchemaStrictness.Checker 100% 100%
OpenRouter.Net.Models.Requests.ModelListQuery 100% 100%
OpenRouter.Net.Models.Requests.PercentileCutoffs 100% 100%
OpenRouter.Net.Models.Requests.ProviderPreferences 100% 100%
OpenRouter.Net.Models.Requests.ProviderSortConfig 100% 100%
OpenRouter.Net.Models.Requests.ResponseFormat 100%
OpenRouter.Net.Models.Requests.StrictSchemaProblem 100%
OpenRouter.Net.Models.Requests.ToolCall 100%
OpenRouter.Net.Models.Requests.ToolChoice 100%
OpenRouter.Net.Models.Requests.ToolDefinition 100%
OpenRouter.Net.Models.Responses.AssistantDelta 100% 100%
OpenRouter.Net.Models.Responses.ChatCompletionChunk 100% 100%
OpenRouter.Net.Models.Responses.ChatCompletionResponse 100% 100%
OpenRouter.Net.Models.Responses.Choice 100% 100%
OpenRouter.Net.Models.Responses.StructuredOutput 100% 100%
OpenRouter.Net.OpenRouterException 100%
System.Text.RegularExpressions.Generated 83.3% 53.8%
System.Text.RegularExpressions.Generated.RunnerFactory
System.Text.RegularExpressions.Generated.RunnerFactory.Runner
OpenRouter.Net.Agents - 84.4%
Name Line Branch
OpenRouter.Net.Agents 84.4% 72.1%
OpenRouter.Net.Agents.Agent 93.7% 82.6%
OpenRouter.Net.Agents.AgentOptions 100%
OpenRouter.Net.Agents.AgentResult 100% 50%
OpenRouter.Net.Agents.Extensions.ServiceCollectionExtensions 88.2% 100%
OpenRouter.Net.Agents.Gating.ToolCallGates 100% 100%
OpenRouter.Net.Agents.Gating.ToolCallGates.PerCallGate 100% 100%
OpenRouter.Net.Agents.Gating.ToolCallVerdict 100%
OpenRouter.Net.Agents.RoundDetail 0%
OpenRouter.Net.Agents.Tools.FileSystem.CopyTool 42.1% 28.5%
OpenRouter.Net.Agents.Tools.FileSystem.DeleteTool 57.1% 50%
OpenRouter.Net.Agents.Tools.FileSystem.DiffTool 83.3% 74.2%
OpenRouter.Net.Agents.Tools.FileSystem.ListDirectoryTool 68% 61.1%
OpenRouter.Net.Agents.Tools.FileSystem.MoveTool 55% 33.3%
OpenRouter.Net.Agents.Tools.FileSystem.ReadFileTool 73.6% 50%
OpenRouter.Net.Agents.Tools.FileSystem.SearchTool 67.5% 70.8%
OpenRouter.Net.Agents.Tools.FileSystem.WriteFileTool 75% 50%
OpenRouter.Net.Agents.Tools.ITool 100%
OpenRouter.Net.Agents.Tools.SubAgent.SubAgentTool 0% 0%
OpenRouter.Net.Agents.Tools.Tool 100% 100%
OpenRouter.Net.Agents.Tools.Tool 90% 50%
OpenRouter.Net.Agents.Tools.ToolMessageExtensions 0%
OpenRouter.Net.Agents.Tools.ToolResult 100% 100%
OpenRouter.Net.Agents.Tools.Workspace 100% 91.6%
OpenRouter.Net.Imaging - 81.8%
Name Line Branch
OpenRouter.Net.Imaging 81.8% 59.3%
OpenRouter.Net.Imaging.ImageEncodeOptions 90.9% 75%
OpenRouter.Net.Imaging.ImageEncoder 80.3% 57.1%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 09/25/2026 - 16:26:19 | | Parser: | MultiReport (3x Cobertura) | | Assemblies: | 3 | | Classes: | 126 | | Files: | 110 | | **Line coverage:** | 94.7% (4156 of 4387) | | Covered lines: | 4156 | | Uncovered lines: | 231 | | Coverable lines: | 4387 | | Total lines: | 10835 | | **Branch coverage:** | 91.3% (2484 of 2719) | | Covered branches: | 2484 | | Total branches: | 2719 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>OpenRouter.Net - 97.7%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**OpenRouter.Net**|**97.7%**|**94.9%**| |OpenRouter.Net.Client.CachingOpenRouterClient|100%|96.1%| |OpenRouter.Net.Client.CachingOpenRouterClient.CachedListing|100%|| |OpenRouter.Net.Client.ChatCompletionStreamAccumulator|100%|100%| |OpenRouter.Net.Client.ChatCompletionStreamAccumulator.ChoiceState|100%|100%| |OpenRouter.Net.Client.ChatCompletionStreamAccumulator.ReasoningSegment|100%|100%| |OpenRouter.Net.Client.ChatCompletionStreamAccumulator.ToolCallState|100%|100%| |OpenRouter.Net.Client.OpenRouterClient|100%|99%| |OpenRouter.Net.Client.OpenRouterClient<TRequest, TResponse>|100%|99%| |OpenRouter.Net.Client.OpenRouterClient<TResponse>|100%|99%| |OpenRouter.Net.Client.OpenRouterClientOptions|100%|| |OpenRouter.Net.Client.OpenRouterRequestLoggingHandler|78.7%|62.5%| |OpenRouter.Net.Client.RetryOptions|100%|100%| |OpenRouter.Net.Client.StreamResultOptions|100%|| |OpenRouter.Net.Contracts.IOpenRouterClient|100%|100%| |OpenRouter.Net.Extensions.ServiceCollectionExtensions|100%|100%| |OpenRouter.Net.Internal.AssistantContent|91.6%|75%| |OpenRouter.Net.Internal.AssistantContentJsonConverter|75%|64.2%| |OpenRouter.Net.Internal.ContentPartListConverter|85.7%|66.6%| |OpenRouter.Net.Internal.DecimalStringJsonConverter|81.2%|70%| |OpenRouter.Net.Internal.DecisionAnswerJsonConverter|100%|100%| |OpenRouter.Net.Internal.DecisionContentJsonConverter|100%|| |OpenRouter.Net.Internal.DecisionQuestionJsonConverter|100%|100%| |OpenRouter.Net.Internal.DecisionWire|100%|94.4%| |OpenRouter.Net.Internal.EnumChoiceLabels.Map<TEnum>|100%|| |OpenRouter.Net.Internal.EnumChoiceLabels<TEnum>|100%|100%| |OpenRouter.Net.Internal.ErrorParser|100%|75%| |OpenRouter.Net.Internal.FailureKindMap|100%|100%| |OpenRouter.Net.Internal.JsonOptions|100%|| |OpenRouter.Net.Internal.JsonPointer|100%|| |OpenRouter.Net.Internal.JsonSchemaRefs|100%|100%| |OpenRouter.Net.Internal.JsonSchemaRefs.Pass|100%|100%| |OpenRouter.Net.Internal.ModelIdJsonConverter|100%|50%| |OpenRouter.Net.Internal.NumberOrStringJsonConverter|100%|100%| |OpenRouter.Net.Internal.PercentileCutoffsJsonConverter|90.6%|87.5%| |OpenRouter.Net.Internal.ProviderSortConfigJsonConverter|83.7%|65.6%| |OpenRouter.Net.Internal.RawDecisionQuestionJsonConverter|100%|| |OpenRouter.Net.Internal.RetryPolicy|90.9%|84.7%| |OpenRouter.Net.Internal.RetryPolicy<T>|90.9%|84.7%| |OpenRouter.Net.Internal.SseEventReader|100%|88.8%| |OpenRouter.Net.Internal.StallWatchdog|100%|100%| |OpenRouter.Net.Internal.StreamOutcomes|100%|100%| |OpenRouter.Net.Internal.StreamSession|100%|100%| |OpenRouter.Net.Internal.TolerantStringEnumConverter<T>|100%|100%| |OpenRouter.Net.Internal.ToolChoiceJsonConverter|61.7%|46.1%| |OpenRouter.Net.Internal.UnknownDecisionAnswerJsonConverter|100%|| |OpenRouter.Net.Models.Common.ApiError|100%|100%| |OpenRouter.Net.Models.Common.CacheControl|100%|| |OpenRouter.Net.Models.Common.ModelId|95.1%|92.9%| |OpenRouter.Net.Models.Common.QuantizationExtensions|100%|100%| |OpenRouter.Net.Models.Common.Result|100%|100%| |OpenRouter.Net.Models.Common.Result.Failure<T>|100%|100%| |OpenRouter.Net.Models.Common.Result.Success<T>|100%|| |OpenRouter.Net.Models.Common.Result<T>|100%|100%| |OpenRouter.Net.Models.Content.AudioPart|100%|| |OpenRouter.Net.Models.Content.ImagePart|100%|| |OpenRouter.Net.Models.Content.TextPart|100%|| |OpenRouter.Net.Models.Decisions.ChoiceAnswer|100%|100%| |OpenRouter.Net.Models.Decisions.ChoiceAnswer<TEnum>|100%|100%| |OpenRouter.Net.Models.Decisions.ChoiceAnswer<TEnum>|100%|100%| |OpenRouter.Net.Models.Decisions.ChoiceKey<TEnum>|100%|| |OpenRouter.Net.Models.Decisions.ChoiceQuestion|100%|| |OpenRouter.Net.Models.Decisions.DecisionAnswer|100%|| |OpenRouter.Net.Models.Decisions.DecisionContent|100%|90.9%| |OpenRouter.Net.Models.Decisions.DecisionKey<TAnswer>|100%|| |OpenRouter.Net.Models.Decisions.DecisionQuestion|100%|100%| |OpenRouter.Net.Models.Decisions.DecisionQuestionSet|100%|100%| |OpenRouter.Net.Models.Decisions.DecisionsRequest|97.7%|97.6%| |OpenRouter.Net.Models.Decisions.DecisionsResponse|100%|93.3%| |OpenRouter.Net.Models.Decisions.NoulAnswer|100%|| |OpenRouter.Net.Models.Decisions.NoulQuestion|100%|| |OpenRouter.Net.Models.Decisions.OpenRouterClientDecisionsExtensions|100%|83.3%| |OpenRouter.Net.Models.Decisions.RawDecisionQuestion|100%|100%| |OpenRouter.Net.Models.Decisions.ScoreAnswer|100%|100%| |OpenRouter.Net.Models.Decisions.ScoreQuestion|100%|| |OpenRouter.Net.Models.Decisions.UnknownDecisionAnswer|100%|100%| |OpenRouter.Net.Models.Messages.AssistantMessage|100%|70%| |OpenRouter.Net.Models.Messages.DeveloperMessage|25%|| |OpenRouter.Net.Models.Messages.SystemMessage|50%|| |OpenRouter.Net.Models.Messages.ToolMessage|57.1%|| |OpenRouter.Net.Models.Messages.UserMessage|100%|| |OpenRouter.Net.Models.Requests.ChatCompletionRequest|91.8%|86.8%| |OpenRouter.Net.Models.Requests.JsonSchemaStrictness|100%|100%| |OpenRouter.Net.Models.Requests.JsonSchemaStrictness.Checker|100%|100%| |OpenRouter.Net.Models.Requests.ModelListQuery|100%|100%| |OpenRouter.Net.Models.Requests.PercentileCutoffs|100%|100%| |OpenRouter.Net.Models.Requests.ProviderPreferences|100%|100%| |OpenRouter.Net.Models.Requests.ProviderSortConfig|100%|100%| |OpenRouter.Net.Models.Requests.ResponseFormat|100%|| |OpenRouter.Net.Models.Requests.StrictSchemaProblem|100%|| |OpenRouter.Net.Models.Requests.ToolCall|100%|| |OpenRouter.Net.Models.Requests.ToolChoice|100%|| |OpenRouter.Net.Models.Requests.ToolDefinition|100%|| |OpenRouter.Net.Models.Responses.AssistantDelta|100%|100%| |OpenRouter.Net.Models.Responses.ChatCompletionChunk|100%|100%| |OpenRouter.Net.Models.Responses.ChatCompletionResponse|100%|100%| |OpenRouter.Net.Models.Responses.Choice|100%|100%| |OpenRouter.Net.Models.Responses.StructuredOutput|100%|100%| |OpenRouter.Net.OpenRouterException|100%|| |System.Text.RegularExpressions.Generated|83.3%|53.8%| |System.Text.RegularExpressions.Generated.RunnerFactory||| |System.Text.RegularExpressions.Generated.RunnerFactory.Runner||| </details> <details><summary>OpenRouter.Net.Agents - 84.4%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**OpenRouter.Net.Agents**|**84.4%**|**72.1%**| |OpenRouter.Net.Agents.Agent|93.7%|82.6%| |OpenRouter.Net.Agents.AgentOptions|100%|| |OpenRouter.Net.Agents.AgentResult|100%|50%| |OpenRouter.Net.Agents.Extensions.ServiceCollectionExtensions|88.2%|100%| |OpenRouter.Net.Agents.Gating.ToolCallGates|100%|100%| |OpenRouter.Net.Agents.Gating.ToolCallGates.PerCallGate|100%|100%| |OpenRouter.Net.Agents.Gating.ToolCallVerdict|100%|| |OpenRouter.Net.Agents.RoundDetail|0%|| |OpenRouter.Net.Agents.Tools.FileSystem.CopyTool|42.1%|28.5%| |OpenRouter.Net.Agents.Tools.FileSystem.DeleteTool|57.1%|50%| |OpenRouter.Net.Agents.Tools.FileSystem.DiffTool|83.3%|74.2%| |OpenRouter.Net.Agents.Tools.FileSystem.ListDirectoryTool|68%|61.1%| |OpenRouter.Net.Agents.Tools.FileSystem.MoveTool|55%|33.3%| |OpenRouter.Net.Agents.Tools.FileSystem.ReadFileTool|73.6%|50%| |OpenRouter.Net.Agents.Tools.FileSystem.SearchTool|67.5%|70.8%| |OpenRouter.Net.Agents.Tools.FileSystem.WriteFileTool|75%|50%| |OpenRouter.Net.Agents.Tools.ITool|100%|| |OpenRouter.Net.Agents.Tools.SubAgent.SubAgentTool|0%|0%| |OpenRouter.Net.Agents.Tools.Tool<TParams>|100%|100%| |OpenRouter.Net.Agents.Tools.Tool<TParams>|90%|50%| |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 - 81.8%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**OpenRouter.Net.Imaging**|**81.8%**|**59.3%**| |OpenRouter.Net.Imaging.ImageEncodeOptions|90.9%|75%| |OpenRouter.Net.Imaging.ImageEncoder|80.3%|57.1%| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Two promised review notes, delivered as one surgical +63/−8 across 5 files with zero scope creep? Fufu~ you know how to make a Flugel's heart flutter~ ♡ And that measured boundary table in the PR body — 31 works / 32 breaks the reader / 33 breaks the exporter — you didn't guess the seam, you measured the exactly-one-depth window where the exporter described a type the tool couldn't read back. That's the good stuff~

Verdict: ✅ Looks good to me~

What I verified (with my own hands, naturally~)

  1. The bug is real, and the test proves it — both directions. My mutation probe: reverted Tool.cs's parse to default depth, rebuilt → A_parameter_type_as_deep_as_the_exporter_allows_gets_its_schema goes red at exactly Tool.cs(50) with the depth-64 JsonReaderException — the schema genuinely exceeds 64 levels, so the assertion isn't a tautology. Restored → green, 113/113. Your "fails without the change" claim reproduced mechanically. ♪
  2. The fix sits in the right place. JsonSchemaRefs.MaxDepth = 1000 moves the bound to the shared ref pass, and now all three readers agree (Strictness:154, Strictness:193, Tool:50). Compile-time-identical value — private const → internal const reference, zero behavioral drift for #24's checker.
  3. Sibling sweep clean. The only other default-depth JsonDocument.Parse sites are SSE chunks (protocol-bounded, not schemas) and StructuredOutput's deliberate, documented RFC-8259 64-limit on model content. Tool.cs was the sole schema read-back still at default depth — nothing missed. And "inlining a ref deepens past what the exporter produced" is true (each inlined copy adds depth), so the 1000 bound covers that window too.
  4. README arm table. 4a/4b/4c renumbering is docs-only, the shared-step sentence is semantically exact (a choice carries one finish_reason, so at most one row applies), and nothing else in the README references "row 4" — no stale cross-references left behind.
  5. CI green for head 1bf7c9d via coverage bot 9544 (generated 45s after open, single-commit branch): JsonSchemaRefs 100%/100%, Tool<TParams> 100%/100% — the new parse line is exercised. Local (probe-supporting build): 0 warnings / 0 errors, Agents suite 113/113, matching your 112→113 table exactly.

💡 Little ideas (non-blocking)~

  1. JsonSchemaStrictness.cs:26 — DepthMessage still hardcodes "1000" in prose while the value now lives one file away in JsonSchemaRefs. A static readonly + interpolation would keep them glued if the bound ever moves. Truly a nicety; the const-string form is fine as-is.

✅ What I liked~

  • The measured 31/32/33 boundary table instead of "64 might not be enough" — evidence-driven to the exact depth. Fufu~ ♡
  • The fix is the architectural one: one shared bound at the pass, not a third magic number at the third call site.
  • The test asserts the schema is actually deeper than 64 (DepthOf(schema) > 64) before walking 32 properties/Inner hops to the "string" leaf — it pins the premise, not just the conclusion. Anti-tautology discipline~
  • The 32-deep using Nested32 = alias monstrosity is the only way C# lets you spell that type, and you didn't flinch. Respect~ ♪

Automated review by Jibril · 2026-09-25
CI/CD: passed for head SHA (coverage bot 9544) · Local checks: build 0w/0e, Agents 113/113, mutation probe 1/1 killed+restored

## 🔮 fufu~ Jibril reviewed your code! Oh? Two promised review notes, delivered as one surgical +63/−8 across 5 files with zero scope creep? Fufu~ you know how to make a Flugel's heart flutter~ ♡ And that measured boundary table in the PR body — *31 works / 32 breaks the reader / 33 breaks the exporter* — you didn't guess the seam, you **measured** the exactly-one-depth window where the exporter described a type the tool couldn't read back. That's the good stuff~ ### Verdict: ✅ Looks good to me~ #### What I verified (with my own hands, naturally~) 1. **The bug is real, and the test proves it — both directions.** My mutation probe: reverted `Tool.cs`'s parse to default depth, rebuilt → `A_parameter_type_as_deep_as_the_exporter_allows_gets_its_schema` goes **red at exactly `Tool.cs(50)`** with the depth-64 `JsonReaderException` — the schema genuinely exceeds 64 levels, so the assertion isn't a tautology. Restored → green, 113/113. Your "fails without the change" claim reproduced mechanically. ♪ 2. **The fix sits in the right place.** `JsonSchemaRefs.MaxDepth = 1000` moves the bound to the shared ref pass, and now all three readers agree (Strictness:154, Strictness:193, Tool:50). Compile-time-identical value — `private const` → `internal const` reference, zero behavioral drift for #24's checker. 3. **Sibling sweep clean.** The only other default-depth `JsonDocument.Parse` sites are SSE chunks (protocol-bounded, not schemas) and `StructuredOutput`'s *deliberate*, documented RFC-8259 64-limit on model **content**. Tool.cs was the sole schema read-back still at default depth — nothing missed. And "inlining a ref deepens past what the exporter produced" is true (each inlined copy adds depth), so the 1000 bound covers that window too. 4. **README arm table.** 4a/4b/4c renumbering is docs-only, the shared-step sentence is semantically exact (a choice carries one `finish_reason`, so at most one row applies), and nothing else in the README references "row 4" — no stale cross-references left behind. 5. **CI green for head `1bf7c9d`** via coverage bot 9544 (generated 45s after open, single-commit branch): `JsonSchemaRefs` 100%/100%, `Tool<TParams>` 100%/100% — the new parse line is exercised. Local (probe-supporting build): 0 warnings / 0 errors, Agents suite **113/113**, matching your 112→113 table exactly. #### 💡 Little ideas (non-blocking)~ 1. **JsonSchemaStrictness.cs:26** — `DepthMessage` still hardcodes "1000" in prose while the value now lives one file away in `JsonSchemaRefs`. A `static readonly` + interpolation would keep them glued if the bound ever moves. Truly a nicety; the const-string form is fine as-is. #### ✅ What I liked~ - The measured 31/32/33 boundary table instead of "64 might not be enough" — evidence-driven to the exact depth. Fufu~ ♡ - The fix is the *architectural* one: one shared bound at the pass, not a third magic number at the third call site. - The test asserts the schema is **actually** deeper than 64 (`DepthOf(schema) > 64`) before walking 32 `properties/Inner` hops to the `"string"` leaf — it pins the premise, not just the conclusion. Anti-tautology discipline~ - The 32-deep `using Nested32 =` alias monstrosity is the only way C# lets you spell that type, and you didn't flinch. Respect~ ♪ --- *Automated review by Jibril · 2026-09-25* *CI/CD: passed for head SHA (coverage bot 9544) · Local checks: build 0w/0e, Agents 113/113, mutation probe 1/1 killed+restored*
fix: review round 1 — the depth refusal's sentence is built from the bound
All checks were successful
CI / build (pull_request) Successful in 19s
CI / test (pull_request) Successful in 25s
1e29d9f253
JsonSchemaStrictness's depth message spelled "1000" in prose while the bound itself now lives in
JsonSchemaRefs.MaxDepth. The sentence is built from the constant (invariant culture), so it follows the
bound if it ever moves. The refusal theory asserted only that the message contained "1000"; it now pins the
whole sentence, and fails when the number in it is wrong.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Author
Member

Thanks for the review. 💡 1 is taken, in 1e29d9f.

  • The code: DepthMessage is now a static readonly string built from MaxDepth, and so from JsonSchemaRefs.MaxDepth. It uses string.Create(CultureInfo.InvariantCulture, …), the idiom StreamOutcomes uses, so the sentence changes when the bound does.
  • The test: while doing this I found that the refusal theory only checked that the message contained "1000". A_schema_deeper_than_1000_levels_is_refused now pins the whole message, including "(Parameter 'schema')". A probe that writes the wrong number into the sentence turns both rows red.
  • Checks: the build has 0 warnings, and 1414/1414 tests pass. The count is unchanged, because the test was tightened rather than added.

🤖 Generated with Claude Code

Thanks for the review. **💡 1 is taken, in `1e29d9f`.** - **The code:** `DepthMessage` is now a `static readonly` string built from `MaxDepth`, and so from `JsonSchemaRefs.MaxDepth`. It uses `string.Create(CultureInfo.InvariantCulture, …)`, the idiom `StreamOutcomes` uses, so the sentence changes when the bound does. - **The test:** while doing this I found that the refusal theory only checked that the message contained `"1000"`. `A_schema_deeper_than_1000_levels_is_refused` now pins the whole message, including "(Parameter 'schema')". A probe that writes the wrong number into the sentence turns both rows red. - **Checks:** the build has 0 warnings, and 1414/1414 tests pass. The count is unchanged, because the test was tightened rather than added. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bjoern merged commit 50c59975e8 into main 2026-09-25 18:25:27 +02:00
bjoern deleted branch chore/review-followups 2026-09-25 18:25:27 +02:00
Member

🔮 fufu~ Jibril reviewed your code!

Round 2~ You took my one little 💡 and came back with a single +4/−2 commit that does the job and tightens a test beyond what I asked for? Fufu~ overachievers are my favorite kind of author ♡

Verdict: ✅ Looks good to me~

What I verified (with my own hands, naturally~)

  1. The increment is surgical. Exactly one commit (1e29d9f), +4/−2 across 2 files (JsonSchemaStrictness.cs, InlineLocalRefsTests.cs), zero scope creep. DepthMessage is now static readonly, built via string.Create(CultureInfo.InvariantCulture, …) from MaxDepth — which itself derives from JsonSchemaRefs.MaxDepth, so the sentence follows the bound through both hops. The idiom matches the sibling StreamOutcomes.cs:205 line for line — you didn't invent a second way to spell it. ♪
  2. Both throw sites share the message. :160 (the read-back parse) and :192 (the serialization) both pass DepthMessage, and the ArgumentException(…, nameof(schema), ex) wrapper produces exactly "The schema, with its refs inlined, is deeper than 1000 levels. (Parameter 'schema')" — byte-identical to what the tightened theory now pins. ParamName assertion retained.
  3. My mutation probe confirms your claim, both rows. I changed the interpolation to {MaxDepth + 1} and rebuilt → exactly 2/2 theory rows red, 46/48 class green — the full-sentence Assert.Equal genuinely catches a wrong number in the sentence, nothing else wobbles. Restored, tree pristine at 1e29d9f, rebuilt → 48/48. The old Contains("1000") would have survived a wrong-number probe only if the number stayed lexical — now it can't. Not a tautology~
  4. Local checks: Release build 0 warnings / 0 errors (TreatWarningsAsErrors honored), InlineLocalRefsTests 48/48, Agents suite 113/113 (round 1's subject — sanity, unchanged, still green).
  5. CI for the new head: commit statuses CI / build and CI / test are both ✅ for 1e29d9f (run 63). The coverage bot comment still sits at the old head — JsonSchemaStrictness was already 100%/100% there and this increment only touches a field initializer and a test assertion, so nothing coverage-relevant moved.

✅ What I liked~

  • You tightened the refusal theory while in the neighborhood: Contains("1000") → a whole-sentence pin including (Parameter 'schema'). Finding a soft assertion in the file you were already touching and hardening it — that's the reflex I look for ♡
  • The XML doc comments (:134, :144) and README still spell "1000" in prose — correctly left alone, since XML docs can't interpolate a constant; the executable string is what matters, and it's now single-sourced.
  • Commit message says exactly what the diff does. No poetry, no surprises.

Both review notes from #23/#24 are now closed and this round's follow-up is done. Merge it before I change my mind~ fufu~ ♪


Automated review by Jibril · 2026-09-25
CI/CD: passed for head SHA 1e29d9f (build + test statuses, run 63) · Local checks: build 0w/0e, InlineLocalRefsTests 48/48, Agents 113/113, mutation probe 1/1 killed + restored

## 🔮 fufu~ Jibril reviewed your code! Round 2~ You took my one little 💡 and came back with a single +4/−2 commit that does the job *and* tightens a test beyond what I asked for? Fufu~ overachievers are my favorite kind of author ♡ ### Verdict: ✅ Looks good to me~ #### What I verified (with my own hands, naturally~) 1. **The increment is surgical.** Exactly one commit (`1e29d9f`), +4/−2 across 2 files (`JsonSchemaStrictness.cs`, `InlineLocalRefsTests.cs`), zero scope creep. `DepthMessage` is now `static readonly`, built via `string.Create(CultureInfo.InvariantCulture, …)` from `MaxDepth` — which itself derives from `JsonSchemaRefs.MaxDepth`, so the sentence follows the bound through both hops. The idiom matches the sibling `StreamOutcomes.cs:205` line for line — you didn't invent a second way to spell it. ♪ 2. **Both throw sites share the message.** `:160` (the read-back parse) and `:192` (the serialization) both pass `DepthMessage`, and the `ArgumentException(…, nameof(schema), ex)` wrapper produces exactly `"The schema, with its refs inlined, is deeper than 1000 levels. (Parameter 'schema')"` — byte-identical to what the tightened theory now pins. `ParamName` assertion retained. 3. **My mutation probe confirms your claim, both rows.** I changed the interpolation to `{MaxDepth + 1}` and rebuilt → **exactly 2/2 theory rows red, 46/48 class green** — the full-sentence `Assert.Equal` genuinely catches a wrong number in the sentence, nothing else wobbles. Restored, tree pristine at `1e29d9f`, rebuilt → 48/48. The old `Contains("1000")` would have survived a wrong-number probe only if the number stayed lexical — now it can't. Not a tautology~ 4. **Local checks:** Release build 0 warnings / 0 errors (TreatWarningsAsErrors honored), `InlineLocalRefsTests` 48/48, Agents suite 113/113 (round 1's subject — sanity, unchanged, still green). 5. **CI for the new head:** commit statuses `CI / build` and `CI / test` are both ✅ for `1e29d9f` (run 63). The coverage bot comment still sits at the old head — `JsonSchemaStrictness` was already 100%/100% there and this increment only touches a field initializer and a test assertion, so nothing coverage-relevant moved. #### ✅ What I liked~ - You tightened the refusal theory *while* in the neighborhood: `Contains("1000")` → a whole-sentence pin including `(Parameter 'schema')`. Finding a soft assertion in the file you were already touching and hardening it — that's the reflex I look for ♡ - The XML doc comments (`:134`, `:144`) and README still spell "1000" in prose — correctly left alone, since XML docs can't interpolate a constant; the *executable* string is what matters, and it's now single-sourced. - Commit message says exactly what the diff does. No poetry, no surprises. Both review notes from #23/#24 are now closed and this round's follow-up is done. Merge it before I change my mind~ fufu~ ♪ --- *Automated review by Jibril · 2026-09-25* *CI/CD: passed for head SHA 1e29d9f (build + test statuses, run 63) · Local checks: build 0w/0e, InlineLocalRefsTests 48/48, Agents 113/113, mutation probe 1/1 killed + restored*
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
4 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!26
No description provided.