feat: tools and the gate know who the run is for #35

Merged
bjoern merged 1 commit from feat/caller-context into main 2026-10-23 18:36:38 +02:00
Member

This is the fourth step of the library track Shikigami's runtime needs: "Caller context" in its PROJECT_PLAN (Shikigami ADR 0021; ADR 0022 needs the gate to decide by the run's profile). It adds an opaque caller state that every tool call and the tool-call gate read, so a tool knows the familiar, chat and run it works for without being built around them. It is opt-in: with no state set, nothing changes for Orihon, Kagura or Tsumugi.

What's in

AgentOptions.CallerState (object?)

  • The agent hands it unchanged to every call (ToolInvocationContext.CallerState) and to the gate (ToolTurnReview.CallerState). It never looks inside.
  • It is read once per tool turn, before the turn is announced. The gate and every call it allows see one object, even if a tool or an event handler changes the options mid-turn, so a call can never run under a state the gate didn't review. A change applies from the next turn.
  • Clone() copies the reference. A CreateSubAgent sub-agent shares the object unless its configure callback sets another. It is in the options registry (AgentOwnOptions, and the every-option fixture).

Reading it back

  • GetCallerState<T>() on both records returns the state as its own type, or as a type it derives from or implements.
  • It throws InvalidOperationException when there is no state ("set AgentOptions.CallerState to the Run its tools and gate read") or when the state is another type ("is a Run, not a String").

SubAgentTool — this was open in the track's notes, and I chose this; easy to reverse if you'd rather.

  • A delegated sub-agent works for the run that called it, so it gets the calling run's state. Its own options' state is used only when that run has none.
  • Why: the tool's base options are shared by every run it serves, so a per-run state can only come from the call.

The critical-section contract

  • ITool.InvokeAsync's doc now says what the loop already did: a tool that returns after the run's token is cancelled (finishing a write it must not leave half done) has its answer recorded, the run ends as cancelled, and the calls after it are answered as not run.
  • For a blind model's images the doc is narrower: the describer gets the cancelled token, so a describer that honours it leaves them undescribed.

One behaviour fix the review found

  • A cancel that landed in the last allowed round, after its last call returned, used to end the run as MaxIterationsReached, and OnMaxRoundsReached was offered more rounds for a cancelled run.
  • The loop now checks the token before the cap hook, so such a run ends as cancelled in both modes: ChatAsync throws, or it returns Cancelled with ReturnResult.
  • This is a row in "Upgrading from 0.3", since a 0.3 host could see that run end differently.

Docs

  • README "Who the run is for", a subsection of Authoring custom tools, covers:
    • the example;
    • read once per turn;
    • one options object per concurrent run, because the agent keeps the instance it was given;
    • the DI route: CreateSubAgent(o => o.CallerState = run, sp.GetServices<ITool>()), since a sub-agent starts with no tools.
  • Bullets in Gating, Sub-agents and the cancel section; the cap hook section; Highlights; the Upgrading intro and row; the ToolInvocationContext paragraph and Tool<TParams>.ExecuteAsync's param doc.

