Skip to content

Commit 0f9eccc

Browse files
Qardclaude
andcommitted
fix(e2e): fix SSE chunk splitting and URL normalization in cassette conversion
Two issues in the legacy cassette converter: 1. SSE chunks were stored as raw HTTP DATA frame fragments in the old format, not as complete SSE events. The converter was mapping each old fragment to a new seinfeld chunk, causing mid-event splits. The AI SDK then saw truncated JSON fragments as SSE event bodies and failed to parse them. Fix: concatenate all fragment bytes first, then split on \n\n to produce complete SSE events as seinfeld's format requires. 2. The huggingface cassette stored URLs with percent-encoded brackets (%5B%5D) but the HuggingFace SDK sends them unencoded ([]). Seinfeld's default matcher uses strict string comparison on the full URL, so the encoding difference caused spurious misses. Fix: add URL normalization through the WHATWG URL parser to seinfeld's default filter preset, applied to both cassette candidates and incoming requests before comparison. All 25 cassettes re-converted from the original legacy format with the corrected SSE splitting logic. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 804437d commit 0f9eccc

27 files changed

Lines changed: 20800 additions & 15093 deletions

File tree

dev-packages/seinfeld/scripts/migrate-from-legacy.mjs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -205,24 +205,30 @@ function convertBody(encoding, body, chunks) {
205205
return { kind: "base64", value: String(body ?? "") };
206206
}
207207
case "sse-chunks": {
208-
// Legacy SSE: chunks is an array of { data: base64, encoding: 'base64' }.
209-
// Seinfeld SSE: chunks is an array of raw UTF-8 strings.
208+
// Legacy SSE: arbitrary byte-stream fragments, each base64-encoded.
209+
// The fragments may NOT align with SSE event boundaries — a single SSE
210+
// event can span multiple chunks. Seinfeld's chunk array must contain
211+
// complete SSE events (one entry per \n\n-terminated event).
212+
// Fix: concatenate all decoded fragment bytes first, then split on \n\n.
210213
const rawChunks = Array.isArray(chunks)
211214
? chunks
212215
: Array.isArray(body)
213216
? body
214217
: [];
215-
return {
216-
kind: "sse",
217-
chunks: rawChunks.map((c) => {
218+
const fullText = rawChunks
219+
.map((c) => {
218220
if (typeof c === "string") return c;
219221
const obj = /** @type {Record<string, unknown>} */ (c);
220222
if (obj["encoding"] === "base64" && typeof obj["data"] === "string") {
221223
return Buffer.from(String(obj["data"]), "base64").toString("utf8");
222224
}
223225
return String(obj["data"] ?? c);
224-
}),
225-
};
226+
})
227+
.join("");
228+
// Split on \n\n (SSE event separator) and drop trailing empty entry.
229+
const parts = fullText.replace(/\r\n/g, "\n").split("\n\n");
230+
if (parts.length > 0 && parts[parts.length - 1] === "") parts.pop();
231+
return { kind: "sse", chunks: parts };
226232
}
227233
default:
228234
// Unknown encoding — fall back to empty

dev-packages/seinfeld/src/normalizer/presets.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ const DEFAULT_FILTER: FilterConfig = {
2121
...RATE_LIMIT_HEADERS,
2222
...FINGERPRINT_HEADERS,
2323
],
24+
// Normalize the URL through the WHATWG URL parser so that percent-encoding
25+
// differences (e.g. `%5B%5D` vs `[]`) don't produce spurious misses.
26+
normalizeRequest: (req) => {
27+
try {
28+
return { ...req, url: new URL(req.url).href };
29+
} catch {
30+
return req;
31+
}
32+
},
2433
};
2534

