diff --git a/CHANGELOG.md b/CHANGELOG.md index 053c5c30..b254ec70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,15 @@ - Malformed fixture `citations` or `files` (not an array, a missing or mistyped field, a non-integer offset, an offset outside the text, or citations on a turn with no text: tool calls only, `blocks` with no text block, or empty content) answer a 500 before any content is written: `aimock_invalid_fixture_citation` or `aimock_invalid_fixture_file`. A non-`image/*` file on the OpenAI Responses API answers a 500 with `aimock_unsupported_generated_file`. This is the same on every wire that reads these keys, with or without a misbehavior fault: the fixture is checked on its original turn before any fault applies (only an invalid misbehavior config answers its own 400 or 501 first). A valid fixture under a fault that changes the reply text is served without its `citations`. Only Chat Completions, Anthropic Messages, Gemini and OpenAI Responses read these keys; other wires ignore them. - Fixture load validation warns about malformed `citations` and `files`, with the same problem description the 500 carries (the 500 message adds an `aimock: invalid fixture` prefix; the warning adds which 500 the requests answer). The fixture still loads. - `InvalidFixtureOutputError` (the error behind `aimock_invalid_fixture_citation` and `aimock_invalid_fixture_file`) is exported from `@copilotkit/aimock`. +- Opt-in fixture `nonStreamingTimingAllProviders: true` delays non-streaming replies on OpenAI chat, Anthropic, Responses, Gemini/Vertex, Gemini Interactions, Cohere, Bedrock invoke/converse and Ollama; `nonStreamingTiming` is unchanged (OpenAI chat only); error replies are never delayed. On the OpenAI Responses API the wait also counts the stream frames that fixture `citations` and generated `files` add ### Fixed - Misbehavior faults on Chat Completions, Anthropic Messages, Gemini and the OpenAI Responses API (HTTP and WebSocket): a fixture the wire rejects (generated `files` on Chat Completions or Anthropic, a non-`image/*` file on Responses, malformed `citations` or `files`) is never faulted. It takes the normal path, is journaled as a 500 that keeps its fixture, and consumes no fault `times` budget or seed ordinal; `aimock_misbehavior_total` does not count it. A non-stream Chat Completions fault is journaled before its timing wait. - Journal accuracy: a client that hangs up during the timing wait of any non-stream Chat Completions reply (faulted or not) is journaled as interrupted, not as served. +- Client disconnects stop delivery (no more chunks or waits) on OpenAI chat, Anthropic, Responses, Gemini/Vertex, Gemini Interactions, Cohere, Bedrock, Ollama, transcription streams and the Responses, Realtime and Gemini Live WebSockets +- Client disconnects on those surfaces are journaled as `interrupted` with `interruptReason: "client aborted"` (HTTP `status: 0` when nothing was sent); aimock-caused teardowns record `"disconnect"`. Journal snapshots of hung-up requests now carry these fields +- WebSocket: a client TCP FIN without a close frame now closes the connection at once (1006) and ends the turn - Docker image: the base image is pinned to `node:22.23.4-alpine` by digest. Node 22.23.4 fixes CVE-2026-111497 (Brotli decompression memory bug); the 1.46.0 image shipped Node 22.23.3. ## [1.46.0] - 2026-10-20 diff --git a/docs/aws-bedrock/index.html b/docs/aws-bedrock/index.html index c155f91f..4b84bd61 100644 --- a/docs/aws-bedrock/index.html +++ b/docs/aws-bedrock/index.html @@ -107,6 +107,16 @@
+ If the client hangs up before the reply is complete, aimock stops delivery and journals
+ the request with interruptReason: "client aborted" (see
+ Client Disconnects). To delay
+ InvokeModel and Converse replies, set
+ nonStreamingTimingAllProviders: true with a streamingProfile or
+ recordedTimings (see
+ All Providers).
+
Bedrock Claude requests use the Anthropic Messages format. The diff --git a/docs/chat-completions/index.html b/docs/chat-completions/index.html index ac7f3aaa..d337fa28 100644 --- a/docs/chat-completions/index.html +++ b/docs/chat-completions/index.html @@ -73,6 +73,16 @@
+ If the client hangs up before the reply is complete, aimock stops delivery and journals
+ the request with interruptReason: "client aborted" (see
+ Client Disconnects). To delay
+ stream: false replies, set nonStreamingTiming: true or
+ nonStreamingTimingAllProviders: true with a streamingProfile or
+ recordedTimings (see
+ Non-Streaming Responses).
+
Using the programmatic API with vitest, register a fixture and assert on the response. diff --git a/docs/cohere/index.html b/docs/cohere/index.html index 5c4ae1b4..ec1c4f76 100644 --- a/docs/cohere/index.html +++ b/docs/cohere/index.html @@ -73,6 +73,16 @@
+ If the client hangs up before the reply is complete, aimock stops delivery and journals
+ the request with interruptReason: "client aborted" (see
+ Client Disconnects). To delay
+ stream: false replies, set
+ nonStreamingTimingAllProviders: true with a streamingProfile or
+ recordedTimings (see
+ All Providers).
+
response.interrupted: true and response.interruptReason
response.interruptReason: "client aborted"
+ A client that disconnects is recorded the same way, with
+ response.interruptReason: "client aborted" (see
+ Client Disconnects)
+ + When the client hangs up before the reply is complete, aimock stops delivery. It sends no + more chunks and skips the remaining waits. This applies to these endpoints, streaming and + non-streaming: +
+/api/chat and /api/generate+ On WebSocket, a client that closes its TCP connection without sending a close frame is + also a disconnect. aimock closes the connection at once (code 1006, "Connection lost"), + stops the turn and journals it. +
+The journal entry records how the reply ended:
+response.interrupted: true and
+ response.interruptReason: "client aborted" when the client left.
+ response.status: 0 when no byte of the reply reached the client.
+ Otherwise the status stays the one aimock sent.
+ response.interruptReason: "disconnect" when aimock tore the response down
+ itself, for example with the chaos disconnect action or a server-side
+ destroy. This is not blamed on the client.
+ truncateAfterChunks,
+ disconnectAfterMs) keep their own reasons.
stream: false OpenAI Chat Completions replies by the
fixture's streamingProfile or recordedTimings (default
- false). See
+ false). Other providers ignore it (use
+ nonStreamingTimingAllProviders). See
Non-Streaming Responses
stream: false replies the same way on OpenAI Chat Completions,
+ Anthropic Messages, OpenAI Responses, Gemini, Vertex AI, Gemini Interactions,
+ Cohere, Bedrock InvokeModel and Converse, and Ollama (default false).
+ Needs streamingProfile or recordedTimings. Error replies
+ and audio transcription are not delayed. See
+ All Providers
+
+ If the client hangs up before the reply is complete, aimock stops delivery and journals
+ the request with interruptReason: "client aborted" (see
+ Client Disconnects). To delay
+ stream: false replies, set
+ nonStreamingTimingAllProviders: true with a streamingProfile or
+ recordedTimings (see
+ All Providers).
+
Set up a minimal mock that responds to a Gemini Interactions request:
diff --git a/docs/gemini/index.html b/docs/gemini/index.html index 0ec1e515..c5ed266b 100644 --- a/docs/gemini/index.html +++ b/docs/gemini/index.html @@ -98,6 +98,16 @@
+ If the client hangs up before the reply is complete, aimock stops delivery and journals
+ the request with interruptReason: "client aborted" (see
+ Client Disconnects). Gemini Live records
+ the turn the same way. To delay generateContent replies, set
+ nonStreamingTimingAllProviders: true with a streamingProfile or
+ recordedTimings (see
+ All Providers).
+
The native catalog contains Google IDs and unrecognized IDs from fixtures, in first-seen diff --git a/docs/ollama/index.html b/docs/ollama/index.html index c8037263..ae3b5089 100644 --- a/docs/ollama/index.html +++ b/docs/ollama/index.html @@ -93,6 +93,16 @@
+ If the client hangs up before the reply is complete, aimock stops delivery and journals
+ the request with interruptReason: "client aborted" (see
+ Client Disconnects). To delay
+ "stream": false replies, set
+ nonStreamingTimingAllProviders: true with a streamingProfile or
+ recordedTimings (see
+ All Providers).
+
Use the Ollama SDK's embed({ model, input }) with a string or array of
diff --git a/docs/openrouter-chat/index.html b/docs/openrouter-chat/index.html
index 04d9fa09..645e414f 100644
--- a/docs/openrouter-chat/index.html
+++ b/docs/openrouter-chat/index.html
@@ -107,6 +107,16 @@
+ If the client hangs up before the reply is complete, aimock stops delivery and journals
+ the request with interruptReason: "client aborted" (see
+ Client Disconnects). To delay
+ stream: false replies, set nonStreamingTiming: true or
+ nonStreamingTimingAllProviders: true with a streamingProfile or
+ recordedTimings (see
+ Non-Streaming Responses).
+
The dominant real-world integration path is the OpenAI SDK with its diff --git a/docs/responses-api/index.html b/docs/responses-api/index.html index e82005b7..1d68aa42 100644 --- a/docs/responses-api/index.html +++ b/docs/responses-api/index.html @@ -77,6 +77,16 @@
+ If the client hangs up before the reply is complete, aimock stops delivery and journals
+ the request with interruptReason: "client aborted" (see
+ Client Disconnects). The WebSocket
+ transport records the turn the same way. To delay HTTP stream: false replies,
+ set nonStreamingTimingAllProviders: true with a
+ streamingProfile or recordedTimings (see
+ All Providers).
+
nonStreamingTiming has an effect only on OpenAI Chat Completions, and only
when the fixture also sets streamingProfile or recordedTimings.
- Non-streaming responses from other providers are not delayed.
+ Non-streaming responses from other providers are not delayed by it.
+ To delay stream: false replies on other providers too, set
+ nonStreamingTimingAllProviders: true on the fixture. It uses the same formula
+ as nonStreamingTiming: true, and it also needs a
+ streamingProfile or recordedTimings. It applies to these
+ non-streaming endpoints:
+
nonStreamingTiming: true)generateContentstream: false)/api/chat and /api/generate
+ Audio transcription replies are not delayed. nonStreamingTiming is unchanged:
+ it still applies to OpenAI Chat Completions only. Setting
+ nonStreamingTimingAllProviders: false does not turn off
+ nonStreamingTiming: true.
+
{
+ "fixtures": [
+ {
+ "match": { "userMessage": "think carefully" },
+ "response": { "content": "Let me think about this..." },
+ "streamingProfile": { "ttft": 800, "tps": 50 },
+ "nonStreamingTimingAllProviders": true
+ }
+ ]
+}
+ error response) are never delayed. aimock sends
+ them at once, with the fixture status.
+ response.interrupted: true,
+ response.interruptReason: "client aborted" and
+ response.status: 0. See
+ Client Disconnects.
+ latencylatencylatency and --latency are gaps between SSE chunks only. They
never delay a non-streaming response, including the fallback case above. To delay
stream: false replies, set streamingProfile and
- nonStreamingTiming: true (see
+ nonStreamingTiming: true (OpenAI Chat Completions) or
+ nonStreamingTimingAllProviders: true (see
Non-Streaming Responses).
+ If the client hangs up during a streamed transcription (stream=true), aimock
+ stops the stream and journals the request with
+ interruptReason: "client aborted" (see
+ Client Disconnects).
+
Using the programmatic API with vitest, register a fixture and assert on the response. diff --git a/docs/vertex-ai/index.html b/docs/vertex-ai/index.html index 88f2ef95..779b221c 100644 --- a/docs/vertex-ai/index.html +++ b/docs/vertex-ai/index.html @@ -82,6 +82,16 @@
+ If the client hangs up before the reply is complete, aimock stops delivery and journals
+ the request with interruptReason: "client aborted" (see
+ Client Disconnects). To delay
+ generateContent replies, set
+ nonStreamingTimingAllProviders: true with a streamingProfile or
+ recordedTimings (see
+ All Providers).
+
The key difference between consumer Gemini and Vertex AI is the URL routing. Consumer diff --git a/docs/websocket/index.html b/docs/websocket/index.html index d4288ada..fac4f432 100644 --- a/docs/websocket/index.html +++ b/docs/websocket/index.html @@ -88,6 +88,13 @@
+ On OpenAI Responses, OpenAI Realtime and Gemini Live, if the client closes the connection
+ during a reply, aimock stops sending and journals the turn with
+ interruptReason: "client aborted" (see
+ Client Disconnects).
+
GPT-Live uses a primary WebSocket upgrade: GET /v1/live/sessions. Connect to
diff --git a/etc/aimock-a2a.import.api.md b/etc/aimock-a2a.import.api.md
index b2482cfa..7c4306d9 100644
--- a/etc/aimock-a2a.import.api.md
+++ b/etc/aimock-a2a.import.api.md
@@ -335,6 +335,7 @@ interface Fixture {
// (undocumented)
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
nonStreamingTiming?: boolean;
+ nonStreamingTimingAllProviders?: boolean;
openRouterProcessing?: boolean;
// (undocumented)
recordedTimings?: RecordedTimings;
diff --git a/etc/aimock-a2a.require.api.md b/etc/aimock-a2a.require.api.md
index b2482cfa..7c4306d9 100644
--- a/etc/aimock-a2a.require.api.md
+++ b/etc/aimock-a2a.require.api.md
@@ -335,6 +335,7 @@ interface Fixture {
// (undocumented)
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
nonStreamingTiming?: boolean;
+ nonStreamingTimingAllProviders?: boolean;
openRouterProcessing?: boolean;
// (undocumented)
recordedTimings?: RecordedTimings;
diff --git a/etc/aimock-agui.import.api.md b/etc/aimock-agui.import.api.md
index 2d106358..d3a8449b 100644
--- a/etc/aimock-agui.import.api.md
+++ b/etc/aimock-agui.import.api.md
@@ -964,6 +964,7 @@ interface Fixture {
// (undocumented)
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
nonStreamingTiming?: boolean;
+ nonStreamingTimingAllProviders?: boolean;
openRouterProcessing?: boolean;
// (undocumented)
recordedTimings?: RecordedTimings;
diff --git a/etc/aimock-agui.require.api.md b/etc/aimock-agui.require.api.md
index 2d106358..d3a8449b 100644
--- a/etc/aimock-agui.require.api.md
+++ b/etc/aimock-agui.require.api.md
@@ -964,6 +964,7 @@ interface Fixture {
// (undocumented)
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
nonStreamingTiming?: boolean;
+ nonStreamingTimingAllProviders?: boolean;
openRouterProcessing?: boolean;
// (undocumented)
recordedTimings?: RecordedTimings;
diff --git a/etc/aimock-index.import.api.md b/etc/aimock-index.import.api.md
index 5fea0202..242db462 100644
--- a/etc/aimock-index.import.api.md
+++ b/etc/aimock-index.import.api.md
@@ -2105,6 +2105,7 @@ export interface Fixture {
// (undocumented)
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
nonStreamingTiming?: boolean;
+ nonStreamingTimingAllProviders?: boolean;
openRouterProcessing?: boolean;
// (undocumented)
recordedTimings?: RecordedTimings;
@@ -2223,6 +2224,7 @@ export interface FixtureFileEntry {
// (undocumented)
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
nonStreamingTiming?: boolean;
+ nonStreamingTimingAllProviders?: boolean;
openRouterProcessing?: boolean;
// (undocumented)
recordedTimings?: RecordedTimings;
diff --git a/etc/aimock-index.require.api.md b/etc/aimock-index.require.api.md
index 5fea0202..242db462 100644
--- a/etc/aimock-index.require.api.md
+++ b/etc/aimock-index.require.api.md
@@ -2105,6 +2105,7 @@ export interface Fixture {
// (undocumented)
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
nonStreamingTiming?: boolean;
+ nonStreamingTimingAllProviders?: boolean;
openRouterProcessing?: boolean;
// (undocumented)
recordedTimings?: RecordedTimings;
@@ -2223,6 +2224,7 @@ export interface FixtureFileEntry {
// (undocumented)
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
nonStreamingTiming?: boolean;
+ nonStreamingTimingAllProviders?: boolean;
openRouterProcessing?: boolean;
// (undocumented)
recordedTimings?: RecordedTimings;
diff --git a/etc/aimock-jest.import.api.md b/etc/aimock-jest.import.api.md
index 2a553853..add7814e 100644
--- a/etc/aimock-jest.import.api.md
+++ b/etc/aimock-jest.import.api.md
@@ -271,6 +271,7 @@ export interface Fixture {
// (undocumented)
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
nonStreamingTiming?: boolean;
+ nonStreamingTimingAllProviders?: boolean;
openRouterProcessing?: boolean;
// (undocumented)
recordedTimings?: RecordedTimings;
@@ -383,6 +384,7 @@ interface FixtureFileEntry {
// (undocumented)
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
nonStreamingTiming?: boolean;
+ nonStreamingTimingAllProviders?: boolean;
openRouterProcessing?: boolean;
// (undocumented)
recordedTimings?: RecordedTimings;
diff --git a/etc/aimock-jest.require.api.md b/etc/aimock-jest.require.api.md
index 2a553853..add7814e 100644
--- a/etc/aimock-jest.require.api.md
+++ b/etc/aimock-jest.require.api.md
@@ -271,6 +271,7 @@ export interface Fixture {
// (undocumented)
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
nonStreamingTiming?: boolean;
+ nonStreamingTimingAllProviders?: boolean;
openRouterProcessing?: boolean;
// (undocumented)
recordedTimings?: RecordedTimings;
@@ -383,6 +384,7 @@ interface FixtureFileEntry {
// (undocumented)
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
nonStreamingTiming?: boolean;
+ nonStreamingTimingAllProviders?: boolean;
openRouterProcessing?: boolean;
// (undocumented)
recordedTimings?: RecordedTimings;
diff --git a/etc/aimock-mcp.import.api.md b/etc/aimock-mcp.import.api.md
index d821ab97..8d4b6a43 100644
--- a/etc/aimock-mcp.import.api.md
+++ b/etc/aimock-mcp.import.api.md
@@ -203,6 +203,7 @@ interface Fixture {
// (undocumented)
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
nonStreamingTiming?: boolean;
+ nonStreamingTimingAllProviders?: boolean;
openRouterProcessing?: boolean;
// (undocumented)
recordedTimings?: RecordedTimings;
diff --git a/etc/aimock-mcp.require.api.md b/etc/aimock-mcp.require.api.md
index d821ab97..8d4b6a43 100644
--- a/etc/aimock-mcp.require.api.md
+++ b/etc/aimock-mcp.require.api.md
@@ -203,6 +203,7 @@ interface Fixture {
// (undocumented)
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
nonStreamingTiming?: boolean;
+ nonStreamingTimingAllProviders?: boolean;
openRouterProcessing?: boolean;
// (undocumented)
recordedTimings?: RecordedTimings;
diff --git a/etc/aimock-vector.import.api.md b/etc/aimock-vector.import.api.md
index 5fd533c2..1a6ad453 100644
--- a/etc/aimock-vector.import.api.md
+++ b/etc/aimock-vector.import.api.md
@@ -197,6 +197,7 @@ interface Fixture {
// (undocumented)
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
nonStreamingTiming?: boolean;
+ nonStreamingTimingAllProviders?: boolean;
openRouterProcessing?: boolean;
// (undocumented)
recordedTimings?: RecordedTimings;
diff --git a/etc/aimock-vector.require.api.md b/etc/aimock-vector.require.api.md
index 5fd533c2..1a6ad453 100644
--- a/etc/aimock-vector.require.api.md
+++ b/etc/aimock-vector.require.api.md
@@ -197,6 +197,7 @@ interface Fixture {
// (undocumented)
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
nonStreamingTiming?: boolean;
+ nonStreamingTimingAllProviders?: boolean;
openRouterProcessing?: boolean;
// (undocumented)
recordedTimings?: RecordedTimings;
diff --git a/etc/aimock-vitest.import.api.md b/etc/aimock-vitest.import.api.md
index cb667af8..de442b65 100644
--- a/etc/aimock-vitest.import.api.md
+++ b/etc/aimock-vitest.import.api.md
@@ -271,6 +271,7 @@ export interface Fixture {
// (undocumented)
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
nonStreamingTiming?: boolean;
+ nonStreamingTimingAllProviders?: boolean;
openRouterProcessing?: boolean;
// (undocumented)
recordedTimings?: RecordedTimings;
@@ -383,6 +384,7 @@ interface FixtureFileEntry {
// (undocumented)
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
nonStreamingTiming?: boolean;
+ nonStreamingTimingAllProviders?: boolean;
openRouterProcessing?: boolean;
// (undocumented)
recordedTimings?: RecordedTimings;
diff --git a/etc/aimock-vitest.require.api.md b/etc/aimock-vitest.require.api.md
index cb667af8..de442b65 100644
--- a/etc/aimock-vitest.require.api.md
+++ b/etc/aimock-vitest.require.api.md
@@ -271,6 +271,7 @@ export interface Fixture {
// (undocumented)
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
nonStreamingTiming?: boolean;
+ nonStreamingTimingAllProviders?: boolean;
openRouterProcessing?: boolean;
// (undocumented)
recordedTimings?: RecordedTimings;
@@ -383,6 +384,7 @@ interface FixtureFileEntry {
// (undocumented)
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
nonStreamingTiming?: boolean;
+ nonStreamingTimingAllProviders?: boolean;
openRouterProcessing?: boolean;
// (undocumented)
recordedTimings?: RecordedTimings;
diff --git a/skills/write-fixtures/SKILL.md b/skills/write-fixtures/SKILL.md
index 3d1405c2..436e4696 100644
--- a/skills/write-fixtures/SKILL.md
+++ b/skills/write-fixtures/SKILL.md
@@ -666,9 +666,9 @@ Custom tool calls (`customToolCalls` / `customToolCall` blocks in `responsesBloc
2. **`arguments` accepts both objects and strings** — `"arguments": {"key":"value"}` (preferred, auto-stringified) or `"arguments": "{\"key\":\"value\"}"` (legacy). The same applies to `content` fields that contain JSON. The fixture loader detects `typeof === "object"` and calls `JSON.stringify()` automatically.
-3. **Latency is per-chunk, not total** — `latency: 100` means 100ms between each SSE chunk, not 100ms total response time. It never delays a non-streaming (`stream: false`) response, also with `nonStreamingTiming: true`. Similarly, `truncateAfterChunks` and `disconnectAfterMs` are for simulating stream interruptions (added in v1.3.0).
+3. **Latency is per-chunk, not total** — `latency: 100` means 100ms between each SSE chunk, not 100ms total response time. It never delays a non-streaming (`stream: false`) response, also with `nonStreamingTiming: true` or `nonStreamingTimingAllProviders: true`. Similarly, `truncateAfterChunks` and `disconnectAfterMs` are for simulating stream interruptions (added in v1.3.0).
-4. **`streamingProfile` takes precedence over `latency`** — when both are set on a fixture, `streamingProfile` controls timing. Use one or the other. By default, `streamingProfile` and `recordedTimings` delay only streamed replies: a `stream: false` reply returns immediately, also for recorded fixtures. Set `nonStreamingTiming: true` on the fixture to also delay `stream: false` OpenAI Chat Completions replies (text, tool calls, combined content and tool calls, and `blocks`) by the sum of the per-chunk delays a streamed reply at the same `chunkSize` would get. Other providers' non-streaming replies are not delayed.
+4. **`streamingProfile` takes precedence over `latency`** — when both are set on a fixture, `streamingProfile` controls timing. Use one or the other. By default, `streamingProfile` and `recordedTimings` delay only streamed replies: a `stream: false` reply returns immediately, also for recorded fixtures. Set `nonStreamingTiming: true` on the fixture to also delay `stream: false` OpenAI Chat Completions replies (text, tool calls, combined content and tool calls, and `blocks`) by the sum of the per-chunk delays a streamed reply at the same `chunkSize` would get. `nonStreamingTiming` does not delay other providers. To delay `stream: false` replies with the same formula on OpenAI Chat Completions, Anthropic Messages, OpenAI Responses, Gemini and Vertex AI `generateContent`, Gemini Interactions (`stream: false`), Cohere v2 chat, Bedrock InvokeModel and Converse, and Ollama `/api/chat` and `/api/generate`, set `nonStreamingTimingAllProviders: true` instead. Audio transcription replies are not delayed. Error replies are never delayed. If the client disconnects during the wait, nothing is written and the journal entry gets `interrupted: true`, `interruptReason: "client aborted"` and `status: 0`.
5. **Tool result messages don't change the user message** — after a tool call, the client sends the same conversation + tool result. Matching on `userMessage` will hit the SAME fixture again → infinite loop. Always use `predicate` checking `role === "tool"` for tool results. Note: a whole-conversation `role === "tool"` check (e.g. `req.messages.some((m) => m.role === "tool")`) diverges from `hasToolResult`'s current-turn scoping in multi-turn flows — the built-in `hasToolResult` matcher only looks after the last user message, so a later turn whose history carries an earlier tool result still reads `false`.
diff --git a/src/__tests__/__snapshots__/frame-codec-golden/aws-eventstream.json b/src/__tests__/__snapshots__/frame-codec-golden/aws-eventstream.json
new file mode 100644
index 00000000..ed54e8a1
--- /dev/null
+++ b/src/__tests__/__snapshots__/frame-codec-golden/aws-eventstream.json
@@ -0,0 +1,523 @@
+{
+ "text": {
+ "log": [
+ {
+ "op": "setHeader",
+ "name": "Content-Type",
+ "value": "application/vnd.amazon.eventstream"
+ },
+ {
+ "op": "setHeader",
+ "name": "Transfer-Encoding",
+ "value": "chunked"
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 118,
+ "headersLength": 82,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"role\":\"assistant\"}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 152,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"Hello \"}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 151,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"world\"}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 125,
+ "headersLength": 86,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 122,
+ "headersLength": 81,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"stopReason\":\"end_turn\"}"
+ }
+ ]
+ },
+ {
+ "op": "end"
+ },
+ {
+ "op": "return",
+ "completed": true
+ }
+ ],
+ "journal": null
+ },
+ "tool": {
+ "log": [
+ {
+ "op": "setHeader",
+ "name": "Content-Type",
+ "value": "application/vnd.amazon.eventstream"
+ },
+ {
+ "op": "setHeader",
+ "name": "Transfer-Encoding",
+ "value": "chunked"
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 118,
+ "headersLength": 82,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"role\":\"assistant\"}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 152,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"Hello \"}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 151,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"world\"}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 125,
+ "headersLength": 86,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 122,
+ "headersLength": 81,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"stopReason\":\"end_turn\"}"
+ }
+ ]
+ },
+ {
+ "op": "end"
+ },
+ {
+ "op": "return",
+ "completed": true
+ }
+ ],
+ "journal": null
+ },
+ "truncate2": {
+ "log": [
+ {
+ "op": "setHeader",
+ "name": "Content-Type",
+ "value": "application/vnd.amazon.eventstream"
+ },
+ {
+ "op": "setHeader",
+ "name": "Transfer-Encoding",
+ "value": "chunked"
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 118,
+ "headersLength": 82,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"role\":\"assistant\"}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 152,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"Hello \"}}"
+ }
+ ]
+ },
+ {
+ "op": "return",
+ "completed": false
+ }
+ ],
+ "journal": null
+ },
+ "delay-text": {
+ "log": [
+ {
+ "op": "setHeader",
+ "name": "Content-Type",
+ "value": "application/vnd.amazon.eventstream"
+ },
+ {
+ "op": "setHeader",
+ "name": "Transfer-Encoding",
+ "value": "chunked"
+ },
+ {
+ "op": "timer",
+ "ms": 7
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 118,
+ "headersLength": 82,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"role\":\"assistant\"}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 11
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 152,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"Hello \"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 13
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 151,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"world\"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 17
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 125,
+ "headersLength": 86,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 19
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 122,
+ "headersLength": 81,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"stopReason\":\"end_turn\"}"
+ }
+ ]
+ },
+ {
+ "op": "end"
+ },
+ {
+ "op": "return",
+ "completed": true
+ }
+ ],
+ "journal": null
+ },
+ "delay-replay2": {
+ "log": [
+ {
+ "op": "setHeader",
+ "name": "Content-Type",
+ "value": "application/vnd.amazon.eventstream"
+ },
+ {
+ "op": "setHeader",
+ "name": "Transfer-Encoding",
+ "value": "chunked"
+ },
+ {
+ "op": "timer",
+ "ms": 3.5
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 118,
+ "headersLength": 82,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"role\":\"assistant\"}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 5.5
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 152,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"Hello \"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 6.5
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 151,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"world\"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 8.5
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 125,
+ "headersLength": 86,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 9.5
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 122,
+ "headersLength": 81,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"stopReason\":\"end_turn\"}"
+ }
+ ]
+ },
+ {
+ "op": "end"
+ },
+ {
+ "op": "return",
+ "completed": true
+ }
+ ],
+ "journal": null
+ }
+}
diff --git a/src/__tests__/__snapshots__/frame-codec-golden/bedrock-converse-eventstream.json b/src/__tests__/__snapshots__/frame-codec-golden/bedrock-converse-eventstream.json
new file mode 100644
index 00000000..29b08275
--- /dev/null
+++ b/src/__tests__/__snapshots__/frame-codec-golden/bedrock-converse-eventstream.json
@@ -0,0 +1,1946 @@
+{
+ "text": {
+ "log": [
+ {
+ "op": "setHeader",
+ "name": "Content-Type",
+ "value": "application/vnd.amazon.eventstream"
+ },
+ {
+ "op": "setHeader",
+ "name": "Transfer-Encoding",
+ "value": "chunked"
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 118,
+ "headersLength": 82,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"role\":\"assistant\"}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 137,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"start\":{}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"Hello g\"}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"olden w\"}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"orld, t\"}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"his is \"}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"a chara\"}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"cteriza\"}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"tion ru\"}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 148,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"n.\"}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 125,
+ "headersLength": 86,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 122,
+ "headersLength": 81,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"stopReason\":\"end_turn\"}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 180,
+ "headersLength": 78,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "metadata",
+ ":message-type": "event"
+ },
+ "payload": "{\"usage\":{\"inputTokens\":0,\"outputTokens\":0,\"totalTokens\":0},\"metrics\":{\"latencyMs\":0}}"
+ }
+ ]
+ },
+ {
+ "op": "end"
+ }
+ ],
+ "journal": {
+ "status": 200,
+ "interrupted": null,
+ "interruptReason": null
+ }
+ },
+ "tool": {
+ "log": [
+ {
+ "op": "setHeader",
+ "name": "Content-Type",
+ "value": "application/vnd.amazon.eventstream"
+ },
+ {
+ "op": "setHeader",
+ "name": "Transfer-Encoding",
+ "value": "chunked"
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 118,
+ "headersLength": 82,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"role\":\"assistant\"}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 191,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"start\":{\"toolUse\":{\"toolUseId\":\"call_g1\",\"name\":\"get_weather\"}}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 168,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"toolUse\":{\"input\":\"{\\\"city\\\"\"}}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 168,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"toolUse\":{\"input\":\":\\\"NYC\\\"}\"}}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 125,
+ "headersLength": 86,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 122,
+ "headersLength": 81,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"stopReason\":\"tool_use\"}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 180,
+ "headersLength": 78,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "metadata",
+ ":message-type": "event"
+ },
+ "payload": "{\"usage\":{\"inputTokens\":0,\"outputTokens\":0,\"totalTokens\":0},\"metrics\":{\"latencyMs\":0}}"
+ }
+ ]
+ },
+ {
+ "op": "end"
+ }
+ ],
+ "journal": {
+ "status": 200,
+ "interrupted": null,
+ "interruptReason": null
+ }
+ },
+ "combined": {
+ "log": [
+ {
+ "op": "setHeader",
+ "name": "Content-Type",
+ "value": "application/vnd.amazon.eventstream"
+ },
+ {
+ "op": "setHeader",
+ "name": "Transfer-Encoding",
+ "value": "chunked"
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 118,
+ "headersLength": 82,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"role\":\"assistant\"}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 137,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"start\":{}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"Checkin\"}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 152,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"g now.\"}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 125,
+ "headersLength": 86,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 191,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":1,\"start\":{\"toolUse\":{\"toolUseId\":\"call_g2\",\"name\":\"get_weather\"}}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 168,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":1,\"delta\":{\"toolUse\":{\"input\":\"{\\\"city\\\"\"}}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 167,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":1,\"delta\":{\"toolUse\":{\"input\":\":\\\"SF\\\"}\"}}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 125,
+ "headersLength": 86,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":1}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 122,
+ "headersLength": 81,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"stopReason\":\"tool_use\"}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 180,
+ "headersLength": 78,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "metadata",
+ ":message-type": "event"
+ },
+ "payload": "{\"usage\":{\"inputTokens\":0,\"outputTokens\":0,\"totalTokens\":0},\"metrics\":{\"latencyMs\":0}}"
+ }
+ ]
+ },
+ {
+ "op": "end"
+ }
+ ],
+ "journal": {
+ "status": 200,
+ "interrupted": null,
+ "interruptReason": null
+ }
+ },
+ "blocks": {
+ "log": [
+ {
+ "op": "setHeader",
+ "name": "Content-Type",
+ "value": "application/vnd.amazon.eventstream"
+ },
+ {
+ "op": "setHeader",
+ "name": "Transfer-Encoding",
+ "value": "chunked"
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 118,
+ "headersLength": 82,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"role\":\"assistant\"}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 137,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"start\":{}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"Lead te\"}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 149,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"xt.\"}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 125,
+ "headersLength": 86,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 188,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":1,\"start\":{\"toolUse\":{\"toolUseId\":\"call_g3\",\"name\":\"get_time\"}}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 161,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":1,\"delta\":{\"toolUse\":{\"input\":\"{}\"}}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 125,
+ "headersLength": 86,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":1}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 137,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":2,\"start\":{}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":2,\"delta\":{\"text\":\"Tail te\"}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 149,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":2,\"delta\":{\"text\":\"xt.\"}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 125,
+ "headersLength": 86,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":2}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 122,
+ "headersLength": 81,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"stopReason\":\"tool_use\"}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 180,
+ "headersLength": 78,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "metadata",
+ ":message-type": "event"
+ },
+ "payload": "{\"usage\":{\"inputTokens\":0,\"outputTokens\":0,\"totalTokens\":0},\"metrics\":{\"latencyMs\":0}}"
+ }
+ ]
+ },
+ {
+ "op": "end"
+ }
+ ],
+ "journal": {
+ "status": 200,
+ "interrupted": null,
+ "interruptReason": null
+ }
+ },
+ "misbehavior": {
+ "log": [
+ {
+ "op": "setHeader",
+ "name": "Content-Type",
+ "value": "application/vnd.amazon.eventstream"
+ },
+ {
+ "op": "setHeader",
+ "name": "Transfer-Encoding",
+ "value": "chunked"
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 118,
+ "headersLength": 82,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"role\":\"assistant\"}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 191,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"start\":{\"toolUse\":{\"toolUseId\":\"call_m1\",\"name\":\"get_weather\"}}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 168,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"toolUse\":{\"input\":\"{\\\"city\\\"\"}}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 125,
+ "headersLength": 86,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 188,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":1,\"start\":{\"toolUse\":{\"toolUseId\":\"call_m2\",\"name\":\"get_time\"}}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 168,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":1,\"delta\":{\"toolUse\":{\"input\":\"{\\\"zone\\\"\"}}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 168,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":1,\"delta\":{\"toolUse\":{\"input\":\":\\\"UTC\\\"}\"}}}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 125,
+ "headersLength": 86,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":1}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 122,
+ "headersLength": 81,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"stopReason\":\"tool_use\"}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 182,
+ "headersLength": 78,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "metadata",
+ ":message-type": "event"
+ },
+ "payload": "{\"usage\":{\"inputTokens\":3,\"outputTokens\":10,\"totalTokens\":13},\"metrics\":{\"latencyMs\":0}}"
+ }
+ ]
+ },
+ {
+ "op": "end"
+ }
+ ],
+ "journal": {
+ "status": 200,
+ "interrupted": null,
+ "interruptReason": null
+ }
+ },
+ "truncate2": {
+ "log": [
+ {
+ "op": "setHeader",
+ "name": "Content-Type",
+ "value": "application/vnd.amazon.eventstream"
+ },
+ {
+ "op": "setHeader",
+ "name": "Transfer-Encoding",
+ "value": "chunked"
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 118,
+ "headersLength": 82,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"role\":\"assistant\"}"
+ }
+ ]
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 137,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"start\":{}}"
+ }
+ ]
+ },
+ {
+ "op": "destroy"
+ }
+ ],
+ "journal": {
+ "status": 200,
+ "interrupted": true,
+ "interruptReason": "truncateAfterChunks"
+ }
+ },
+ "delay-text": {
+ "log": [
+ {
+ "op": "setHeader",
+ "name": "Content-Type",
+ "value": "application/vnd.amazon.eventstream"
+ },
+ {
+ "op": "setHeader",
+ "name": "Transfer-Encoding",
+ "value": "chunked"
+ },
+ {
+ "op": "timer",
+ "ms": 7
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 118,
+ "headersLength": 82,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"role\":\"assistant\"}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 11
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 137,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"start\":{}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 13
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"Hello g\"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 17
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"olden w\"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 19
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"orld, t\"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 23
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"his is \"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 29
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"a chara\"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 31
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"cteriza\"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 37
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"tion ru\"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 41
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 148,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"n.\"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 43
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 125,
+ "headersLength": 86,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 47
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 122,
+ "headersLength": 81,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"stopReason\":\"end_turn\"}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 53
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 180,
+ "headersLength": 78,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "metadata",
+ ":message-type": "event"
+ },
+ "payload": "{\"usage\":{\"inputTokens\":0,\"outputTokens\":0,\"totalTokens\":0},\"metrics\":{\"latencyMs\":0}}"
+ }
+ ]
+ },
+ {
+ "op": "end"
+ }
+ ],
+ "journal": {
+ "status": 200,
+ "interrupted": null,
+ "interruptReason": null
+ }
+ },
+ "delay-combined": {
+ "log": [
+ {
+ "op": "setHeader",
+ "name": "Content-Type",
+ "value": "application/vnd.amazon.eventstream"
+ },
+ {
+ "op": "setHeader",
+ "name": "Transfer-Encoding",
+ "value": "chunked"
+ },
+ {
+ "op": "timer",
+ "ms": 7
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 118,
+ "headersLength": 82,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"role\":\"assistant\"}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 11
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 137,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"start\":{}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 13
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"Checkin\"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 17
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 152,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"g now.\"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 19
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 125,
+ "headersLength": 86,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 23
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 191,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":1,\"start\":{\"toolUse\":{\"toolUseId\":\"call_g2\",\"name\":\"get_weather\"}}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 29
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 168,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":1,\"delta\":{\"toolUse\":{\"input\":\"{\\\"city\\\"\"}}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 31
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 167,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":1,\"delta\":{\"toolUse\":{\"input\":\":\\\"SF\\\"}\"}}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 37
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 125,
+ "headersLength": 86,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":1}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 41
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 122,
+ "headersLength": 81,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"stopReason\":\"tool_use\"}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 43
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 180,
+ "headersLength": 78,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "metadata",
+ ":message-type": "event"
+ },
+ "payload": "{\"usage\":{\"inputTokens\":0,\"outputTokens\":0,\"totalTokens\":0},\"metrics\":{\"latencyMs\":0}}"
+ }
+ ]
+ },
+ {
+ "op": "end"
+ }
+ ],
+ "journal": {
+ "status": 200,
+ "interrupted": null,
+ "interruptReason": null
+ }
+ },
+ "delay-replay2": {
+ "log": [
+ {
+ "op": "setHeader",
+ "name": "Content-Type",
+ "value": "application/vnd.amazon.eventstream"
+ },
+ {
+ "op": "setHeader",
+ "name": "Transfer-Encoding",
+ "value": "chunked"
+ },
+ {
+ "op": "timer",
+ "ms": 3.5
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 118,
+ "headersLength": 82,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"role\":\"assistant\"}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 5.5
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 137,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStart",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"start\":{}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 6.5
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"Hello g\"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 8.5
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"olden w\"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 9.5
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"orld, t\"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 11.5
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"his is \"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 14.5
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"a chara\"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 15.5
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"cteriza\"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 18.5
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 153,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"tion ru\"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 20.5
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 148,
+ "headersLength": 87,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockDelta",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0,\"delta\":{\"text\":\"n.\"}}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 21.5
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 125,
+ "headersLength": 86,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "contentBlockStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"contentBlockIndex\":0}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 23.5
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 122,
+ "headersLength": 81,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "messageStop",
+ ":message-type": "event"
+ },
+ "payload": "{\"stopReason\":\"end_turn\"}"
+ }
+ ]
+ },
+ {
+ "op": "timer",
+ "ms": 26.5
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 180,
+ "headersLength": 78,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "metadata",
+ ":message-type": "event"
+ },
+ "payload": "{\"usage\":{\"inputTokens\":0,\"outputTokens\":0,\"totalTokens\":0},\"metrics\":{\"latencyMs\":0}}"
+ }
+ ]
+ },
+ {
+ "op": "end"
+ }
+ ],
+ "journal": {
+ "status": 200,
+ "interrupted": null,
+ "interruptReason": null
+ }
+ }
+}
diff --git a/src/__tests__/__snapshots__/frame-codec-golden/bedrock-invoke-eventstream.json b/src/__tests__/__snapshots__/frame-codec-golden/bedrock-invoke-eventstream.json
new file mode 100644
index 00000000..2e07190c
--- /dev/null
+++ b/src/__tests__/__snapshots__/frame-codec-golden/bedrock-invoke-eventstream.json
@@ -0,0 +1,1946 @@
+{
+ "text": {
+ "log": [
+ {
+ "op": "setHeader",
+ "name": "Content-Type",
+ "value": "application/vnd.amazon.eventstream"
+ },
+ {
+ "op": "setHeader",
+ "name": "Transfer-Encoding",
+ "value": "chunked"
+ },
+ {
+ "op": "write",
+ "frames": [
+ {
+ "total": 439,
+ "headersLength": 75,
+ "preludeOk": true,
+ "messageOk": true,
+ "headers": {
+ ":content-type": "application/json",
+ ":event-type": "chunk",
+ ":message-type": "event"
+ },
+ "payload": "{\"bytes\":\"