Not in this step

  • The steering pull hook (L4) is the next PR. After it come cache controls (L3a, L3b) and hardening (L5a, L5b).
  • AgentSnapshot (the cap hook's view) carries no caller state. No consumer needs it, and the hook closes over its own run.

Verification

  • The whole solution passes 1660/1660, in local time and under TZ=UTC: core 1415, Agents 238 (+14), Imaging 7. The build has 0 warnings.

  • CallerStateTests (8 tests, one a theory of 4 rows):

    • every call and the gate see the caller's own object, checked across two turns and three calls (Assert.Same, counts pinned first);
    • a state changed between two chats reaches the second chat;
    • a state changed by a tool during a turn leaves the rest of that turn as the gate reviewed it, and reaches the next turn;
    • with no state, both see null and reading one gives the exact message;
    • a state reads back as itself, a base type or an interface, and not as another type;
    • CreateSubAgent shares the state unless configure replaces it;
    • a DI agent gets its run's state through a sub-agent given the registered tools;
    • SubAgentTool across all four combinations: calling run with or without a state, the tool's options with or without one. The base options are left untouched.
  • Cancellation:

    • Throw mode: a tool that finishes despite the cancel keeps its answer, and the next call is answered as not run.
    • Throw mode, last allowed round: the run throws the caller's token, and the cap hook is never asked.
    • ReturnResult mode, the same case: Cancelled, the closing event says so, the hook isn't asked, and the round sits in Rounds with no cut-off round or partial.
  • Mutation probes: 13 sites, 13 killed, 0 survived. The sites:

    • each context losing the state;
    • the call reading the options anew instead of the turn's value;
    • the cap check removed;
    • Clone dropping the state;
    • SubAgentTool ignoring the run, preferring its own options, or dropping the fallback;
    • both GetCallerStates cast instead of checking;
    • an exact-type check;
    • both messages.

    They ran before the second pass. Its fixes were docs and one test, and add no production logic.

  • Review (read-only workflow; every count is from its saved JSON):

    • First pass: 12 findings — 9 confirmed, 2 plausible, 1 refuted.
      • Taken: the per-call read the gate never reviewed; the cancel at the cap; the blind-mode image wording; one options object per run and the DI route; a missing mid-run test; the stale ToolInvocationContext paragraph and param doc; the Upgrading list; the test summary; the Highlights wrap.
      • Refuted: "a DI agent cannot get a per-run state". CreateSubAgent gives it one; its doc point is covered.
    • Second pass over the fixes: 2 findings, both confirmed and taken. The DI recipe gave a tool-less sub-agent; it now hands over the registered tools, and a test pins it. The cap change needed its Upgrading row.

Notes

  • Merge order: none. Shikigami pins the merged commit when its runtime slice (2c) bumps the pointer.
  • Other consumers are unaffected. The state is opt-in, and the records gain optional init members. The cap change applies only to a run whose token was already cancelled.

🤖 Generated with Claude Code

This is the fourth step of the library track Shikigami's runtime needs: "Caller context" in its PROJECT_PLAN (Shikigami ADR 0021; ADR 0022 needs the gate to decide by the run's profile). It adds an **opaque caller state that every tool call and the tool-call gate read**, so a tool knows the familiar, chat and run it works for without being built around them. It is opt-in: with no state set, nothing changes for Orihon, Kagura or Tsumugi. ## What's in **`AgentOptions.CallerState`** (`object?`) - The agent hands it unchanged to every call (`ToolInvocationContext.CallerState`) and to the gate (`ToolTurnReview.CallerState`). It never looks inside. - It is **read once per tool turn**, before the turn is announced. The gate and every call it allows see one object, even if a tool or an event handler changes the options mid-turn, so a call can never run under a state the gate didn't review. A change applies from the next turn. - `Clone()` copies the reference. A `CreateSubAgent` sub-agent shares the object unless its configure callback sets another. It is in the options registry (`AgentOwnOptions`, and the every-option fixture). **Reading it back** - `GetCallerState<T>()` on both records returns the state as its own type, or as a type it derives from or implements. - It throws `InvalidOperationException` when there is no state ("set AgentOptions.CallerState to the Run its tools and gate read") or when the state is another type ("is a Run, not a String"). **`SubAgentTool`** — this was open in the track's notes, and I chose this; easy to reverse if you'd rather. - A delegated sub-agent works for the run that called it, so it gets **the calling run's state**. Its own options' state is used only when that run has none. - Why: the tool's base options are shared by every run it serves, so a per-run state can only come from the call. **The critical-section contract** - `ITool.InvokeAsync`'s doc now says what the loop already did: a tool that returns after the run's token is cancelled (finishing a write it must not leave half done) has its answer recorded, the run ends as cancelled, and the calls after it are answered as not run. - For a blind model's images the doc is narrower: the describer gets the cancelled token, so a describer that honours it leaves them undescribed. **One behaviour fix the review found** - A cancel that landed in the last allowed round, after its last call returned, used to end the run as `MaxIterationsReached`, and `OnMaxRoundsReached` was offered more rounds for a cancelled run. - The loop now checks the token before the cap hook, so such a run ends as cancelled in both modes: `ChatAsync` throws, or it returns `Cancelled` with `ReturnResult`. - This is a row in "Upgrading from 0.3", since a 0.3 host could see that run end differently. **Docs** - README "Who the run is for", a subsection of Authoring custom tools, covers: - the example; - read once per turn; - one options object per concurrent run, because the agent keeps the instance it was given; - the DI route: `CreateSubAgent(o => o.CallerState = run, sp.GetServices<ITool>())`, since a sub-agent starts with no tools. - Bullets in Gating, Sub-agents and the cancel section; the cap hook section; Highlights; the Upgrading intro and row; the `ToolInvocationContext` paragraph and `Tool<TParams>.ExecuteAsync`'s param doc. ## Not in this step - **The steering pull hook (L4)** is the next PR. After it come cache controls (L3a, L3b) and hardening (L5a, L5b). - **`AgentSnapshot` (the cap hook's view) carries no caller state.** No consumer needs it, and the hook closes over its own run. ## Verification - The whole solution passes **1660/1660**, in local time and under `TZ=UTC`: core 1415, Agents 238 (+14), Imaging 7. The build has 0 warnings. - **`CallerStateTests`** (8 tests, one a theory of 4 rows): - every call and the gate see the caller's own object, checked across two turns and three calls (`Assert.Same`, counts pinned first); - a state changed between two chats reaches the second chat; - a state changed by a tool during a turn leaves the rest of that turn as the gate reviewed it, and reaches the next turn; - with no state, both see `null` and reading one gives the exact message; - a state reads back as itself, a base type or an interface, and not as another type; - `CreateSubAgent` shares the state unless configure replaces it; - a DI agent gets its run's state through a sub-agent given the registered tools; - `SubAgentTool` across all four combinations: calling run with or without a state, the tool's options with or without one. The base options are left untouched. - **Cancellation:** - Throw mode: a tool that finishes despite the cancel keeps its answer, and the next call is answered as not run. - Throw mode, last allowed round: the run throws the caller's token, and the cap hook is never asked. - `ReturnResult` mode, the same case: `Cancelled`, the closing event says so, the hook isn't asked, and the round sits in `Rounds` with no cut-off round or partial. - **Mutation probes:** 13 sites, **13 killed**, 0 survived. The sites: - each context losing the state; - the call reading the options anew instead of the turn's value; - the cap check removed; - `Clone` dropping the state; - `SubAgentTool` ignoring the run, preferring its own options, or dropping the fallback; - both `GetCallerState`s cast instead of checking; - an exact-type check; - both messages. They ran before the second pass. Its fixes were docs and one test, and add no production logic. - **Review** (read-only workflow; every count is from its saved JSON): - **First pass: 12 findings** — 9 confirmed, 2 plausible, 1 refuted. - Taken: the per-call read the gate never reviewed; the cancel at the cap; the blind-mode image wording; one options object per run and the DI route; a missing mid-run test; the stale `ToolInvocationContext` paragraph and param doc; the Upgrading list; the test summary; the Highlights wrap. - Refuted: "a DI agent cannot get a per-run state". `CreateSubAgent` gives it one; its doc point is covered. - **Second pass** over the fixes: **2 findings, both confirmed and taken.** The DI recipe gave a tool-less sub-agent; it now hands over the registered tools, and a test pins it. The cap change needed its Upgrading row. ## Notes - **Merge order:** none. Shikigami pins the merged commit when its runtime slice (2c) bumps the pointer. - **Other consumers are unaffected.** The state is opt-in, and the records gain optional init members. The cap change applies only to a run whose token was already cancelled. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: tools and the gate know who the run is for
All checks were successful
CI / build (pull_request) Successful in 18s
CI / test (pull_request) Successful in 27s
95318deef1
AgentOptions.CallerState reaches every tool call and the tool-call gate,
read once per turn; a delegated sub-agent works with the calling run's
state. A cancel in the last allowed round now ends the run as cancelled
instead of out of rounds.

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

