llm-stream-assemble 1.3.6 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +68 -13
  2. package/dist/adapters/anthropic.cjs +181 -125
  3. package/dist/adapters/anthropic.cjs.map +1 -1
  4. package/dist/adapters/anthropic.d.cts +1 -3
  5. package/dist/adapters/anthropic.d.ts +1 -3
  6. package/dist/adapters/anthropic.js +181 -125
  7. package/dist/adapters/anthropic.js.map +1 -1
  8. package/dist/adapters/bedrock.cjs +450 -0
  9. package/dist/adapters/bedrock.cjs.map +1 -0
  10. package/dist/adapters/bedrock.d.cts +15 -0
  11. package/dist/adapters/bedrock.d.ts +15 -0
  12. package/dist/adapters/bedrock.js +448 -0
  13. package/dist/adapters/bedrock.js.map +1 -0
  14. package/dist/adapters/gemini.cjs +86 -44
  15. package/dist/adapters/gemini.cjs.map +1 -1
  16. package/dist/adapters/gemini.js +86 -44
  17. package/dist/adapters/gemini.js.map +1 -1
  18. package/dist/adapters/openai-chat.cjs +20 -8
  19. package/dist/adapters/openai-chat.cjs.map +1 -1
  20. package/dist/adapters/openai-chat.js +20 -8
  21. package/dist/adapters/openai-chat.js.map +1 -1
  22. package/dist/adapters/openai-compatible.cjs +20 -8
  23. package/dist/adapters/openai-compatible.cjs.map +1 -1
  24. package/dist/adapters/openai-compatible.js +20 -8
  25. package/dist/adapters/openai-compatible.js.map +1 -1
  26. package/dist/adapters/openai-responses.cjs +37 -27
  27. package/dist/adapters/openai-responses.cjs.map +1 -1
  28. package/dist/adapters/openai-responses.js +37 -27
  29. package/dist/adapters/openai-responses.js.map +1 -1
  30. package/dist/core/index.cjs +9 -2
  31. package/dist/core/index.cjs.map +1 -1
  32. package/dist/core/index.js +9 -2
  33. package/dist/core/index.js.map +1 -1
  34. package/dist/index.cjs +498 -187
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +2 -1
  37. package/dist/index.d.ts +2 -1
  38. package/dist/index.js +498 -188
  39. package/dist/index.js.map +1 -1
  40. package/package.json +11 -2
package/README.md CHANGED
@@ -1,19 +1,19 @@
1
1
  # llm-stream-assemble
2
2
 