2635
/**

e2e/scenarios/ai-sdk-instrumentation/__cassettes__/ai-sdk-v3.cassette.json

Lines changed: 6172 additions & 2222 deletions
Large diffs are not rendered by default.

e2e/scenarios/ai-sdk-instrumentation/__cassettes__/ai-sdk-v4.cassette.json

Lines changed: 6172 additions & 2223 deletions
Large diffs are not rendered by default.

e2e/scenarios/ai-sdk-instrumentation/__cassettes__/ai-sdk-v5.cassette.json

Lines changed: 84 additions & 3710 deletions
Large diffs are not rendered by default.

e2e/scenarios/ai-sdk-instrumentation/__cassettes__/ai-sdk-v6.cassette.json

Lines changed: 84 additions & 3772 deletions
Large diffs are not rendered by default.

e2e/scenarios/ai-sdk-otel-export/__cassettes__/ai-sdk-v5.cassette.json

Lines changed: 38 additions & 21 deletions
Large diffs are not rendered by default.

e2e/scenarios/ai-sdk-otel-export/__cassettes__/ai-sdk-v6.cassette.json

Lines changed: 38 additions & 21 deletions
Large diffs are not rendered by default.

e2e/scenarios/anthropic-instrumentation/__cassettes__/anthropic-v0273.cassette.json

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -328,13 +328,13 @@
328328
"body": {
329329
"kind": "sse",
330330
"chunks": [
331-
"event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-haiku-4-5-20251001\",\"id\":\"msg_01YPdGeF3zFRM14L8B2cC6TL\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":24,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}} }\n\n",
332-
"event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\n",
333-
"event: ping\ndata: {\"type\": \"ping\"}\n\n",
334-
"event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"1 one\\n2 two\\n3 three\"} }\n\n",
335-
"event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\n",
336-
"event: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":24,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":15} }\n\n",
337-
"event: message_stop\ndata: {\"type\":\"message_stop\" }\n\n"
331+
"event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-haiku-4-5-20251001\",\"id\":\"msg_01YPdGeF3zFRM14L8B2cC6TL\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":24,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}} }",
332+
"event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }",
333+
"event: ping\ndata: {\"type\": \"ping\"}",
334+
"event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"1 one\\n2 two\\n3 three\"} }",
335+
"event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }",
336+
"event: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":24,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":15} }",
337+
"event: message_stop\ndata: {\"type\":\"message_stop\" }"
338338
]
339339
}
340340
}
@@ -398,12 +398,13 @@
398398
"body": {
399399
"kind": "sse",
400400
"chunks": [
401-
"event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-haiku-4-5-20251001\",\"id\":\"msg_012EbUGmLfEc7GsCBMZAKpSK\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":24,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}} }\n\n",
402-
"event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\n",
403-
"event: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"1 one\\n2 two\\n3 three\"} }\n\n",
404-
"event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\n",
405-
"event: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":24,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":15} }\n\n",
406-
"event: message_stop\ndata: {\"type\":\"message_stop\" }\n\n"
401+
"event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-haiku-4-5-20251001\",\"id\":\"msg_012EbUGmLfEc7GsCBMZAKpSK\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":24,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}} }",
402+
"event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}",
403+
"event: ping\ndata: {\"type\": \"ping\"}",
404+
"event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"1 one\\n2 two\\n3 three\"} }",
405+
"event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }",
406+
"event: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":24,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":15} }",
407+
"event: message_stop\ndata: {\"type\":\"message_stop\" }"
407408
]
408409
}
409410
}
@@ -450,7 +451,9 @@
450451
"type": "string"
451452
}
452453
},
453-
"required": ["location"],
454+
"required": [
455+
"location"
456+
],
454457
"type": "object"
455458
},
456459
"name": "get_weather"
@@ -486,16 +489,16 @@
486489
"body": {
487490
"kind": "sse",
488491
"chunks": [
489-
"event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-haiku-4-5-20251001\",\"id\":\"msg_01V3odUVvBp7MxCnzqZFrW1u\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":687,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":13,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}} }\n\n",
490-
"event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_016LWtxJSJr8E91HsktMeniw\",\"name\":\"get_weather\",\"input\":{},\"caller\":{\"type\":\"direct\"}} }\n\n",
491-
"event: ping\ndata: {\"type\": \"ping\"}\n\n",
492-
"event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"} }\n\n",
493-
"event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"location\\\":\"} }\n\n",
494-
"event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\" \\\"Paris, F\"} }\n\n",
495-
"event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"rance\\\"}\"} }\n\n",
496-
"event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\n",
497-
"event: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":687,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":26} }\n\n",
498-
"event: message_stop\ndata: {\"type\":\"message_stop\" }\n\n"
492+
"event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-haiku-4-5-20251001\",\"id\":\"msg_01V3odUVvBp7MxCnzqZFrW1u\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":687,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":13,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}} }",
493+
"event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_016LWtxJSJr8E91HsktMeniw\",\"name\":\"get_weather\",\"input\":{},\"caller\":{\"type\":\"direct\"}} }",
494+
"event: ping\ndata: {\"type\": \"ping\"}",
495+
"event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"} }",
496+
"event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"location\\\":\"} }",
497+
"event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\" \\\"Paris, F\"} }",
498+
"event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"rance\\\"}\"} }",
499+
"event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }",
500+
"event: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":687,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":26} }",
501+
"event: message_stop\ndata: {\"type\":\"message_stop\" }"
499502
]
500503
}
501504
}
@@ -536,7 +539,9 @@
536539
"type": "string"
537540
}
538541
},
539-
"required": ["location"],
542+
"required": [
543+
"location"
544+
],
540545
"type": "object"
541546
},
542547
"name": "get_weather"

0 commit comments

Comments
 (0)