feat(provider-utils)!: align StreamingToolCallTracker with the AI SDK and move it next to its users - #204
feat(provider-utils)!: align StreamingToolCallTracker with the AI SDK and move it next to its users#204cunninghamcard-bit wants to merge 5 commits into
Conversation
|
这里需要先与 #185 / ROADMAP 对齐 tracker 的方向:原计划是删除 tracker,再引入 SSE 大事件和解析行为修复可以与 tracker 的架构选择分开讨论;如果希望先落可独立验证的修复,可以考虑拆分。 另有两点需要补齐:当前 head |
No crate in the workspace referenced the tracker; providers accumulate streamed tool-call deltas themselves. Drops the module, its 787-line test file and the re-exported companion types. Breaking for aimux-stream's public Rust API; noted in CHANGELOG under Unreleased. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_015EYDYDWYcPsDmjBjFeuDVe
Re-adds the tracker as a port of the current @ai-sdk/provider-utils streaming-tool-call-tracker.ts instead of the older index-only version removed in the previous commit. - Correlate deltas by wire id, index and function name; drop ambiguous ones - Unique tool-call ids with bounded suffixes; blank generator output falls back to "tool-call" - Ignore unmatched blank function names; retain blank-name continuations - Flush in index order only when every call has an index - New StreamingToolCallArgumentState / starts_with_structured_value port - process_delta and flush return the emitted parts instead of buffering Tests are ported case for case from the upstream suites (45 tracker cases, 9 argument-state cases). No caller in the workspace uses the tracker yet. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_015EYDYDWYcPsDmjBjFeuDVe
…nd wire the OpenAI stream to it The AI SDK keeps StreamingToolCallTracker in @ai-sdk/provider-utils, where every provider that speaks the OpenAI chat-completions wire format imports it. aimux had the tracker in aimux-stream (the eventsource-parser role), with its own ToolCallStreamPart event type and no callers, while openai/model.rs kept an index-only accumulator. This commit puts the pieces where the AI SDK has them. aimux-stream - StreamingToolCallTracker, StreamingToolCallArgumentState, starts_with_structured_value and ToolCallStreamPart move out; the crate is SSE decoding only. serde_json becomes a dev-dependency (test fixture); the `tool-calls` keyword is dropped. aimux-provider-utils - streaming_tool_call_tracker.rs / streaming_tool_call_argument_state.rs land here, logic unchanged. The tracker emits aimux_core::StreamPart ToolInputStart / ToolInputDelta / ToolInputEnd / ToolCall directly, so there is no second event type (RFC-0036: ToolCallStreamPart must not be a second public protocol). The generic metadata parameter becomes serde_json::Value / ProviderMetadata; TrackerError converts into AiMuxError::InvalidResponseData. The ported upstream tests (45 + 9) move with it, projecting StreamPart back to the upstream event shape. aimux-providers - openai/model.rs replaces its HashMap<usize, ToolCallAccumulator> with the tracker: tool_calls deltas are correlated by wire id, index and function name instead of index alone; id-less continuations follow their call; indices reused across parallel calls stay distinct; ambiguous deltas are dropped; a call without a wire id gets a generated `tool-call` / `tool-call-N` id instead of an empty string; a new call without a function name ends the stream with InvalidResponseData (AI SDK behaviour) instead of starting a call with an empty name. - DeltaToolCall.index is Option<usize> (the AI SDK schema is `index: z.number().nullish()`). Every registry-backed provider goes through openai/model.rs, so this is the one plug point until S4-2 folds the mistral and xai chat copies into the same implementation. Verification: cargo test -p aimux-stream -p aimux-provider-utils; cargo test -p aimux-providers (129 suites, 3041 passed, 0 failed, including the cassette replays); cargo clippy -p aimux-stream -p aimux-provider-utils -p aimux-providers --all-targets -- -D warnings; cargo fmt --all -- --check. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
…-state port Carried over from the SSE test port commit so the two split branches leave the file identical. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
Describes only the final API: the tracker lives in aimux-provider-utils and emits StreamPart directly; the intermediate ToolCallStreamPart- returning API from earlier commits is not mentioned. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS
3521d5d to
793feeb
Compare
861f26e to
9260626
Compare
|
已拆分:SSE 部分单独成 PR(见 https://github.com/arcships/aimux/pull/213),本 PR 只剩 tracker,base 改为 方向:选"对齐上游并移入 provider-utils",不走 #185 的"纯删"。两点理由写在描述里:上游在 RFC-0042(#212)的 T2 接受"累积器放 provider-utils"这个归属,T3 要求歧义增量报 CHANGELOG 改成只描述最终状态(tracker 在 provider-utils、直接产出 Generated by Claude Code |
Summary
Now tracker only, based on
master; the SSE work moved to its own PR (see #213). Kept as draft until the direction below is confirmed against #185 / ROADMAP.StreamingToolCallTrackeris rewritten to match@ai-sdk/provider-utils(5.0.51) and moved fromaimux-streamtoaimux-provider-utils, where upstream keeps it. It emitsStreamPartdirectly (ToolInputStart/ToolInputDelta/ToolInputEnd/ToolCall). Deltas are correlated by wireid,indexand function name, ambiguous deltas are dropped, ids are de-duplicated with bounded suffixes, blank function names are ignored, andflushorders calls by index only when every call has one. AddsStreamingToolCallArgumentState/starts_with_structured_value.aimux-providersOpenAI chat (openai/model.rs, which serves every registry-backed provider) drives the tracker instead of its privateToolCallAccumulator;DeltaToolCall.indexbecomesOption<usize>to match the wire. Same wiring as@ai-sdk/openai's chat model.aimux-streamno longer exports the tracker orToolCallStreamPart.Direction vs. #185 / ROADMAP
#185 and ROADMAP planned "delete the tracker (pure deletion), then introduce
ToolInput{Raw,Parsed}". This PR takes the other route and the reasons are:@ai-sdk/provider-utilsand its OpenAI chat model drives it. aimux'sopenai/model.rshad its ownToolCallAccumulatordoing the same job with index-only correlation, which mishandles reused indices across parallel calls and id-less continuations. Deleting the unusedaimux-streamcopy would have left that duplicate in place.inputis the raw string; core'sparse_tool_callproduces the parsed value). The tracker is orthogonal to it.ROADMAP's #185 line is updated in #200 (§0.7 table + a note on the line). If the maintainers prefer the deletion route, this PR is closed and only the SSE PR goes in.
Review notes
aimux-provider-utils, noToolCallStreamPart); the intermediate return-ToolCallStreamPartAPI from earlier commits is gone from the entry.streaming_tool_call_tracker_test.rsand the argument-state test are case-for-case ports of the upstream tests at the pinned tag; the tracker test projectsStreamPartdown to the four fields upstream asserts on.tool-call(as before), not a random id.Checklist
cargo fmt --all -- --checkcargo clippy -p aimux-stream -p aimux-provider-utils -p aimux-providers --all-targets -- -D warningscargo docwithRUSTDOCFLAGS=-D warningscargo test -p aimux-stream -p aimux-provider-utils -p aimux-providers🤖 Generated with Claude Code
https://claude.ai/code/session_01WGwanvo9WLU9HWD7sRR8TS