Summary

Summary
Generated on: 10/23/2026 - 16:19:02
Parser: MultiReport (3x Cobertura)
Assemblies: 3
Classes: 136
Files: 116
Line coverage: 96.3% (4698 of 4877)
Covered lines: 4698
Uncovered lines: 179
Coverable lines: 4877
Total lines: 12097
Branch coverage: 93.6% (3057 of 3265)
Covered branches: 3057
Total branches: 3265
Method coverage: Feature is only available for sponsors

Coverage

OpenRouter.Net - 98.1%
Name Line Branch
OpenRouter.Net 98.1% 95.8%
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.ProviderFieldFold 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 85.7% 87.5%
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 93.3% 93.1%
OpenRouter.Net.Internal.ProviderSortConfigJsonConverter 94% 95.8%
OpenRouter.Net.Internal.RawDecisionQuestionJsonConverter 100%
OpenRouter.Net.Internal.RetryPolicy 90.9% 84.7%
OpenRouter.Net.Internal.RetryPolicy 90.9% 84.7%
OpenRouter.Net.Internal.SchemaTooDeepException 100%
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% 98.1%
OpenRouter.Net.Internal.ToolChoiceJsonConverter 61.7% 46.1%
OpenRouter.Net.Internal.TooManyRefsFollowedException 100%
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% 81.2%
OpenRouter.Net.Models.Messages.DeveloperMessage 100%
OpenRouter.Net.Models.Messages.MessageJson 100% 100%
OpenRouter.Net.Models.Messages.SystemMessage 100%
OpenRouter.Net.Models.Messages.ToolMessage 100%
OpenRouter.Net.Models.Messages.UserMessage 100%
OpenRouter.Net.Models.Requests.ChatCompletionRequest 95.1% 94.8%
OpenRouter.Net.Models.Requests.JsonSchemaStrictness 99.3% 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.RequestPlugin 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 - 91.7%
Name Line Branch
OpenRouter.Net.Agents 91.7% 86.6%
OpenRouter.Net.Agents.Agent 98.4% 92.6%
OpenRouter.Net.Agents.Agent.StreamedRound 100% 98.6%
OpenRouter.Net.Agents.AgentOptions 100% 100%
OpenRouter.Net.Agents.AgentResult 100% 50%
OpenRouter.Net.Agents.CallerStates 100% 100%
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.Gating.ToolTurnReview 100%
OpenRouter.Net.Agents.RoundDetail 100% 75%
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 96.1% 69.5%
OpenRouter.Net.Agents.Tools.Tool 100% 100%
OpenRouter.Net.Agents.Tools.Tool 90% 50%
OpenRouter.Net.Agents.Tools.ToolInvocationContext 100%
OpenRouter.Net.Agents.Tools.ToolMessageExtensions 0%
OpenRouter.Net.Agents.Tools.ToolResult 100% 100%
OpenRouter.Net.Agents.Tools.ToolSchema 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: | 10/23/2026 - 16:19:02 | | Parser: | MultiReport (3x Cobertura) | | Assemblies: | 3 | | Classes: | 136 | | Files: | 116 | | **Line coverage:** | 96.3% (4698 of 4877) | | Covered lines: | 4698 | | Uncovered lines: | 179 | | Coverable lines: | 4877 | | Total lines: | 12097 | | **Branch coverage:** | 93.6% (3057 of 3265) | | Covered branches: | 3057 | | Total branches: | 3265 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>OpenRouter.Net - 98.1%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**OpenRouter.Net**|**98.1%**|**95.8%**| |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.ProviderFieldFold|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|85.7%|87.5%| |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|93.3%|93.1%| |OpenRouter.Net.Internal.ProviderSortConfigJsonConverter|94%|95.8%| |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.SchemaTooDeepException|100%|| |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%|98.1%| |OpenRouter.Net.Internal.ToolChoiceJsonConverter|61.7%|46.1%| |OpenRouter.Net.Internal.TooManyRefsFollowedException|100%|| |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%|81.2%| |OpenRouter.Net.Models.Messages.DeveloperMessage|100%|| |OpenRouter.Net.Models.Messages.MessageJson|100%|100%| |OpenRouter.Net.Models.Messages.SystemMessage|100%|| |OpenRouter.Net.Models.Messages.ToolMessage|100%|| |OpenRouter.Net.Models.Messages.UserMessage|100%|| |OpenRouter.Net.Models.Requests.ChatCompletionRequest|95.1%|94.8%| |OpenRouter.Net.Models.Requests.JsonSchemaStrictness|99.3%|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.RequestPlugin|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 - 91.7%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**OpenRouter.Net.Agents**|**91.7%**|**86.6%**| |OpenRouter.Net.Agents.Agent|98.4%|92.6%| |OpenRouter.Net.Agents.Agent.StreamedRound|100%|98.6%| |OpenRouter.Net.Agents.AgentOptions|100%|100%| |OpenRouter.Net.Agents.AgentResult|100%|50%| |OpenRouter.Net.Agents.CallerStates|100%|100%| |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.Gating.ToolTurnReview|100%|| |OpenRouter.Net.Agents.RoundDetail|100%|75%| |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|96.1%|69.5%| |OpenRouter.Net.Agents.Tools.Tool<TParams>|100%|100%| |OpenRouter.Net.Agents.Tools.Tool<TParams>|90%|50%| |OpenRouter.Net.Agents.Tools.ToolInvocationContext|100%|| |OpenRouter.Net.Agents.Tools.ToolMessageExtensions|0%|| |OpenRouter.Net.Agents.Tools.ToolResult|100%|100%| |OpenRouter.Net.Agents.Tools.ToolSchema|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>
bjoern merged commit b786f4af17 into main 2026-10-23 18:36:38 +02:00
bjoern deleted branch feat/caller-context 2026-10-23 18:36:38 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 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!35
No description provided.