3
- ![core](https://img.shields.io/badge/core-1.3.6-blue)
3
+ ![core](https://img.shields.io/badge/core-1.4.1-blue)
4
4
  ![node](https://img.shields.io/badge/node-%3E%3D18-339933)
5
5
  ![runtime deps](https://img.shields.io/badge/runtime_deps-0-brightgreen)
6
- ![tests](https://img.shields.io/badge/tests-1019%2B_passing-brightgreen)
6
+ ![tests](https://img.shields.io/badge/tests-1183_passing-brightgreen)
7
7
  [![ci](https://github.com/01laky/llm-stream-assemble/actions/workflows/ci.yml/badge.svg)](https://github.com/01laky/llm-stream-assemble/actions/workflows/ci.yml)
8
- ![status](https://img.shields.io/badge/status-stable_1.3.6-brightgreen)
8
+ ![status](https://img.shields.io/badge/status-stable_1.4.1-brightgreen)
9
9
 
10
10
  **One typed event model for every LLM stream** — text, tool calls, reasoning, JSON, usage, refusals, errors, and non-streaming responses.
11
11
 
12
- > A zero-dependency TypeScript layer for assembling **OpenAI**, **Anthropic**, **Google Gemini**, and **OpenAI-compatible** LLM streams into unified events so you can stop hand-rolling provider parsers and keep one clean, typed event model across chat UIs, agents, proxies, and backends.
12
+ > A zero-dependency TypeScript layer between raw LLM provider bytes and your app: six built-in adapters, thirteen host presets, and a single StreamEvent model for text, tools, reasoning, JSON, and lifecycle from Ollama to Azure to Bedrock to Cloudflare Workers AI.
13
13
 
14
14
  Turn provider SSE fragments into typed events — **not another `+=` loop**.
15
15
 
16
- **Status:** Stable `1.3.6`. Five built-in adapters, thirteen OpenAI-compatible host presets (including **Azure OpenAI** and **Cloudflare Workers AI**), transforms, replay helpers, and examples are production-ready. Pin semver ranges as usual and review [CHANGELOG.md](./CHANGELOG.md) before major upgrades.
16
+ **Status:** Stable `1.4.1`. Six built-in adapters, thirteen OpenAI-compatible host presets (including **Azure OpenAI** and **Cloudflare Workers AI**), transforms, replay helpers, and examples are production-ready. Pin semver ranges as usual and review [CHANGELOG.md](./CHANGELOG.md) before major upgrades.
17
17
 
18
18
  ---
19
19
 
@@ -138,13 +138,14 @@ Diagram sources: [`docs/img/`](./docs/img/) (Mermaid `.mmd` + committed SVG). Re
138
138
 
139
139
  ## Providers at a glance
140
140
 
141
- | Adapter | Provider / API | Import |
142
- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
143
- | `openaiChatAdapter()` | OpenAI Chat Completions | `llm-stream-assemble` |
144
- | `openaiCompatibleAdapter({ provider })` | Groq, DeepSeek, Mistral, Ollama, LM Studio, Together, Fireworks, OpenRouter, Perplexity, xAI, **Azure OpenAI**, **Cloudflare Workers AI**, generic | `llm-stream-assemble` |
145
- | `anthropicAdapter()` | Anthropic Messages | `llm-stream-assemble` |
146
- | `openaiResponsesAdapter()` | OpenAI Responses API | `llm-stream-assemble` |
147
- | `geminiAdapter()` | Google AI Gemini | `llm-stream-assemble` or `/adapters/gemini` |
141
+ | Adapter | Provider / API | Import |
142
+ | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
143
+ | `openaiChatAdapter()` | OpenAI Chat Completions | `llm-stream-assemble` |
144
+ | `openaiCompatibleAdapter({ provider })` | Groq, DeepSeek, Mistral, Ollama, LM Studio, Together, Fireworks, OpenRouter, Perplexity, xAI, **Azure OpenAI**, **Cloudflare Workers AI**, generic | `llm-stream-assemble` |
145
+ | `anthropicAdapter()` | Anthropic Messages | `llm-stream-assemble` |
146
+ | `openaiResponsesAdapter()` | OpenAI Responses API | `llm-stream-assemble` |
147
+ | `geminiAdapter()` | Google AI Gemini | `llm-stream-assemble` or `/adapters/gemini` |
148
+ | `bedrockAdapter()` | AWS Bedrock Converse / ConverseStream | `llm-stream-assemble` or `/adapters/bedrock` |
148
149
 
149
150
  Full feature flags and quirks: [compatibility matrix](./docs/compatibility.md).
150
151
 
@@ -200,6 +201,7 @@ Pick an adapter in ~30 seconds:
200
201
  - **OpenAI Responses API** → `openaiResponsesAdapter()`
201
202
  - **Anthropic Messages** → `anthropicAdapter()`
202
203
  - **Google Gemini** → `geminiAdapter()`
204
+ - **AWS Bedrock ConverseStream** → `bedrockAdapter()` (decoded JSON per event — see [Bedrock Usage](#bedrock-usage))
203
205
  - **Groq, Ollama, Azure, Cloudflare, OpenRouter, …** → `openaiCompatibleAdapter({ provider })`
204
206
  - **Non-streaming JSON body** → `assembleResponse(body, adapter)`
205
207
  - **React chat UI / full agent framework** → not this package — see [comparison](./docs/comparison.md)
@@ -271,6 +273,10 @@ for await (const event of assembleStream(response.body!, adapter)) {
271
273
 
272
274
  → [`examples/node-fetch/gemini.ts`](./examples/node-fetch/gemini.ts) · Usage: [Gemini](#gemini-usage)
273
275
 
276
+ ### AWS Bedrock
277
+
278
+ → [`examples/node-fetch/bedrock.ts`](./examples/node-fetch/bedrock.ts) · Usage: [Bedrock](#bedrock-usage) · Decode helper: [`examples/bedrock/README.md`](./examples/bedrock/README.md)
279
+
274
280
  ### Streaming JSON (structured output)
275
281
 
276
282
  ```ts
@@ -311,7 +317,7 @@ Wire unified events into **Hono**, **Express**, **Cloudflare Workers**, **LiteLL
311
317
 
312
318
  ### Core Usage
313
319
 
314
- The core pipeline works with any adapter that emits `RawChunk[]`, including the built-in OpenAI Chat, OpenAI-compatible, Anthropic Messages, OpenAI Responses, and Google Gemini adapters:
320
+ The core pipeline works with any adapter that emits `RawChunk[]`, including the built-in OpenAI Chat, OpenAI-compatible, Anthropic Messages, OpenAI Responses, Google Gemini, and AWS Bedrock adapters:
315
321
 
316
322
  ```ts
317
323
  import { assembleFromPayloads, type StreamAdapter } from "llm-stream-assemble";
@@ -551,6 +557,54 @@ Subpath import: `import { geminiAdapter } from "llm-stream-assemble/adapters/gem
551
557
 
552
558
  Vertex AI and the Interactions API are out of scope for this adapter; see [compatibility matrix](./docs/compatibility.md).
553
559
 
560
+ ### Bedrock Usage
561
+
562
+ `bedrockAdapter()` parses **decoded** AWS Bedrock **ConverseStream** JSON events — one ConverseStream envelope object per `parseChunk` call. Create one adapter instance per request/stream.
563
+
564
+ Bedrock streaming responses are often `application/vnd.amazon.eventstream` (binary). **Decode EventStream bytes in your app, AWS SDK, or the example helper** before assembly — this library does not sign requests or parse binary framing.
565
+
566
+ ```
567
+ Bedrock Runtime → EventStream bytes → [SDK or decode helper] → JSON strings
568
+ → bedrockAdapter().parseChunk / assembleFromPayloads / assembleStream → StreamEvent[]
569
+ ```
570
+
571
+ **Recommended path:** use `@aws-sdk/client-bedrock-runtime` `ConverseStreamCommand`, iterate the async stream, `JSON.stringify` each event object, and feed lines to `assembleFromPayloads`. See [`examples/bedrock/README.md`](./examples/bedrock/README.md) and [`examples/node-fetch/bedrock.ts`](./examples/node-fetch/bedrock.ts).
572
+
573
+ ```ts
574
+ import { assembleFromPayloads, bedrockAdapter } from "llm-stream-assemble";
575
+
576
+ async function* decodedConverseEvents(sdkStream: AsyncIterable<Record<string, unknown>>) {
577
+ for await (const event of sdkStream) {
578
+ yield JSON.stringify(event);
579
+ }
580
+ }
581
+
582
+ for await (const event of assembleFromPayloads(
583
+ decodedConverseEvents(converseStream),
584
+ bedrockAdapter({ modelFamily: "auto" }),
585
+ )) {
586
+ if (event.type === "text.delta") process.stdout.write(event.text);
587
+ if (event.type === "tool_call.done") console.log(event.name, event.args);
588
+ }
589
+ ```
590
+
591
+ **`modelFamily`** hints which ConverseStream dialect to prefer when envelopes overlap:
592
+
593
+ | Value | When to use |
594
+ | --------------- | ----------------------------------------------------------------- |
595
+ | `"auto"` | Default — structural detection from payload shape |
596
+ | `"anthropic"` | Claude on Bedrock — reasoning deltas, Anthropic-style tool blocks |
597
+ | `"nova"` | Amazon Nova models |
598
+ | `"openai-like"` | Llama and other OpenAI-shaped delta fields |
599
+
600
+ Use `bedrockAdapter({ jsonMode: true })` when structured JSON text blocks should map to `json.*` instead of `text.*`. Guardrail interventions map to `finish` with `content_filter`; trace details remain in `metadata.raw`.
601
+
602
+ **Environment variables** for live smoke and examples: `AWS_REGION`, `BEDROCK_MODEL_ID`, plus standard AWS credential chain (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_PROFILE`, SSO). IAM and SigV4 signing stay outside this library.
603
+
604
+ Subpath import: `import { bedrockAdapter } from "llm-stream-assemble/adapters/bedrock"`.
605
+
606
+ Worker proxy recipe: [`examples/integrations/bedrock-worker-proxy.ts`](./examples/integrations/bedrock-worker-proxy.ts). EventStream decode helper (examples only): [`examples/bedrock/decode-event-stream.ts`](./examples/bedrock/decode-event-stream.ts).
607
+
554
608
  ---
555
609
 
556
610
  ## Transforms & replay
@@ -623,6 +677,7 @@ for await (const event of assembleFromFile(
623
677
  | [`examples/node-fetch/xai.ts`](./examples/node-fetch/xai.ts) | xAI Grok streaming |
624
678
  | [`examples/node-fetch/anthropic.ts`](./examples/node-fetch/anthropic.ts) | Anthropic Messages |
625
679
  | [`examples/node-fetch/gemini.ts`](./examples/node-fetch/gemini.ts) | Google Gemini SSE |
680
+ | [`examples/node-fetch/bedrock.ts`](./examples/node-fetch/bedrock.ts) | AWS Bedrock ConverseStream (decoded JSON) |
626
681
  | [`examples/node-fetch/replay-fixture.ts`](./examples/node-fetch/replay-fixture.ts) | Local fixture replay |
627
682
  | [`examples/proxy-safety/`](./examples/proxy-safety/) | Proxy + browser client patterns |
628
683
 
@@ -1,5 +1,36 @@
1
1
  'use strict';
2
2
 
3
+ // src/core/utils/object.ts
4
+ function stripUndefined(obj) {
5
+ return Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== void 0));
6
+ }
7
+
8
+ // src/adapters/errors.ts
9
+ function libraryError(message) {
10
+ return new Error(`llm-stream-assemble: ${message}`);
11
+ }
12
+ function adapterScopedError(scope, message) {
13
+ return new Error(`llm-stream-assemble: ${scope}: ${message}`);
14
+ }
15
+ function providerErrorChunks(error, recoverable = false) {
16
+ return [
17
+ { kind: "provider-error", error, recoverable },
18
+ { kind: "finish", reason: "error" }
19
+ ];
20
+ }
21
+ function providerErrorChunksFromMessage(message, recoverable = false) {
22
+ return providerErrorChunks(libraryError(message), recoverable);
23
+ }
24
+ function providerErrorChunksFromPayload(errorPayload, scope, recoverable, fallbackMessage) {
25
+ const message = asString(errorPayload.message) ?? fallbackMessage;
26
+ const error = adapterScopedError(scope, message);
27
+ Object.defineProperty(error, "raw", {
28
+ value: errorPayload,
29
+ enumerable: false
30
+ });
31
+ return providerErrorChunks(error, recoverable);
32
+ }
33
+
3
34
  // src/adapters/utils.ts
4
35
  function isRecord(value) {
5
36
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -11,9 +42,7 @@ function asNumber(value) {
11
42
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
12
43
  }
13
44
  function optionalRawChunk(input) {
14
- return Object.fromEntries(
15
- Object.entries(input).filter(([, value]) => value !== void 0)
16
- );
45
+ return stripUndefined(input);
17
46
  }
18
47
  function prefixedAdapterError(feature, message) {
19
48
  return adapterScopedError(feature, message);
@@ -37,21 +66,125 @@ function parseAdapterJSON(raw, feature) {
37
66
  }
38
67
  }
39
68
 
40
- // src/adapters/errors.ts
41
- function libraryError(message) {
42
- return new Error(`llm-stream-assemble: ${message}`);
69
+ // src/adapters/shared/anthropic-blocks.ts
70
+ function anthropicResponseBlockChunks(block, index, context) {
71
+ return anthropicBlockStartChunks(block, index, { ...context, mode: "response" });
43
72
  }
44
- function adapterScopedError(scope, message) {
45
- return new Error(`llm-stream-assemble: ${scope}: ${message}`);
73
+ function anthropicBlockStartChunks(block, index, context) {
74
+ const mode = context.mode ?? "stream-start";
75
+ const blockType = asString(block.type) ?? "unknown";
76
+ switch (blockType) {
77
+ case "text": {
78
+ const text = asString(block.text);
79
+ if (!text) return [];
80
+ if (context.jsonMode) return [{ kind: "json-delta", delta: text }];
81
+ return [{ kind: "text-delta", text }];
82
+ }
83
+ case "thinking": {
84
+ const text = asString(block.thinking);
85
+ return text ? [{ kind: "reasoning-delta", text, variant: "detail" }] : [];
86
+ }
87
+ case "redacted_thinking":
88
+ return [];
89
+ case "tool_use":
90
+ return anthropicToolUseBlockChunks(block, index, mode);
91
+ case "json": {
92
+ const text = asString(block.text) ?? asString(block.partial_json);
93
+ return text ? [{ kind: "json-delta", delta: text }] : [];
94
+ }
95
+ case "refusal": {
96
+ const text = asString(block.refusal) ?? asString(block.text);
97
+ return text ? [{ kind: "refusal-delta", text }] : [];
98
+ }
99
+ default:
100
+ return [];
101
+ }
46
102
  }
47
- function providerErrorChunks(error, recoverable = false) {
48
- return [
49
- { kind: "provider-error", error, recoverable },
50
- { kind: "finish", reason: "error" }
51
- ];
103
+ function anthropicToolUseBlockChunks(block, index, mode) {
104
+ const id = asString(block.id);
105
+ const name = asString(block.name) ?? "unknown";
106
+ const chunks = [optionalRawChunk({ kind: "tool-start", id, name, index })];
107
+ const input = block.input;
108
+ if (input !== void 0 && !(isRecord(input) && Object.keys(input).length === 0)) {
109
+ chunks.push(
110
+ optionalRawChunk({ kind: "tool-args-delta", id, delta: JSON.stringify(input), index })
111
+ );
112
+ }
113
+ if (mode === "response") {
114
+ chunks.push(optionalRawChunk({ kind: "tool-done", id, index }));
115
+ }
116
+ return chunks;
52
117
  }
53
- function providerErrorChunksFromMessage(message, recoverable = false) {
54
- return providerErrorChunks(libraryError(message), recoverable);
118
+
119
+ // src/adapters/shared/parse-payload.ts
120
+ function parseAdapterObjectPayload(raw, scope, options = {}) {
121
+ const { trim = true, allowDone = true } = options;
122
+ const input = trim ? raw.trim() : raw;
123
+ if (input.length === 0 || allowDone && input === "[DONE]") return null;
124
+ const payload = parseAdapterJSON(input, scope);
125
+ if (!isRecord(payload)) {
126
+ throw adapterScopedError(scope, "expected a JSON object");
127
+ }
128
+ return payload;
129
+ }
130
+
131
+ // src/adapters/shared/stop-reasons.ts
132
+ function mapAnthropicLikeStopReason(value) {
133
+ switch (value) {
134
+ case "end_turn":
135
+ case "stop_sequence":
136
+ return "stop";
137
+ case "tool_use":
138
+ return "tool_calls";
139
+ case "max_tokens":
140
+ return "length";
141
+ case "content_filtered":
142
+ case "guardrail_intervened":
143
+ case "refusal":
144
+ return "content_filter";
145
+ default:
146
+ return "stop";
147
+ }
148
+ }
149
+
150
+ // src/adapters/shared/usage.ts
151
+ var DEFAULT_ALIASES = {
152
+ input: ["inputTokens", "input_tokens", "promptTokens", "promptTokenCount", "inputTokenCount"],
153
+ output: [
154
+ "outputTokens",
155
+ "output_tokens",
156
+ "completionTokens",
157
+ "candidatesTokenCount",
158
+ "outputTokenCount"
159
+ ],
160
+ reasoning: ["reasoningTokens", "reasoning_tokens", "thoughtsTokenCount"],
161
+ total: ["totalTokens", "totalTokenCount", "total_tokens"]
162
+ };
163
+ function firstNumber(value, fields) {
164
+ if (!fields) return void 0;
165
+ for (const field of fields) {
166
+ const number = asNumber(value[field]);
167
+ if (number !== void 0) return number;
168
+ }
169
+ return void 0;
170
+ }
171
+ function buildUsageChunk(value, aliases = DEFAULT_ALIASES, options) {
172
+ if (!isRecord(value)) return void 0;
173
+ const inputTokens = firstNumber(value, aliases.input);
174
+ const outputTokens = firstNumber(value, aliases.output);
175
+ const reasoningTokens = firstNumber(value, aliases.reasoning);
176
+ const totalTokens = firstNumber(value, aliases.total);
177
+ if (inputTokens === void 0 && outputTokens === void 0 && reasoningTokens === void 0 && totalTokens === void 0) {
178
+ return void 0;
179
+ }
180
+ const raw = value;
181
+ return optionalRawChunk({
182
+ kind: "usage",
183
+ inputTokens,
184
+ outputTokens,
185
+ reasoningTokens,
186
+ raw
187
+ });
55
188
  }
56
189
 
57
190
  // src/adapters/anthropic.ts
@@ -71,10 +204,8 @@ var AnthropicStreamParser = class {
71
204
  blocks = /* @__PURE__ */ new Map();
72
205
  sawFinish = false;
73
206
  parseChunk(raw) {
74
- const payload = parseAdapterJSON(raw, "anthropicAdapter.parseChunk");
75
- if (!isRecord(payload)) {
76
- throw libraryError("anthropicAdapter.parseChunk expected a JSON object");
77
- }
207
+ const payload = parseAdapterObjectPayload(raw, "anthropicAdapter.parseChunk");
208
+ if (!payload) return [];
78
209
  const type = asString(payload.type);
79
210
  switch (type) {
80
211
  case "ping":
@@ -92,7 +223,7 @@ var AnthropicStreamParser = class {
92
223
  case "message_stop":
93
224
  return this.messageStop();
94
225
  case "error":
95
- return providerErrorChunks2(payload.error);
226
+ return providerErrorFromPayload(payload.error);
96
227
  default:
97
228
  throw libraryError(`anthropicAdapter.parseChunk unknown event type: ${String(type)}`);
98
229
  }
@@ -107,7 +238,7 @@ var AnthropicStreamParser = class {
107
238
  if (id || model) {
108
239
  chunks.push(optionalRawChunk({ kind: "metadata", responseId: id, model, raw: message }));
109
240
  }
110
- const usage = usageChunk(message.usage);
241
+ const usage = buildUsageChunk(message.usage);
111
242
  if (usage) chunks.push(usage);
112
243
  return chunks;
113
244
  }
@@ -118,42 +249,16 @@ var AnthropicStreamParser = class {
118
249
  const blockType = asString(block.type) ?? "unknown";
119
250
  const state = { type: blockType };
120
251
  this.blocks.set(index, state);
121
- switch (blockType) {
122
- case "text": {
123
- const text = asString(block.text);
124
- return text ? [{ kind: "text-delta", text }] : [];
125
- }
126
- case "thinking": {
127
- const text = asString(block.thinking);
128
- return text ? [{ kind: "reasoning-delta", text, variant: "detail" }] : [];
129
- }
130
- case "redacted_thinking":
131
- return [];
132
- case "tool_use": {
133
- const id = asString(block.id);
134
- const name = asString(block.name) ?? "unknown";
135
- if (id) state.id = id;
136
- state.name = name;
137
- const chunks = [optionalRawChunk({ kind: "tool-start", id, name, index })];
138
- const input = block.input;
139
- if (input !== void 0 && !(isRecord(input) && Object.keys(input).length === 0)) {
140
- chunks.push(
141
- optionalRawChunk({ kind: "tool-args-delta", id, delta: JSON.stringify(input), index })
142
- );
143
- }
144
- return chunks;
145
- }
146
- case "json": {
147
- const text = asString(block.text) ?? asString(block.partial_json);
148
- return text ? [{ kind: "json-delta", delta: text }] : [];
149
- }
150
- case "refusal": {
151
- const text = asString(block.refusal) ?? asString(block.text);
152
- return text ? [{ kind: "refusal-delta", text }] : [];
153
- }
154
- default:
155
- return [];
252
+ if (blockType === "tool_use") {
253
+ const id = asString(block.id);
254
+ const name = asString(block.name) ?? "unknown";
255
+ if (id) state.id = id;
256
+ state.name = name;
156
257
  }
258
+ return anthropicBlockStartChunks(block, index, {
259
+ jsonMode: this.options.jsonMode,
260
+ mode: "stream-start"
261
+ });
157
262
  }
158
263
  contentBlockDelta(payload) {
159
264
  const index = asNumber(payload.index) ?? 0;
@@ -165,8 +270,9 @@ var AnthropicStreamParser = class {
165
270
  case "text_delta": {
166
271
  const text = asString(delta.text);
167
272
  if (!text) return [];
168
- if (this.options.jsonMode || state?.type === "json")
273
+ if (this.options.jsonMode || state?.type === "json") {
169
274
  return [{ kind: "json-delta", delta: text }];
275
+ }
170
276
  if (state?.type === "refusal") return [{ kind: "refusal-delta", text }];
171
277
  return [{ kind: "text-delta", text }];
172
278
  }
@@ -198,7 +304,7 @@ var AnthropicStreamParser = class {
198
304
  }
199
305
  messageDelta(payload) {
200
306
  const chunks = [];
201
- const usage = usageChunk(payload.usage);
307
+ const usage = buildUsageChunk(payload.usage);
202
308
  if (usage) chunks.push(usage);
203
309
  const delta = isRecord(payload.delta) ? payload.delta : void 0;
204
310
  const stopReason = delta ? asString(delta.stop_reason) : void 0;
@@ -220,91 +326,41 @@ function parseResponse(body, options) {
220
326
  throw libraryError("anthropicAdapter.parseResponse expected an Anthropic message object");
221
327
  }
222
328
  if (asString(body.type) === "error" || isRecord(body.error)) {
223
- return providerErrorChunks2(body.error);
329
+ return providerErrorFromPayload(body.error);
224
330
  }
225
331
  const chunks = [];
226
332
  const id = asString(body.id);
227
333
  const model = asString(body.model);
228
334
  if (id) chunks.push({ kind: "message-start", id });
229
- if (id || model)
335
+ if (id || model) {
230
336
  chunks.push(optionalRawChunk({ kind: "metadata", responseId: id, model, raw: body }));
231
- const inputUsage = usageChunk(body.usage);
337
+ }
338
+ const inputUsage = buildUsageChunk(body.usage);
232
339
  if (inputUsage) chunks.push(inputUsage);
233
340
  const content = Array.isArray(body.content) ? body.content : [];
234
341
  for (let index = 0; index < content.length; index += 1) {
235
342
  const block = content[index];
236
343
  if (!isRecord(block)) continue;
237
- chunks.push(...responseBlockChunks(block, index, options));
344
+ chunks.push(...anthropicResponseBlockChunks(block, index, { jsonMode: options.jsonMode }));
238
345
  }
239
346
  const reason = finishReason(asString(body.stop_reason));
240
347
  if (reason) chunks.push({ kind: "finish", reason });
241
348
  return chunks;
242
349
  }
243
- function responseBlockChunks(block, index, options) {
244
- const type = asString(block.type);
245
- switch (type) {
246
- case "text": {
247
- const text = asString(block.text);
248
- if (!text) return [];
249
- return options.jsonMode ? [{ kind: "json-delta", delta: text }] : [{ kind: "text-delta", text }];
250
- }
251
- case "thinking": {
252
- const text = asString(block.thinking);
253
- return text ? [{ kind: "reasoning-delta", text, variant: "detail" }] : [];
254
- }
255
- case "tool_use": {
256
- const id = asString(block.id);
257
- const name = asString(block.name) ?? "unknown";
258
- const chunks = [optionalRawChunk({ kind: "tool-start", id, name, index })];
259
- if (block.input !== void 0) {
260
- chunks.push(
261
- optionalRawChunk({
262
- kind: "tool-args-delta",
263
- id,
264
- delta: JSON.stringify(block.input),
265
- index
266
- })
267
- );
268
- }
269
- chunks.push(optionalRawChunk({ kind: "tool-done", id, index }));
270
- return chunks;
271
- }
272
- case "refusal": {
273
- const text = asString(block.refusal) ?? asString(block.text);
274
- return text ? [{ kind: "refusal-delta", text }] : [];
275
- }
276
- default:
277
- return [];
278
- }
279
- }
280
350
  function finishReason(value) {
281
- switch (value) {
282
- case "end_turn":
283
- case "stop_sequence":
284
- return "stop";
285
- case "max_tokens":
286
- return "length";
287
- case "tool_use":
288
- return "tool_calls";
289
- case "refusal":
290
- return "content_filter";
291
- case void 0:
292
- case null:
293
- return void 0;
294
- default:
295
- return "stop";
296
- }
297
- }
298
- function usageChunk(value) {
299
- if (!isRecord(value)) return void 0;
300
- const inputTokens = asNumber(value.input_tokens);
301
- const outputTokens = asNumber(value.output_tokens);
302
- if (inputTokens === void 0 && outputTokens === void 0) return void 0;
303
- return optionalRawChunk({ kind: "usage", inputTokens, outputTokens, raw: value });
351
+ if (value === void 0 || value === null) return void 0;
352
+ return mapAnthropicLikeStopReason(value);
304
353
  }
305
- function providerErrorChunks2(value) {
306
- const message = isRecord(value) ? asString(value.message) : void 0;
307
- return providerErrorChunksFromMessage(message ?? "Anthropic provider error", false);
354
+ function providerErrorFromPayload(value) {
355
+ if (isRecord(value)) {
356
+ return providerErrorChunksFromPayload(
357
+ value,
358
+ "anthropicAdapter.parseChunk",
359
+ false,
360
+ "Anthropic provider error"
361
+ );
362
+ }
363
+ return providerErrorChunksFromMessage("Anthropic provider error", false);
308
364
  }
309
365
 
310
366
  exports.anthropicAdapter = anthropicAdapter;