Releases: Andes-Software-Solutions/Andes.Extensions.AI
Release list
v0.9.0
What's Changed
- Update package versions and dependencies to latest stable by @RorroRojas3 in #17
Full Changelog: v0.8.0...v0.9.0
v0.8.0
What's Changed
- Add application-supplied Metadata to the UI contract (0.8.0) by @RorroRojas3 in #15
Full Changelog: v0.7.0...v0.8.0
v0.7.0
What's Changed
- Bump version to 0.7.0 and update UI contract for MCP tool by @RorroRojas3 in #13
Full Changelog: v0.6.0...v0.7.0
v0.6.0
What's Changed
- Add ToolCallUsage.Iteration for per-tool prompt-cost attribution; bump to 0.6.0 by @RorroRojas3 in #11
The usage report learns which model turn issued each tool call: ToolCallUsage.Iteration (#10) correlates the ToolCalls tree with the per-turn Turns breakdown, closing the gap that made exact per-tool prompt-cost attribution impossible — the cost of a tool's arguments and result is billed in the next iteration's prompt, and until now nothing said which calls sat between two turns. Dependency floors move to Microsoft.Extensions.AI 10.9.0 and ModelContextProtocol.Core 2.2.0.
Added
ToolCallUsage.Iteration(Andes.Extensions.AI/Usage/ToolCallUsage.cs, #10): the zero-based model iteration of the function-invocation loop that issued the call, corresponding toAssistantTurnUsage.Iteration. When streaming, the value is stamped the moment the tool's scope opens, so it always names the issuing turn — including when one turn issues several calls, which timing alone could never disambiguate (with the default serial invocation, N calls from one turn are indistinguishable from N single-call turns). Nested calls (theChildrenof another call — an agent's own tools, or a nested agent/MCP child card) report the iteration of the outer request's model turn that issued the enclosing root call. On the non-streaming path the value is always 0, consistent withChatUsageReport.Turnsbeing populated only when streaming.- Why (from the issue): per-tool prompt-cost attribution. A tool — an MCP tool especially — consumes no tokens directly, but its arguments and result enter the next iteration's prompt and are billed there. With the per-turn breakdown that cost is recoverable exactly, from provider-reported integers rather than a tokenizer estimate —
input(N+1) − input(N) − output(N)— andIterationis the missing piece that tells you which calls sit between turn N and turn N+1. The library reports the facts; the delta arithmetic stays with the consumer (reasoning tokens bill as output but are usually not replayed into the next prompt, and cached input bills at a discount, so the subtraction is an attribution model, not an invoice). - The property is
requiredwith aninitsetter. Impact is compile-time only, and only for code constructingToolCallUsagedirectly via an object initializer — the library's sole construction site is the internalRequestTracker, and code that reads reports is unaffected.
- Why (from the issue): per-tool prompt-cost attribution. A tool — an MCP tool especially — consumes no tokens directly, but its arguments and result enter the next iteration's prompt and are billed there. With the per-turn breakdown that cost is recoverable exactly, from provider-reported integers rather than a tokenizer estimate —
Changed
- Dependency pins (
Directory.Packages.props) — the consumer-visible floors:Microsoft.Extensions.AI(the core package's dependency) andMicrosoft.Extensions.AI.Abstractions(the UI package's dependency) 10.8.3 → 10.9.0, andModelContextProtocol.Core(the MCP satellite's dependency) 2.1.0 → 2.2.0. The Agent satellite's floor is unchanged (Microsoft.Agents.AIstays 1.17.0).OpenAIdeliberately stays 2.12.0:Microsoft.Extensions.AI.OpenAI10.9.0 constrains it to>= 2.12.0 && < 2.13.0. Test- and sample-only bumps ride along, none of them shipped to NuGet:Microsoft.Extensions.AI.OpenAI10.8.3 → 10.9.0,ModelContextProtocol(the full package) 2.1.0 → 2.2.0,Microsoft.Extensions.Configuration.Binder/.JsonandMicrosoft.Extensions.Hosting10.0.10 → 10.0.11,Microsoft.NET.Test.Sdk18.8.1 → 18.9.0, andxunit.runner.visualstudio3.1.5 → 4.0.0. - Docs updated for the new member: Getting started adds
Iterationto the usage-report member notes; Architecture adds it to the report shape and gains a correlation paragraph under Usage attribution explaining the delta formula and its caveats; MCP tool tracking and Agent tool tracking note the nested-child semantics where the childToolCallUsageis discussed; and the satellite prerequisites reflect the new floors. - All four packages version in lockstep at 0.6.0; the satellites depend on core
>= 0.6.0.
Full Changelog: v0.5.0...v0.6.0
v0.5.0
0.5.0
Tag: v0.5.0 · Date: 2026-08-06
The middleware stops guessing: the auto-emitted "Starting request"/"Thinking..." statuses are gone, replaced by a detection-driven Reasoning/ReasoningCompleted status pair that fires only when the model actually streams reasoning content — opened the moment reasoning is detected, closed (with the elapsed reasoning time) the moment the answer or the next tool call starts — plus a public Custom status factory so applications emit their own request-level statuses — in the exact synthetic shape the middleware uses. The UI package gains a reasoning-text surface (ReasoningDelta events folding into AssistantStatusSnapshot.ReasoningText), and a new sample runs the whole pipeline over the Azure OpenAI Responses API with stable packages only.
Breaking changes
ChatProgressKind.Thinkingis renamed toChatProgressKind.Reasoning(Andes.Extensions.AI/Progress/ChatProgressKind.cs; the underlying value1is preserved). Consumers switching on the enum renameThinking→Reasoning; persisted numeric values are unaffected.ChatProgressKind.RequestStartedis renamed toChatProgressKind.Custom(same file; the underlying value0is preserved): a developer-constructed status carrying any application-supplied message, never emitted by the middleware. Its factory isChatProgressUpdate.CreateCustom(string message)— the message is required (ArgumentException.ThrowIfNullOrEmpty:ArgumentNullExceptiononnull,ArgumentExceptionon empty), replacing the interimCreateRequestStarted(string? message = null)from earlier drafts of this release. The interimCreateReasoning(...)factory is removed without replacement:Reasoningis strictly detection-driven (model-only), so applications can never emit it via a factory.- The middleware no longer auto-emits request-level statuses. Previous versions opened every request (both call styles) with a "Starting request" status and a
Thinking("Thinking...") status, and re-emitted "Thinking..." after each tool round-trip. Those emissions are removed —RequestTracker.EmitRequestStarted/EmitThinkingare deleted, andAdvanceIterationnow only advances the turn counter and re-arms reasoning detection — so the first in-band event of a tool-calling request is now theToolInvokingheader.- Migration: UIs that relied on the automatic opening status prepend their own —
yield return ChatProgressUpdate.CreateCustom("Starting request").ToResponseUpdate();ahead of streaming the tracked response (see Added below) — or handle the absence (the UI package'sAssistantStatusSnapshot.AssistantStatusnow staysnulluntil the first request-level event arrives).
- Migration: UIs that relied on the automatic opening status prepend their own —
Added
- Detection-driven
Reasoningstatus.ToolTrackingChatClient.Inspectwatches the stream forMicrosoft.Extensions.AI.TextReasoningContentand calls the newRequestTracker.OnReasoningDetected(), which emits oneReasoningevent per model turn (message "Reasoning...", root scope, depth 0), re-armed after each tool round-trip. Updates are inspected before they are forwarded, so the status enters the channel ahead of the update carrying the reasoning content. Detection is content-based and therefore provider-agnostic: the OpenAI Responses API producesTextReasoningContenttoday, while plain Chat Completions never streams reasoning — chat pipelines simply never see the status. Non-streamingGetResponseAsyncmirrors this post-hoc: if any response message containsTextReasoningContent, at most oneReasoningevent is raised (observers only — turns are indistinguishable in an aggregated response). The event never carries the reasoning text itself (privacy invariant unchanged), and with no public factory for the kind, it only ever originates from this detection. ChatProgressKind.ReasoningCompletedcloses each detected reasoning turn. New enum member (ReasoningCompleted = 8,Andes.Extensions.AI/Progress/ChatProgressKind.cs), raised at most once per model turn by aRequestTracker.OnReasoningCompleted()latch — re-armed alongside detection byAdvanceIteration()after each tool round-trip — when the first answer text (non-emptyTextContent) orFunctionCallContentfollows detected reasoning, or when the stream ends on a reasoning-only final turn. The function-call hook fires beforeOnFunctionCall, so the close precedes anyToolInvokingheader; the stream-end close happens inPumpAsyncbefore the channel completes, so the event stays in-band ahead of the trailingRequestCompleted. Message "Reasoning completed", root scope, depth 0.ChatProgressUpdate.Durationcarries the elapsed reasoning time — first detection to the first completion trigger — when streaming; the non-streaming post-hoc mirror passesmeasured: false, soDurationstaysnullthere while observers now receive a balancedReasoning+ReasoningCompletedpair per request. LikeReasoning, the kind has no public factory and never carries reasoning text (privacy invariant unchanged), and the failure path is untouched — a request that faults after detection raisesRequestFailedwith no reasoning completion. The UI package needed zero code changes:MapKindcollapses the new kind toAssistantUiEventKind.Status(status line "Reasoning completed") andToUiEventalready mapsDuration→DurationSecondsgenerically, so UIs receive the reasoning duration on thatStatusevent — the shipped TypeScript contract is untouched (ChatProgressKindnever crosses the wire). Visible in the Responses demo: the header now flips from "Reasoning..." to "Reasoning completed" the moment the answer (or the next tool call) starts, instead of "Reasoning..." lingering until the next event.- Developer-owned request statuses. The public static factory
ChatProgressUpdate.CreateCustom(string message)constructs a request-levelCustomupdate carrying any application-supplied message, stamped with the new public constantChatProgressUpdate.ExternalScopeId("scope-external"— never collides with the middleware's per-request scope identifiers), depth 0, and the current UTC time. The new extensionChatProgressUpdateExtensions.ToResponseUpdate()(Andes.Extensions.AI/Progress/ChatProgressUpdateExtensions.cs) wraps an update into a role-lessChatResponseUpdatecarrying a singleChatProgressContent— the exact synthetic shape the middleware emits — so apps can prepend or interleave their own statuses into the stream a UI consumes (for example, ahead ofToStatusSnapshotsAsync()). Both sample apps demonstrate the prepend pattern withCreateCustom("Starting request")in theirStreamTurnlocal function. Andes.Extensions.AI.UI: the model's reasoning summary text now propagates through the UI contract. NewAssistantUiEventKind.ReasoningDelta(betweenTextDeltaandFinished):ChatResponseUiExtensions.ToUiEventsAsyncemits one per in-bandTextReasoningContentwith non-empty text — encrypted-only reasoning items (empty text) carry nothing renderable and are skipped — with the chunk inAssistantUiEvent.Text.AssistantStatusReduceraccumulates the deltas into the newAssistantStatusSnapshot.ReasoningText: verbatim concatenation across the whole request, including tool round-trips, with no synthetic separators. The shipped TypeScript mirror (typescript/andes-assistant-ui.ts) gains the"ReasoningDelta"union member,reasoningText?: string, and the matchingfoldAssistantEventscase. The privacy boundary holds: reasoning text is sourced only from in-band model content the stream already carries — core progress events remain text-free.- New sample:
samples/Andes.Extensions.AI.Demo.Responses. A console chat over the Azure OpenAI Responses API with stable packages only: the plainOpenAIClient(stableOpenAI2.12.0) targets the OpenAI-v1-compatible endpoint ({endpoint}/openai/v1) — the stableAzure.AI.OpenAI2.1.0 has no Responses surface, which is why the plain-client route is used — andGetResponsesClient().AsIChatClient(deployment)adapts it to the tracked pipeline.ChatOptions.Reasoning = new ReasoningOptions { Output = ReasoningOutput.Full }makes reasoning summaries stream back asTextReasoningContent, lighting up the liveReasoningstatus (see Fixed for whySummarydoes not work on gpt-5-series deployments), and the demo'sStatusRenderershows the accumulatingAssistantStatusSnapshot.ReasoningTextin a dimmed "reasoning" panel — the last six lines while the Live frame streams, and the full text in the persistent final frame (and on failures), with the total reasoning time in the panel header summed from the recordedReasoningCompletedstatuses'Duration. Requires a reasoning-capable deployment (gpt-5 family / o-series); builds withNoWarnOPENAI001because the Responses surface is still[Experimental]in OpenAI 2.12.0. Registered inAndes.Extensions.slnx;IsPackable=falselike the existing sample — it never ships to NuGet. - New optional integration setting
AzureOpenAI:ResponsesDeployment, gating the new[SkippableFact]suitetests/Andes.Extensions.AI.Integration.Test/ResponsesStreamingIntegrationTests.cs: end to end against the Responses API, it asserts that noCustomstatus appears (the middleware never emits one), that exactly one "Reasoning..." status is raised for a single-turn request, that the status precedes the first reasoning content, and that a singleReasoningCompletedwith a non-nullDurationfollows it — pair ordering rather than fixed positions, since real turn structure varies by deployment. When the setting is absent those tests skip cleanly while the chat-deployment tests still run. - Root README badges — NuGet version badges for all four packages, the NuGet Publish workflow status, the MIT license, and .NET 10.
Changed
- Dependency pins (`Director...
v0.4.0
What's Changed
- Bump package versions to 0.4.0 and update release notes by @RorroRojas3 in #8
Andes.Extensions.AI.Mcpnow requiresModelContextProtocol.Core>= 2.0.0 (was>= 1.4.1) — a major release of the MCP C# SDK, published 2026-07-28 and aligned with the MCP 2026-07-28 specification (see the SDK release notes). MCP 2.0 carries its own breaking changes and deprecations for consumers (stateless-by-default HTTP; Roots, Sampling, and Logging obsoleted), which is why this is a minor bump (0.3.1 → 0.4.0) rather than a patch despite no code changes in this repository.- The
Andes.Extensions.AI.Mcpsource itself compiled against MCP 2.0 with zero warnings (warnings are errors) and required no migration — it uses none of the APIs MCP 2.0 deprecates (Roots/Sampling/Logging).
- The
- Core now floors
Microsoft.Extensions.AI>= 10.8.3 andAndes.Extensions.AI.UIfloorsMicrosoft.Extensions.AI.Abstractions>= 10.8.3 (both were>= 10.8.1). Andes.Extensions.AI.Agentnow floorsMicrosoft.Agents.AI>= 1.16.0 (was>= 1.15.0).- The remaining pin updates —
ModelContextProtocol1.4.1 → 2.0.0 andMicrosoft.Extensions.AI.OpenAI10.8.1 → 10.8.3 — are consumed only by the tests, the test MCP server, and the demo sample, none of which ship to NuGet. - All four packages version in lockstep at 0.4.0; the satellites depend on core
>= 0.4.0.
Full Changelog: v0.3.1...v0.4.0
v0.3.1
What's Changed
- Bump version to 0.3.1 and update repository links by @RorroRojas3 in #7
Full Changelog: v0.3.0...v0.3.1
v0.3.0
What's Changed
- Add Andes.Extensions.AI.UI status contract and de-dup progress headers by @RorroRojas3 in #6
Full Changelog: v0.2.0...v0.3.0
v0.2.0
What's Changed
- Enhance MCP progress reporting, handling, and documentation by @RorroRojas3 in #1
- Add integration and unit tests for MCP tool tracking and classification by @RorroRojas3 in #2
- Add Andes.Extensions.AI.Agent package and enhance documentation by @RorroRojas3 in #3
- Enhance GitHub Actions with efficiency and security skills by @RorroRojas3 in #4
- Update CODEOWNERS and nuget.yml for policy clarity by @RorroRojas3 in #5
New Contributors
- @RorroRojas3 made their first contribution in #1
Full Changelog: https://github.com/Andes-Software-Solutions/Andes.Extensions.AI/commits/v0.2.0