Repository navigation
fix: OpenAI Responses reasoning deltas carry the item id - #323
cunninghamcard-bit wants to merge 7 commits into
Conversation
@ai-sdk/openai 4.0.83 takes `maxNumResults` as a JS number and sends it as is (openai-responses-prepare-tools.ts:219), so `5` reaches the API's integer field as `5`. aimux typed the field `Option<f64>`, so the typed factory sent `5.0`. It is an `Option<u32>` now; the other number fields of the tool arguments that count things (`partial_images`, `output_compression`) are integers already. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5
… never started @ai-sdk/openai 4.0.83 starts the stream's `responseId` at `null` (openai-responses-language-model.ts:1587) and only `response.created` sets it, so a stream without that event finishes with `responseId: null`. aimux wrote an empty string. The generate path already writes `null`. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5
@ai-sdk/openai 4.0.83 puts the reasoning item's id on every `reasoning-delta` as `providerMetadata.<key>.itemId` (openai-responses-language-model.ts:2735-2749). aimux sent the deltas without provider metadata, while its reasoning start and end parts already carried it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5
…m-id Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5
eric8810
left a comment
There was a problem hiding this comment.
reasoning delta 现在带 item id,但用的是原始 item_id;上游在 output_item.added 按 output_index 记录并在三个 summary 分支用 resolveOutputItemId。Responses-compatible 旋转 opaque id 时,新的 providerMetadata/openai/itemId 与 reasoning-start 的 {id}:0 不一致,可能开出第二个 reasoning part。建议按 output_index 解析,并让测试使用 rotated id + output_index:0。另:FileSearchArgs.max_num_results 的 f64→u32 是公开类型破坏性变化,请移到 CHANGELOG 的 Breaking。
… index @ai-sdk/openai 4.0.83 records the id each output item started with by output_index (openai-responses-language-model.ts:1633-1644, set at :1905 for reasoning) and resolves reasoning_summary_part.added (:2688), reasoning_summary_text.delta (:2736), reasoning_summary_part.done (:2751) and the reasoning output_item.done (:2503) through it. aimux did this only for messages, so rotated reasoning item ids split one reasoning part into several. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5
…er Breaking FileSearchArgs::max_num_results changed from Option<f64> to Option<u32>, a change to a public type, so its entry belongs under Breaking rather than Fixed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5
…s-reasoning-deltas-carry-the-item-id # Conflicts: # CHANGELOG.md
另:d6f40825 合入了最新 master(只有 CHANGELOG 冲突,两边都保留)。本地全量检查通过(fmt、边界脚本、clippy、2825 个测试)。 Generated by Claude Code Generated by Claude Code |
The reasoning delta now resolves its item id through output_index (commit "correlate rotated Responses reasoning item ids by output index"), so its test sends rotated ids on the summary events and expects the id the item started with, as asked on #323. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5
eric8810
left a comment
There was a problem hiding this comment.
inference 现在按 output_index 的 resolve_output_item_id 解析 rotated id,测试覆盖 rs_1..rs_5;FileSearchArgs 的 u32 变更已移入 Breaking。Approve。
…the AI SDK (#368) * fix(openai): finish a Responses stream with a null responseId when it never started @ai-sdk/openai 4.0.83 starts the stream's `responseId` at `null` (openai-responses-language-model.ts:1587) and only `response.created` sets it, so a stream without that event finishes with `responseId: null`. aimux wrote an empty string. The generate path already writes `null`. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): give Responses reasoning deltas the item id @ai-sdk/openai 4.0.83 puts the reasoning item's id on every `reasoning-delta` as `providerMetadata.<key>.itemId` (openai-responses-language-model.ts:2735-2749). aimux sent the deltas without provider metadata, while its reasoning start and end parts already carried it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): send the file search max_num_results as an integer @ai-sdk/openai 4.0.83 takes `maxNumResults` as a JS number and sends it as is (openai-responses-prepare-tools.ts:219), so `5` reaches the API's integer field as `5`. aimux typed the field `Option<f64>`, so the typed factory sent `5.0`. It is an `Option<u32>` now; the other number fields of the tool arguments that count things (`partial_images`, `output_compression`) are integers already. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): leave null keys and unknown fields out of computer actions @ai-sdk/openai 4.0.83 maps each computer action with mapComputerAction (openai-responses-language-model.ts:128): it keeps the fields of the action's type, renames the scroll offsets, and adds `keys` only when it is not null. aimux copied the wire action and only renamed the scroll offsets, so `"keys":null` (and any extra field) reached the tool input. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): map only the AI SDK's provider tools back to caller tool names @ai-sdk/openai 4.0.83 builds its tool name mapping from a fixed providerToolNames table (openai-responses-language-model.ts:378): code interpreter, computer, file search, image generation, local shell, shell, web search (and preview), MCP, apply patch, tool search and programmatic tool calling. aimux mapped the name of any `openai.*` provider tool, so `toCustomToolName('computer_use')` (line 1358) returned the caller's name for an `openai.computer_use` tool instead of `computer_use`. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): keep only the schema fields of Responses text annotations @ai-sdk/openai 4.0.83 parses annotations with one zod object per type (openai-responses-api.ts:1500-1535 for responses, 1366-1395 for the annotation.added chunk), which drops unknown keys, and puts the parsed annotations in the text part's provider metadata (openai-responses-language-model.ts:1072). aimux copied the wire annotation, so a container_file_citation kept an `index` field. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): list apply patch operation fields in the AI SDK's schema order @ai-sdk/openai 4.0.83 parses an apply_patch_call operation as {type, path, diff} (openai-responses-api.ts:1698) and JSON.stringifies the parsed operation into the tool input (openai-responses-language-model.ts:1443, and the delete_file delta in the stream at line 1857), so the keys follow the schema order. aimux stringified the wire object, keeping the order the API sent. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): list local shell action fields in the AI SDK's order @ai-sdk/openai 4.0.83 parses a local_shell_call action as {type, command, timeout_ms, user, working_directory, env} (openai-responses-api.ts:147) and stringifies the parsed action into the generated tool input (openai-responses-language-model.ts:998); the stream writes {type, command, timeoutMs, user, workingDirectory, env} (line 2435). aimux stringified the wire action, and in the stream moved the renamed keys to the end. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): reject a Responses message part that is not output_text @ai-sdk/openai 4.0.83 lets a message's content hold only output_text parts (openai-responses-api.ts:1486), so a refusal part fails the response schema and createJsonResponseHandler rejects the call with an APICallError ("Invalid JSON response"); openai-evaluation.test.ts expects doEvaluate to reject with APICallError for it. aimux skipped non-output_text parts and returned a result without them. The stream schema does not validate message content (openai-responses-api.ts:1098), so streaming is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): drop bytes from Responses logprobs metadata @ai-sdk/openai 4.0.83 parses Responses logprobs as {token, logprob, top_logprobs: [{token, logprob}]} (openai-responses-api.ts:875 chunk, :1490 response), dropping other keys, and pushes the parsed value into providerMetadata.openai.logprobs (openai-responses-language-model.ts:1065 generate, :2685 stream). aimux pushed the wire value, `bytes` included. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): put the namespace on a streamed function call's tool-input-end @ai-sdk/openai 4.0.83 enqueues the tool-input-end of a streamed function_call with providerMetadata { namespace } when the item has one (openai-responses-language-model.ts:1986). aimux sent the tool-input-end without provider metadata and put the namespace only on the tool-call. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): end early Responses reasoning summary parts with the item id only @ai-sdk/openai 4.0.83 ends a summary part concluded when the next part is added (openai-responses-language-model.ts:2709) or on reasoning_summary_part.done with store (:2762) with providerMetadata { itemId }; only the ends sent on output_item.done (:2523) carry reasoningEncryptedContent. aimux added reasoningEncryptedContent to the early ends as well. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): correlate rotated Responses reasoning item ids by output index @ai-sdk/openai 4.0.83 records the id each output item started with by output_index (openai-responses-language-model.ts:1633-1644, set at :1905 for reasoning) and resolves reasoning_summary_part.added (:2688), reasoning_summary_text.delta (:2736), reasoning_summary_part.done (:2751) and the reasoning output_item.done (:2503) through it. aimux did this only for messages, so rotated reasoning item ids split one reasoning part into several. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): report a Chat Completions chunk on a Responses stream @ai-sdk/openai 4.0.83 sends an error part and finishes with error for a chunk that fails the Responses schema (openai-responses-language-model.ts:1664-1680); when the chunk looks like a Chat Completions chunk (a choices array and no string type, :2962) the error is an APICallError explaining that the endpoint speaks Chat Completions (:2972). aimux ignored the chunk as an unknown event. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): report schema-invalid known Responses stream events as errors @ai-sdk/openai 4.0.83 parses each stream chunk with openaiResponsesChunkSchema (openai-responses-api.ts:869-1443); a modeled event that fails its schema is not matched by the unknown-chunk fallback (:1433-1441), so doStream sends the TypeValidationError as an error part and finishes with error (openai-responses-language-model.ts:1664-1680). aimux read events as loose JSON: a missing output_index defaulted to 0, a missing item was skipped. This checks the envelope fields of each modeled event (type, output_index, item and its type, item_id, the delta/arguments/code/diff string, summary_index, the response object); item internals stay unchecked. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): reject a Responses stream that errors before any output @ai-sdk/openai 4.0.83 passes the Responses event stream through throwIfOpenAIStreamErrorBeforeOutput (openai-stream-error.ts:39, called at openai-responses-language-model.ts:1552): it reads events until the first output event (isResponseOutputChunk, :3112), throws an APICallError for an error frame or a response.failed with an error, stops at a chunk that fails parsing, and after response.in_progress races each read against 50 ms. aimux only checked the first event, so an error after response.created became an error part of a resolved stream. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): warn about unsupported async tools with the quoted tool name and the GPT-6 requirement Upstream resolveAsyncToolOption (src/responses/openai-responses-prepare-tools.ts:684-688) warns with feature `async tool calling for "<name>"` and details "Async tool calling is only supported by GPT-6 and later models.". aimux left the name unquoted and said "Async tool calling is not supported by this model." Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): quote allowedTools entries in warnings and give the upstream reasons Upstream prepareResponsesTools (src/responses/openai-responses-prepare-tools.ts:183-202, :454-504, :654-660) warns with feature `allowedTools entry "<name>"` and separate reasons for a namespaced tool ("tools inside an OpenAI tool namespace are not visible ..."), a deferred tool ("deferred tools are not visible ..."), an unlistable provider tool ("OpenAI does not support <type> tools in tool_choice.allowed_tools") and an ambiguous provider tool name. aimux left the name unquoted, merged the namespace and deferred reasons, and worded the tool_search and ambiguity details differently. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): name the dropped entries when no allowedTools entry can be expressed Upstream prepareResponsesTools (src/responses/openai-responses-prepare-tools.ts:509-515) throws UnsupportedFunctionalityError "allowedTools with only tools that cannot be allow-listed (<dropped names joined by ", ">)". aimux threw the message without the list. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): quote the namespace name in the conflicting namespace descriptions error Upstream prepareResponsesTools (src/responses/openai-responses-prepare-tools.ts:174-178) throws UnsupportedFunctionalityError `conflicting descriptions for OpenAI tool namespace "<name>"`. aimux left the name unquoted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): name the available providers when a Responses file or skill reference has no openai entry Upstream resolves file references (src/responses/convert-to-openai-responses-input.ts:142-145, :548-551) and shell skill references (src/responses/openai-responses-prepare-tools.ts:780-783) with resolveProviderReference, whose NoSuchProviderReferenceError says "No provider reference found for provider '<provider>'. Available providers: <keys>", as the chat conversion in aimux already does. The Responses code said "No file reference for openai" and "No skill reference for openai". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): list the supported efforts when a reasoningEffortUpdate is not supported by the model Upstream (src/responses/openai-responses-language-model.ts:455-492) warns about a request-level reasoningEffortUpdate outside the model's supported efforts with "<model> only supports the following reasoning efforts: <efforts joined by ", ">". aimux said "<model> does not support reasoning effort <effort>". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): replay provider-executed tool search and program calls in full without response storage Upstream convertToOpenAIResponsesInput (src/responses/convert-to-openai-responses-input.ts:780-842) handles tool search and programmatic tool calls before the providerExecuted check: with store false they are rebuilt as tool_search_call / program items. aimux applied the provider-executed rule first and dropped them (only shell calls were rebuilt). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): send the caller of a tool result on its function_call_output Upstream convertToOpenAIResponsesInput (src/responses/convert-to-openai-responses-input.ts:1646-1683) maps the caller provider option of a tool result with mapToolCaller and adds it to the function_call_output item. aimux mapped the caller only on function_call items. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(provider-utils): unparsable stream events report the AI SDK parse error A streamed SSE event whose data is not JSON yielded `JsonParse` with only the parser's message. The AI SDK's `parseJsonEventStream` reads each event with `safeParseJSON`, whose `JSONParseError` reads `JSON parsing failed: Text: <text>.\nError message: <message>` (`@ai-sdk/provider` src/errors/json-parse-error.ts:16-19). Found while porting the `@ai-sdk/openai-compatible` 3.0.62 tests (`doStream › should handle unparsable stream parts`, chat and completion); every stream read through `create_event_source_response_handler` now reports it the same way. Well-formed JSON of the wrong shape stays `InvalidResponseData`. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): name GPT-6 models in the chat promptCacheRetention warning Upstream chat/openai-chat-language-model.ts:235-245 drops prompt_cache_retention for models with supportedReasoningEfforts and warns "promptCacheRetention is not supported by GPT-6 and later models; use promptCacheOptions instead". aimux dropped the field but said "sixth-generation and later models". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): send embedding dimensions as the number given Upstream embedding/openai-embedding-model.ts:94 passes openaiOptions.dimensions through unchanged, so 64 is serialized as 64. aimux parsed it into an f64 and sent 64.0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): name the evaluation structured output "evaluation" Upstream src/openai-evaluation.test.ts:72-89 expects the Responses request of provider.evaluationModel() to carry text.format.name "evaluation" (EvaluationLanguageModel from provider-utils; its own test, evaluation-language-model.test.ts:87, expects responseFormat.name "evaluation"). aimux's shared DecisionLanguageModel sent "decision", for OpenAI, Anthropic and Google alike. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): report image file and mask support by model id Upstream image/openai-image-model.ts:61-92 returns supportsFileInputs true for dall-e-2 and its listed gpt-image/chatgpt-image ids, false for dall-e-3, undefined otherwise, and supportsMaskInputs follows it; Azure passes imageInputCapabilities with undefined values (azure-openai-provider.ts:372-376). aimux used the trait default (unknown) for every model. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): unwrap singleton allOf references in JSON schemas Upstream normalize-openai-json-schema.ts:176-246 rewrites a schema whose allOf is exactly [{ $ref }] to a direct $ref, and at the root expands a local #/definitions or #/$defs reference into the root object (keeping the root's other keys). aimux's normalize_json_schema left the allOf in place. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): reject an empty base URL as "baseURL must be a non-empty string." Upstream src/openai-provider.ts:207-215 passes the base URL through provider-utils validateBaseURL, which throws InvalidArgumentError "baseURL must be a non-empty string." for "" and blank values (validate-base-url.test.ts). aimux's validate_base_url said "base URL cannot be empty" and only for "", so a blank value surfaced as an unparsable URL. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): send transcription responseFormat and chunkingStrategy Upstream transcription/openai-transcription-model.ts:196-259 sends responseFormat (default diarized_json for gpt-4o-transcribe-diarize, also without provider options) and chunking_strategy ("auto", the diarize model's default, or a server_vad object serialized with snake_case keys); the options are declared in openai-transcription-model-options.ts:51-70. aimux ignored both options and sent verbose_json or nothing for the diarize model. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): return diarized transcription speakers in provider metadata Upstream transcription/openai-transcription-model.ts:315-353 maps response segments that carry a speaker (openai-transcription-api.ts:34-41) to providerMetadata.openai.segments with text, startSecond, endSecond and speaker, when there is at least one. aimux parsed only text/start/end and returned no provider metadata. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): send the realtime transcription key as a WebSocket subprotocol Upstream transcription/openai-transcription-model.ts:620-652 (getOpenAIRealtimeConnection) takes the bearer token of the last authorization header (case-insensitive name and scheme), offers the subprotocols ["realtime", "openai-insecure-api-key.<token>"] and strips only the authorization header, since OpenAI rejects handshakes that send both auth channels; without a bearer header it offers ["realtime"] and keeps the headers. aimux sent the Authorization header and no subprotocol. The local test servers now select "realtime", as the endpoint does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): take realtime transcription session options from streaming Upstream transcription/openai-transcription-model.ts:580-617 (buildOpenAIRealtimeTranscriptionSession) adds providerOptions.openai.streaming.delay to session.audio.input.transcription.delay and streaming.include to session.include (options declared in openai-transcription-model-options.ts:72-88). aimux ignored streaming and sent the REST include option as session.include. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * fix(openai): warn about REST-only options on realtime transcription streams Upstream transcription/openai-transcription-model.ts:395-410 checks the raw providerOptions.openai for include, prompt, temperature and timestampGranularities and puts an unsupported warning "OpenAI streaming transcription does not support <option>." for each one set into stream-start. aimux sent stream-start with no warnings. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * test(python): answer the realtime subprotocol in the fake WebSocket server OpenAI realtime transcription now offers the `realtime` subprotocol (with the key as `openai-insecure-api-key.<token>`), and a WebSocket client fails when its offer gets no answer, as the realtime API's own server answers it. The fixture server in ops_fixtures.py sent no Sec-WebSocket-Protocol, so test_gil_release's operation_next case failed with "Server sent no subprotocol". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * test(openai): check the reasoning delta item id under rotated item ids The reasoning delta now resolves its item id through output_index (commit "correlate rotated Responses reasoning item ids by output index"), so its test sends rotated ids on the summary events and expects the id the item started with, as asked on #323. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * docs(changelog): list the file search max_num_results type change under Breaking FileSearchArgs::max_num_results changed from Option<f64> to Option<u32>, a change to a public type, so its entry belongs under Breaking rather than Fixed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 * docs(changelog): keep the merged SSE parse error entry where master has it #354 merged the same entry that commit 25 carried. The master merge kept this branch's copy at its old place, so the diff against master moved it; it now sits where master has it and the diff no longer touches it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5 --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Generated by Claude Code Generated by Claude Code |
Summary
Fixes the 3 behaviour differences in the OpenAI code that Azure reuses, found while porting the upstream Azure tests. Each difference is its own commit (listed below) and adds a test that fails on master and a CHANGELOG
Fixedentry. They were separate PRs (#321, #322 and this one). To keep one fix PR per vendor, the others are closed and this PR carries all of them. The Azure test PR (#324) stacks on this branch. Part of the #276 list.cc72496c595eedc25d54de301. OpenAI file search sends max_num_results as an integer
Before: the typed OpenAI file search factory (
openai::tools::file_search, andprovider.tools.file_searchon OpenAI and Azure) tookmax_num_resultsas anf64, somaxNumResults: 5went out on the wire as"max_num_results": 5.0.After: as in
@ai-sdk/openai4.0.83, it goes out as5.FileSearchArgs::max_num_resultsis anOption<u32>;partial_imagesandoutput_compressionin the same file are integers already.Found while porting the upstream Azure tests (cases
responses › doGenerate › file search tool › … › should send request body with toolandshould send include provider option for file search results).How it works today
What this PR changes
Tests (
tests/openai_upstream_e2e_test.rs):file_search_sends_max_num_results_as_an_integerdeserializes{"vectorStoreIds":["vs_1"],"maxNumResults":5}intoFileSearchArgsand checks the request body; on master it fails withleft: Number(5.0),right: Number(5). The upstream cases are ported in the Azure test PR.Docs: CHANGELOG
Fixed.Note: The field type change is source-breaking for Rust code that wrote
Some(5.0); nothing in the workspace or the bindings constructsFileSearchArgs, and JSON callers that send5are unchanged.2. OpenAI Responses stream reports a null response id
Before: an OpenAI or Azure Responses stream that never received
response.createdfinished withproviderMetadata.<key>.responseId: "".After: as in
@ai-sdk/openai4.0.83, it finishes withresponseId: null. The generate path already wrotenullfor a missing id.Found while porting the upstream Azure tests (case
responses › doStream › should handle file_citation annotations without optional fields in streaming).How it works today
What this PR changes
Tests (
tests/openai_upstream_e2e_test.rs):a_stream_without_response_created_finishes_with_a_null_response_idstreams onlyresponse.completed; on master it fails withleft: String(""),right: Null. The upstream case is ported in the Azure test PR.Docs: CHANGELOG
Fixed.Note: Nothing in the workspace reads
responseIdback from the finish metadata.3. OpenAI Responses reasoning deltas carry the item id
Before: OpenAI and Azure Responses reasoning deltas had no provider metadata, while their reasoning start and end parts carried
{ <key>: { itemId, reasoningEncryptedContent } }.After: as in
@ai-sdk/openai4.0.83, every reasoning delta carries{ <key>: { itemId } }.Found while porting the upstream Azure tests (case
responses › doStream › reasoning › should stream with reasoning encrypted content include reasoning-delta part, whose snapshot has the item id on all 89 deltas).How it works today
What this PR changes
Tests (
tests/openai_upstream_e2e_test.rs):reasoning_deltas_carry_the_item_idstreams one reasoning summary delta; on master it fails withleft: Null,right: {"openai":{"itemId":"rs_1"}}. The upstream case is ported in the Azure test PR.Docs: CHANGELOG
Fixed.Note: Upstream resolves the item id through
resolveOutputItemId(item_id, output_index); aimux's reasoning start and end parts useitem_iddirectly, and this PR does the same for deltas.Type of change
Checklist
cargo +1.99 fmt -p aimux-providers -- --checkpassescargo +1.99 clippy -p aimux-providers --lib --tests -- -D warningspassescargo +1.99 test -p aimux-providers: 129 test binaries, all passscripts/check_provider_boundaries.shpassesNotes for reviewers
🤖 Generated with Claude Code
https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5