Repository navigation
E.3a: ToolResult.providerExecuted passthrough (phase 1E; stacked on #26) - #27
Open
cunninghamcard-bit wants to merge 7 commits into
Open
cunninghamcard-bit wants to merge 7 commits into
cunninghamcard-bit wants to merge 7 commits into
Conversation
fixtures/v4/enum-state/: JSONL samples for ToolCall, ToolResult, RawToolCall and StreamPart/TextStreamPart tool-input-start (absent / explicit false / true, providerExecuted true/false/absent, invalid+error, D26 rejected inputs, RFC-0042:84-91 vectors as RawToolCall input), normalized/ per A7 D25, and baseline/ recorded at 8455db9. aimux-core/tests/enum_state_wire_test.rs compares serialize(deserialize(line)) byte for byte with the baseline and checks normalized/ is a round-trip fixed point. A7 copied verbatim into docs/amendments/.
New aimux_core::tool::RawArguments newtype, serde(transparent) and #[ts(as = "String")] on the field, so the wire and RawToolCall.ts are unchanged. Not an enum (arcships#200:2724). Provides new/as_str/into_string/push_str, From<String|&str>, Into<String>, Deref<str>, AsRef<str>, Display and PartialEq with str/String/serde_json::Value (parity with String). Call sites in core, provider-utils, providers and tests converted with .into() / .to_string(); no behaviour change. Enum-state samples still match the 8455db9 baseline byte for byte.
ToolCall = Static(StaticToolCall) | Dynamic(DynamicToolCall { validity:
Valid | Invalid { error } }), serialized through the private ToolCallWire
(the old struct) with serde(try_from/into) and #[ts(as = "ToolCallWire")];
ToolCall.ts is unchanged. D25: static writes no dynamic, dynamic writes
true, invalid only as true with its error, providerExecuted passed through.
D26: invalid without dynamic:true or without error fails conversion.
Accessors (tool_call_id/tool_name/input/provider_executed/provider_metadata/
is_dynamic/is_invalid/error); parsed_tool_call_arguments and
response_tool_call_input take &ToolCall and match. Consumers converted.
tool_call.jsonl gains the RFC-0042:84-93 core values (string "Tokyo", string
"{\"x\":1}" kept a string, number, bool, null, last-wins {"a":2}, 100.0)
and empty text / {"a": as invalid dynamic calls with an error (never {}).
tool_call_semantic.jsonl holds the non-canonical spellings (duplicate key,
1e2), compared by JSON semantics. check_normalized asserts line counts before
zipping; baseline_equals_samples is skipped in bless mode. Baseline re-blessed
at 8455db9 (existing lines unchanged; new lines appended).
Adds providerExecuted?: boolean to ToolResult only (no enum change), per arcships#200:2292 / AI SDK generate-text/tool-result.ts:17,32. Absent/false/true round-trip byte for byte. Provider converters construct V4 tool results, which carry no providerExecuted in @ai-sdk/provider 4.0.21 (language-model-v4-tool-result.ts), so they write None; every other path moves the whole ToolResult and passes the field through. Samples: the providerExecuted x dynamic rows (incl. provider-executed without dynamic) and the D25 normalized form for E.3.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 1E unit E.3a (PHASES.md, added 2026-10-11): adds
providerExecuted?toToolResultonly. No enum change. Stacked on #26 (E.2) → #25 → #24. The E.3a-only change is commit 5f427aa.Change
aimux-core/src/tool.rs:ToolResultgetspub provider_executed: Option<bool>with#[ts(optional)]andserde(default, skip_serializing_if = "Option::is_none"). It sits afterdynamic. Sources: docs: full-chain AI SDK alignment design and impact map arcships/aimux#200:2292 and AI SDKgenerate-text/tool-result.ts:17,32.bindings/node/src/types/ToolResult.ts: +6 lines only, which is the field's doc comment plusproviderExecuted?: boolean,(git diff --stat: 6 insertions, 0 deletions).gen_ts_types.py --checkpasses. Hand-written bindings are untouched.Where ToolResults are produced (all 29 struct literals; the commit adds
provider_executedat each)Every literal is in a provider converter that builds the V4 provider tool-result. In
@ai-sdk/provider@4.0.21,language-model-v4-tool-result.tshas noproviderExecutedfield, so the source carries nothing and each literal writesNone. This keeps provider output identical to AI SDK provider output.I first tried mirroring the paired call's
Some(true). That brokeopenai_upstream_e2e_test::responses_web_search_stream, a port of the AI SDK provider test, because its expectedtool-resulthas noproviderExecuted. So I reverted toNone.Everywhere else in core and bindings, ToolResult values move whole. These paths carry the field through unchanged:
stream_part.rs:357,:395generate.rs:472,:792response_messages.rs:174,:257Literal sites (file:line of the inserted field):
aimux-providers/src/anthropic/stream.rs:380aimux-providers/src/anthropic/stream.rs:400aimux-providers/src/anthropic/stream.rs:502aimux-providers/src/anthropic/stream.rs:800aimux-providers/src/anthropic/stream.rs:835aimux-providers/src/anthropic/stream.rs:860aimux-providers/src/anthropic/stream.rs:876aimux-providers/src/anthropic/stream.rs:897aimux-providers/src/anthropic/stream.rs:917aimux-providers/src/anthropic/stream.rs:933aimux-providers/src/anthropic/stream.rs:952aimux-providers/src/google/model.rs:481aimux-providers/src/google/model.rs:536aimux-providers/src/google/model.rs:839aimux-providers/src/google/model.rs:971aimux-providers/src/huggingface/responses.rs:455aimux-providers/src/huggingface/responses.rs:1044aimux-providers/src/huggingface/responses.rs:1070aimux-providers/src/openai/responses/provider_events.rs:44aimux-providers/src/openai/responses/responses_convert.rs:913aimux-providers/src/vertex/model.rs:649aimux-providers/src/vertex/model.rs:710aimux-providers/src/vertex/model.rs:928aimux-providers/src/vertex/model.rs:1076aimux-providers/src/xai/responses/mod.rs:162aimux-providers/src/xai/responses/mod.rs:280aimux-providers/src/xai/responses/mod.rs:319aimux-providers/src/xai/responses/mod.rs:1063aimux-providers/src/xai/responses/mod.rs:1131Open for E.3 (needs a decision): AI SDK core sets
providerExecuted: trueitself when it turns a provider tool-result into step content or a stream part:convert-language-model-content.ts:113,129,149,165stream-language-model-call.ts:772,788The matching aimux points are
generate.rs:792andstream_part.rs:395. E.3a does not do this, because it would change core output that existing tests assert. It belongs with E.3's provider-executed variant.Samples (
fixtures/v4/enum-state/)tool_result.jsonladds 6 rows:providerExecutedfalse/true crossed withdynamicabsent/false/true. That includes provider-executed withoutdynamic. Rows withoutproviderExecutedare the absent case.normalized/tool_result.jsonlis now the A7 D25 form plus the A7 correction:providerExecutednot true) always writedynamicas a boolean;dynamicthrough;providerExecuteditself is always passed through.tool_result_samples_match_baseline: all 14 rows round-trip byte for byte (passthrough). The 8 rows that existed at 8455db9 still equalbaseline/. The normalized lines are a fixed point.serialize(x) == normalize(x)is E.3's job (the enum).tool_result_provider_executed_is_passed_throughcovers absent/false/true and provider-executed withoutdynamic, compared byte for byte.baseline_equals_samplesno longer liststool_result, because its new rows cannot exist at 8455db9. The old rows are still checked againstbaseline/in the test above.Acceptance
ToolResult.tsonly gainsproviderExecuted?: boolean(plus its doc comment).git diff --stat -- fixtures/ contract-tests/fixtures/ aimux-providers/tests/cassettes/outsidefixtures/v4/enum-state/is empty.untaggedorflatten.Results (local linux x64)