Skip to content

Add testing + fixes for anthropic => responses SDK streaming path - #400

Merged
Erin McNulty (erin2722) merged 13 commits into
mainfrom
bt-6217-anthropic-responses-streaming
Jul 30, 2026
Merged

Add testing + fixes for anthropic => responses SDK streaming path#400
Erin McNulty (erin2722) merged 13 commits into
mainfrom
bt-6217-anthropic-responses-streaming

Conversation

@erin2722

@erin2722 Erin McNulty (erin2722) commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Erin McNulty (erin2722) and others added 2 commits July 29, 2026 16:55
…reams

Providers with supports_streaming == false have their full response wrapped as a
single chunk (single_bytes_stream) and fake-streamed. complete_stream_internal
gated allow_full_response_fallback on spec.supports_streaming, which is false for
exactly those providers, so StreamTransformSession could not detect the full
response as a stream and passed the raw provider JSON through instead of emitting
SSE events (BT-6217; Azure Foundry anthropic models returned an empty streamText
result). Always enable the fallback -- the native-streaming Vertex full-response
edge relies on it too.

Adds transform_stream_synthesizes_responses_stream_from_full_anthropic_response,
which asserts a full Anthropic response is synthesized into a well-formed
Responses stream (output_item.added -> output_text.delta w/ item_id ->
output_item.done) rather than passed through raw.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 23f0c9a99f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment thread crates/lingua/src/processing/stream.rs Outdated
output_index: Option<u32>,
text: String,
) {
let item = responses_message_item(item_id, "completed", Some(&text));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve incomplete status for truncated message items

When a source ends because of a token limit, the terminal event is response.incomplete, but this newly synthesized message is always marked completed. Native captured Responses streams mark both the output item and the corresponding entry in response.output as incomplete in this scenario (for example, payloads/snapshots/multimodalRequest/responses/response-streaming.json), so consumers can otherwise accept truncated text as a complete message. Derive the item status from the finish reason or terminal event.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment thread crates/lingua/src/processing/stream.rs Outdated
Comment on lines +1151 to +1154
let Some(obj) = event.as_object_mut() else {
continue;
};
let output_index = obj.get("output_index").and_then(Value::as_u64).unwrap_or(0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Parse Responses events through typed views

When an event has a missing, non-numeric, or out-of-range output_index, this raw map access silently substitutes zero and correlates the delta with msg_0 instead of returning an explicit conversion error. Deserialize the generated Responses events into a typed event enum/view before inspecting or modifying their semantic fields, as required for provider-boundary conversion code.

AGENTS.md reference: AGENTS.md:L21-L24

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Erin McNulty (erin2722) and others added 2 commits July 29, 2026 20:52
Drop the verbose explanatory comment blocks added with the BT-6217 fix,
keeping only single-sentence comments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The synthesized Responses message item was hardcoded to "completed", so a
truncated turn emitted response.incomplete alongside an item (and a
response.output entry) marked completed. Native captured Responses streams mark
both incomplete in this case.

Also stamp text deltas with the item id already known by the caller instead of
re-reading output_index out of the generated JSON, which removed a silent
unwrap_or(0) fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 22993740b5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment thread crates/lingua/src/processing/stream.rs Outdated
Replaces the hardcoded `allow_full_response_fallback = true` with
`transform_provider_stream`, which builds the session via
`StreamTransformSession::new` (the library default). The router no longer
invents a fallback policy at the call site.

`transform_stream` and `transform_stream_with_capture` were private to the crate
and had no remaining callers, so they are removed; the explicit-flag API stays
available on `StreamTransformSession::with_full_response_fallback`, which lingua
covers directly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 97950a2c19

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment thread crates/lingua/src/processing/stream.rs
Comment thread crates/lingua/src/processing/stream.rs Outdated
Tool calls emitted output_item.added plus argument deltas but never
function_call_arguments.done or output_item.done, and the function_call item
carried no id. The AI SDK emits its tool-call on output_item.done, so tool calls
never surfaced to consumers -- the same defect as the text envelope, which the
earlier fix only covered for text.

Open tool items are now tracked per output index and closed at finish, ordered
alongside the message item, and included in response.completed.output.

Adds streaming coverage to transforms-ai-sdk.test.ts, which drives lingua's
transformed stream through the real AI SDK. A malformed Responses stream does
not throw -- content is silently dropped -- so the assertions check that text or
tool calls are actually surfaced. Reverting the stream synthesis fixes turns all
9 streaming cases red.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 91e8140758

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment thread crates/lingua/src/processing/stream.rs
Comment thread crates/lingua/src/processing/stream.rs Outdated
@erin2722 Erin McNulty (erin2722) changed the title Bt 6217 anthropic responses streaming Add testing + fixes for anthropic => responses SDK streaming path Jul 30, 2026
requires_json_response: _,
strategy: _,
} = request.inner;
let allow_full_response_fallback = spec.supports_streaming;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to have been erroneously introduced by me when fixing the "vertex will report streaming but respond with a full repsonse" bug-- this was overly perscriptive for other streaming paths. vertex behavior still works here.

}
});
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good call on adding this, it caught one additional bug and I confirmed it would've caught the other issues solved in this PR

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c796088d08

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment thread crates/lingua/src/processing/stream.rs Outdated
Comment on lines +84 to +86
#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
enum ResponsesToolStreamEvent {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reuse the canonical Responses event views

These enums duplicate provider-wire event shapes already modeled by ResponsesOutputItemAddedEvent, ResponsesFunctionCallArgumentsDeltaEvent, and ResponsesCustomToolCallInputDeltaEvent in responses_adapter.rs. If that adapter's emitted schema changes, this second definition can drift and make valid synthesized calls fail inside record_responses_tool_items; expose or extend the canonical views—or keep this state recording in the adapter—instead of defining another wire enum here.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, fixed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cdc601b302

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment thread crates/lingua/src/processing/stream.rs Outdated
Comment thread crates/lingua/src/processing/stream.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 54fba30cc0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment on lines +1030 to +1032
let state = responses_output_index_states
.entry(choice_index)
.or_default();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Finalize tools across Anthropic block indexes

When Anthropic emits parallel tool_use blocks, its adapter assigns each UniversalStreamChoice the content-block index (anthropic/adapter.rs lines 1120-1126), while the terminal message_delta always uses choice index 0 (lines 1024-1027). Keying the newly added tool_items storage by that choice index means calls from blocks 1 and above are never taken when the terminal event arrives, so they receive no response.output_item.done and disappear from response.completed.output. Fresh evidence beyond the earlier terminal-output comment is that tool accumulation is now explicitly nested inside these per-choice states; tool items should instead be finalized response-wide.

Useful? React with 👍 / 👎.

Comment on lines +1078 to +1080
let finish_item_status = match finish_reason.as_deref() {
Some("length") => "incomplete",
_ => "completed",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Treat content-filter finishes as incomplete

When a Bedrock guardrail, Google safety stop, or Anthropic refusal is normalized to content_filter, this match labels every newly synthesized message or tool item as completed. The repository's canonical FinishReason::is_incomplete explicitly includes ContentFilter, so consumers can otherwise accept filtered or partial content as successfully complete; derive the status from that canonical predicate and apply the same correction to the terminal Responses event.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6d4e2ce0fa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

output_format,
allow_full_response_fallback,
),
session: lingua::StreamTransformSession::new(output_format),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve all output items when fake-streaming Responses

When a model with supports_streaming == false returns a complete Responses payload, enabling full-response fallback here converts its output into one UniversalStreamChoice per item, but Responses stream synthesis consumes only chunk.choices.first(). Typical Responses payloads contain a reasoning item followed by the assistant message, so the synthesized stream drops the actual text and tool items—often emitting only an empty terminal response. Expand every choice or merge the full response's output items before stream synthesis.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 339d4d2666

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment on lines +1202 to +1204
let mut entries: Vec<(u32, Value, Option<Value>)> = Vec::new();

if let Some((output_index, item_id, text)) = message {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Retain reasoning items in terminal output

When synthesizing a Responses stream for a turn containing reasoning, the adapter emits response.reasoning_summary_text.delta, but this completion list is populated only with the message and tool items. Consequently no reasoning response.output_item.done is emitted and the terminal response.output omits the reasoning item; this is especially visible when fake-streaming a full Responses payload containing an rs_* item followed by text. Track and finalize reasoning output items alongside messages and tools.

AGENTS.md reference: AGENTS.md:L11-L11

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c9f0840e7c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

if let Some(content) = delta.content {
text.push_str(&content);
}
merged.reasoning.extend(delta.reasoning);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve separate reasoning output items

When fake-streaming a full Responses payload containing multiple reasoning items, this folds every item into one delta, after which Responses synthesis assigns them all the single identity rs_0; empty-summary items disappear entirely. Fresh evidence beyond the earlier full-output fix is payloads/snapshots/toolCallRequest/responses/followup-response.json lines 19-44, which contains five distinct reasoning items that this path cannot reproduce, so the terminal response.output loses their boundaries and IDs. Preserve each full-response output item rather than merging all reasoning vectors.

AGENTS.md reference: AGENTS.md:L11-L11

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that is fine for this path

@erin2722
Erin McNulty (erin2722) merged commit 1ac4b56 into main Jul 30, 2026
31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants