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.
- package/README.md +68 -13
- package/dist/adapters/anthropic.cjs +181 -125
- package/dist/adapters/anthropic.cjs.map +1 -1
- package/dist/adapters/anthropic.d.cts +1 -3
- package/dist/adapters/anthropic.d.ts +1 -3
- package/dist/adapters/anthropic.js +181 -125
- package/dist/adapters/anthropic.js.map +1 -1
- package/dist/adapters/bedrock.cjs +450 -0
- package/dist/adapters/bedrock.cjs.map +1 -0
- package/dist/adapters/bedrock.d.cts +15 -0
- package/dist/adapters/bedrock.d.ts +15 -0
- package/dist/adapters/bedrock.js +448 -0
- package/dist/adapters/bedrock.js.map +1 -0
- package/dist/adapters/gemini.cjs +86 -44
- package/dist/adapters/gemini.cjs.map +1 -1
- package/dist/adapters/gemini.js +86 -44
- package/dist/adapters/gemini.js.map +1 -1
- package/dist/adapters/openai-chat.cjs +20 -8
- package/dist/adapters/openai-chat.cjs.map +1 -1
- package/dist/adapters/openai-chat.js +20 -8
- package/dist/adapters/openai-chat.js.map +1 -1
- package/dist/adapters/openai-compatible.cjs +20 -8
- package/dist/adapters/openai-compatible.cjs.map +1 -1
- package/dist/adapters/openai-compatible.js +20 -8
- package/dist/adapters/openai-compatible.js.map +1 -1
- package/dist/adapters/openai-responses.cjs +37 -27
- package/dist/adapters/openai-responses.cjs.map +1 -1
- package/dist/adapters/openai-responses.js +37 -27
- package/dist/adapters/openai-responses.js.map +1 -1
- package/dist/core/index.cjs +9 -2
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.js +9 -2
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +498 -187
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +498 -188
- package/dist/index.js.map +1 -1
- package/package.json +11 -2
package/README.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# llm-stream-assemble
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|

|
|
5
5
|

|
|
6
|
-

|
|
7
7
|
[](https://github.com/01laky/llm-stream-assemble/actions/workflows/ci.yml)
|
|
8
|
-

|
|
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
|
|
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.
|
|
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,
|
|
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
|
|
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/
|
|
41
|
-
function
|
|
42
|
-
return
|
|
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
|
|
45
|
-
|
|
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
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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 =
|
|
75
|
-
if (!
|
|
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
|
|
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 =
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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(...
|
|
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
|
-
|
|
282
|
-
|
|
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
|
|
306
|
-
|
|
307
|
-
|
|
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;
|