llm-stream-assemble 1.0.0
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/LICENSE +21 -0
- package/README.md +390 -0
- package/dist/adapters/anthropic.cjs +312 -0
- package/dist/adapters/anthropic.cjs.map +1 -0
- package/dist/adapters/anthropic.d.cts +11 -0
- package/dist/adapters/anthropic.d.ts +11 -0
- package/dist/adapters/anthropic.js +310 -0
- package/dist/adapters/anthropic.js.map +1 -0
- package/dist/adapters/openai-chat.cjs +499 -0
- package/dist/adapters/openai-chat.cjs.map +1 -0
- package/dist/adapters/openai-chat.d.cts +9 -0
- package/dist/adapters/openai-chat.d.ts +9 -0
- package/dist/adapters/openai-chat.js +497 -0
- package/dist/adapters/openai-chat.js.map +1 -0
- package/dist/adapters/openai-compatible.cjs +519 -0
- package/dist/adapters/openai-compatible.cjs.map +1 -0
- package/dist/adapters/openai-compatible.d.cts +15 -0
- package/dist/adapters/openai-compatible.d.ts +15 -0
- package/dist/adapters/openai-compatible.js +517 -0
- package/dist/adapters/openai-compatible.js.map +1 -0
- package/dist/adapters/openai-responses.cjs +444 -0
- package/dist/adapters/openai-responses.cjs.map +1 -0
- package/dist/adapters/openai-responses.d.cts +8 -0
- package/dist/adapters/openai-responses.d.ts +8 -0
- package/dist/adapters/openai-responses.js +442 -0
- package/dist/adapters/openai-responses.js.map +1 -0
- package/dist/core/index.cjs +816 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +18 -0
- package/dist/core/index.d.ts +18 -0
- package/dist/core/index.js +808 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.cjs +2238 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +66 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +2206 -0
- package/dist/index.js.map +1 -0
- package/dist/types-CskRfrmD.d.cts +176 -0
- package/dist/types-CskRfrmD.d.ts +176 -0
- package/package.json +95 -0
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/core/utils/bytes.ts
|
|
4
|
+
var Utf8StreamDecoder = class {
|
|
5
|
+
decoder = new TextDecoder();
|
|
6
|
+
decode(chunk) {
|
|
7
|
+
return this.decoder.decode(chunk, { stream: true });
|
|
8
|
+
}
|
|
9
|
+
flush() {
|
|
10
|
+
return this.decoder.decode();
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var encoder = new TextEncoder();
|
|
14
|
+
function utf8ByteLength(value) {
|
|
15
|
+
return encoder.encode(value).byteLength;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/core/utils/source.ts
|
|
19
|
+
async function* readableStreamToStrings(source) {
|
|
20
|
+
const reader = source.getReader();
|
|
21
|
+
const decoder = new Utf8StreamDecoder();
|
|
22
|
+
let completed = false;
|
|
23
|
+
try {
|
|
24
|
+
while (true) {
|
|
25
|
+
const { done, value } = await reader.read();
|
|
26
|
+
if (done) {
|
|
27
|
+
completed = true;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
const decoded = decoder.decode(value);
|
|
31
|
+
if (decoded.length > 0) {
|
|
32
|
+
yield decoded;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const flushed = decoder.flush();
|
|
36
|
+
if (flushed.length > 0) {
|
|
37
|
+
yield flushed;
|
|
38
|
+
}
|
|
39
|
+
} finally {
|
|
40
|
+
if (!completed) {
|
|
41
|
+
await reader.cancel().catch(() => void 0);
|
|
42
|
+
}
|
|
43
|
+
reader.releaseLock();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function isReadableStream(source) {
|
|
47
|
+
return typeof source.getReader === "function";
|
|
48
|
+
}
|
|
49
|
+
function sourceToStrings(source) {
|
|
50
|
+
return isReadableStream(source) ? readableStreamToStrings(source) : source;
|
|
51
|
+
}
|
|
52
|
+
function errorFromUnknown(error) {
|
|
53
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
54
|
+
}
|
|
55
|
+
function prefixedError(message) {
|
|
56
|
+
return new Error(`llm-stream-assemble: ${message}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/core/assembly/process-payload.ts
|
|
60
|
+
function isDoneMarker(payload) {
|
|
61
|
+
return payload.trim() === "[DONE]";
|
|
62
|
+
}
|
|
63
|
+
function processPayload(payload, assembler, adapter, options) {
|
|
64
|
+
if (isDoneMarker(payload)) {
|
|
65
|
+
return { kind: "done-marker" };
|
|
66
|
+
}
|
|
67
|
+
let chunks;
|
|
68
|
+
try {
|
|
69
|
+
chunks = adapter.parseChunk(payload);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (!options.recoverMalformed) {
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
kind: "recoverable-error",
|
|
76
|
+
event: {
|
|
77
|
+
type: "error",
|
|
78
|
+
error: prefixedError(errorFromUnknown(error).message),
|
|
79
|
+
recoverable: true
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const events = [];
|
|
84
|
+
for (const chunk of chunks) {
|
|
85
|
+
events.push(...assembler.push(chunk));
|
|
86
|
+
}
|
|
87
|
+
return { kind: "events", events };
|
|
88
|
+
}
|
|
89
|
+
function resolveTerminalFlush(assembler, state) {
|
|
90
|
+
if (state.aborted) {
|
|
91
|
+
return assembler.flush({ terminalReason: "aborted" });
|
|
92
|
+
}
|
|
93
|
+
if (state.sawTerminalMarker) {
|
|
94
|
+
return assembler.flush({ terminalReason: "stop" });
|
|
95
|
+
}
|
|
96
|
+
if (assembler.hasFinished()) {
|
|
97
|
+
return assembler.flush();
|
|
98
|
+
}
|
|
99
|
+
return assembler.flush({ terminalReason: "incomplete" });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/core/parse-partial-json.ts
|
|
103
|
+
function parsePartialJSON(input) {
|
|
104
|
+
const trimmed = input.trim();
|
|
105
|
+
if (trimmed.length === 0) {
|
|
106
|
+
return { complete: false };
|
|
107
|
+
}
|
|
108
|
+
const complete = parseComplete(trimmed);
|
|
109
|
+
if (complete.ok) {
|
|
110
|
+
return { complete: true, value: complete.value };
|
|
111
|
+
}
|
|
112
|
+
const end = findFirstCompleteValueEnd(trimmed);
|
|
113
|
+
if (end !== void 0) {
|
|
114
|
+
const parsed = parseComplete(trimmed.slice(0, end));
|
|
115
|
+
if (parsed.ok) {
|
|
116
|
+
return { complete: false, value: parsed.value };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
for (const candidate of repairedCandidates(trimmed)) {
|
|
120
|
+
const parsed = parseComplete(candidate);
|
|
121
|
+
if (parsed.ok) {
|
|
122
|
+
return { complete: false, value: parsed.value };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { complete: false };
|
|
126
|
+
}
|
|
127
|
+
function parseComplete(input) {
|
|
128
|
+
try {
|
|
129
|
+
return { ok: true, value: JSON.parse(input) };
|
|
130
|
+
} catch {
|
|
131
|
+
return { ok: false };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function findFirstCompleteValueEnd(input) {
|
|
135
|
+
let inString = false;
|
|
136
|
+
let escaped = false;
|
|
137
|
+
const stack = [];
|
|
138
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
139
|
+
const char = input[index];
|
|
140
|
+
if (inString) {
|
|
141
|
+
if (escaped) {
|
|
142
|
+
escaped = false;
|
|
143
|
+
} else if (char === "\\") {
|
|
144
|
+
escaped = true;
|
|
145
|
+
} else if (char === '"') {
|
|
146
|
+
inString = false;
|
|
147
|
+
if (stack.length === 0) {
|
|
148
|
+
return index + 1;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (char === '"') {
|
|
154
|
+
inString = true;
|
|
155
|
+
} else if (char === "{" || char === "[") {
|
|
156
|
+
stack.push(char === "{" ? "}" : "]");
|
|
157
|
+
} else if (char === "}" || char === "]") {
|
|
158
|
+
if (stack.pop() !== char) return void 0;
|
|
159
|
+
if (stack.length === 0) {
|
|
160
|
+
return index + 1;
|
|
161
|
+
}
|
|
162
|
+
} else if (stack.length === 0 && isPrimitiveTerminator(input, index)) {
|
|
163
|
+
return index;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return void 0;
|
|
167
|
+
}
|
|
168
|
+
function isPrimitiveTerminator(input, index) {
|
|
169
|
+
if (!/[\s,}\]]/.test(input[index] ?? "")) return false;
|
|
170
|
+
const prefix = input.slice(0, index).trim();
|
|
171
|
+
if (prefix.length === 0) return false;
|
|
172
|
+
return parseComplete(prefix).ok;
|
|
173
|
+
}
|
|
174
|
+
function repairedCandidates(input) {
|
|
175
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
176
|
+
const base = closeContainers(closeOpenString(input));
|
|
177
|
+
candidates.add(base);
|
|
178
|
+
for (const trimmed of trimDanglingValues(input)) {
|
|
179
|
+
candidates.add(closeContainers(closeOpenString(trimmed)));
|
|
180
|
+
}
|
|
181
|
+
return [...candidates];
|
|
182
|
+
}
|
|
183
|
+
function closeOpenString(input) {
|
|
184
|
+
let inString = false;
|
|
185
|
+
let escaped = false;
|
|
186
|
+
for (const char of input) {
|
|
187
|
+
if (inString) {
|
|
188
|
+
if (escaped) {
|
|
189
|
+
escaped = false;
|
|
190
|
+
} else if (char === "\\") {
|
|
191
|
+
escaped = true;
|
|
192
|
+
} else if (char === '"') {
|
|
193
|
+
inString = false;
|
|
194
|
+
}
|
|
195
|
+
} else if (char === '"') {
|
|
196
|
+
inString = true;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (!inString) return input;
|
|
200
|
+
return escaped ? `${input.slice(0, -1)}"` : `${input}"`;
|
|
201
|
+
}
|
|
202
|
+
function closeContainers(input) {
|
|
203
|
+
const stack = [];
|
|
204
|
+
let inString = false;
|
|
205
|
+
let escaped = false;
|
|
206
|
+
for (const char of input) {
|
|
207
|
+
if (inString) {
|
|
208
|
+
if (escaped) {
|
|
209
|
+
escaped = false;
|
|
210
|
+
} else if (char === "\\") {
|
|
211
|
+
escaped = true;
|
|
212
|
+
} else if (char === '"') {
|
|
213
|
+
inString = false;
|
|
214
|
+
}
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (char === '"') {
|
|
218
|
+
inString = true;
|
|
219
|
+
} else if (char === "{" || char === "[") {
|
|
220
|
+
stack.push(char === "{" ? "}" : "]");
|
|
221
|
+
} else if ((char === "}" || char === "]") && stack.at(-1) === char) {
|
|
222
|
+
stack.pop();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return `${input}${stack.reverse().join("")}`;
|
|
226
|
+
}
|
|
227
|
+
function trimDanglingValues(input) {
|
|
228
|
+
const variants = [];
|
|
229
|
+
let current = input.trimEnd();
|
|
230
|
+
while (current.length > 0) {
|
|
231
|
+
const last = current.at(-1);
|
|
232
|
+
if (last === "," || last === ":") {
|
|
233
|
+
current = current.slice(0, -1).trimEnd();
|
|
234
|
+
variants.push(current);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const colon = lastTopLevelToken(current, ":");
|
|
238
|
+
const comma = lastTopLevelToken(current, ",");
|
|
239
|
+
const openObject = lastTopLevelToken(current, "{");
|
|
240
|
+
const cutAt = Math.max(colon, comma, openObject);
|
|
241
|
+
if (cutAt === -1) break;
|
|
242
|
+
current = current.slice(0, cutAt + (current[cutAt] === "{" ? 1 : 0)).trimEnd();
|
|
243
|
+
variants.push(current);
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
return variants;
|
|
247
|
+
}
|
|
248
|
+
function lastTopLevelToken(input, token) {
|
|
249
|
+
let inString = false;
|
|
250
|
+
let escaped = false;
|
|
251
|
+
let depth = 0;
|
|
252
|
+
for (let index = input.length - 1; index >= 0; index -= 1) {
|
|
253
|
+
const char = input[index];
|
|
254
|
+
if (inString) {
|
|
255
|
+
if (char === "\\" && !escaped) {
|
|
256
|
+
escaped = true;
|
|
257
|
+
} else if (char === '"' && !escaped) {
|
|
258
|
+
inString = false;
|
|
259
|
+
} else {
|
|
260
|
+
escaped = false;
|
|
261
|
+
}
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (char === '"') {
|
|
265
|
+
inString = true;
|
|
266
|
+
} else if (char === "}" || char === "]") {
|
|
267
|
+
depth += 1;
|
|
268
|
+
} else if (char === "{" || char === "[") {
|
|
269
|
+
if (depth === 0 && char === token) return index;
|
|
270
|
+
depth -= 1;
|
|
271
|
+
} else if (depth === 0 && char === token) {
|
|
272
|
+
return index;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return -1;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/core/assembler/event-assembler.ts
|
|
279
|
+
var EventAssembler = class {
|
|
280
|
+
constructor(options = {}) {
|
|
281
|
+
this.options = options;
|
|
282
|
+
}
|
|
283
|
+
options;
|
|
284
|
+
text = /* @__PURE__ */ new Map();
|
|
285
|
+
reasoning = "";
|
|
286
|
+
reasoningVariant;
|
|
287
|
+
refusal = "";
|
|
288
|
+
json = "";
|
|
289
|
+
tools = /* @__PURE__ */ new Map();
|
|
290
|
+
toolByIndex = /* @__PURE__ */ new Map();
|
|
291
|
+
finishEmitted = false;
|
|
292
|
+
push(chunk) {
|
|
293
|
+
if (this.finishEmitted) return [];
|
|
294
|
+
switch (chunk.kind) {
|
|
295
|
+
case "message-start":
|
|
296
|
+
return [
|
|
297
|
+
optionalEvent({ type: "message.start", id: chunk.id, choiceIndex: chunk.choiceIndex })
|
|
298
|
+
];
|
|
299
|
+
case "text-delta":
|
|
300
|
+
return this.pushText(chunk.text, chunk.choiceIndex);
|
|
301
|
+
case "reasoning-delta":
|
|
302
|
+
return this.pushReasoning(chunk.text, chunk.variant);
|
|
303
|
+
case "refusal-delta":
|
|
304
|
+
return this.pushRefusal(chunk.text);
|
|
305
|
+
case "json-delta":
|
|
306
|
+
return this.pushJson(chunk.delta);
|
|
307
|
+
case "tool-start":
|
|
308
|
+
return this.pushToolStart(chunk);
|
|
309
|
+
case "tool-args-delta":
|
|
310
|
+
return this.pushToolArgs(chunk);
|
|
311
|
+
case "tool-done":
|
|
312
|
+
return this.finishTool(chunk);
|
|
313
|
+
case "metadata": {
|
|
314
|
+
const { kind: _kind, ...metadata } = chunk;
|
|
315
|
+
return [optionalEvent({ type: "metadata", ...metadata })];
|
|
316
|
+
}
|
|
317
|
+
case "usage": {
|
|
318
|
+
const { kind: _kind, ...usage } = chunk;
|
|
319
|
+
return [optionalEvent({ type: "usage", ...usage })];
|
|
320
|
+
}
|
|
321
|
+
case "finish":
|
|
322
|
+
return chunk.choiceIndex === void 0 ? this.flush({ terminalReason: chunk.reason }) : this.flush({ terminalReason: chunk.reason, choiceIndex: chunk.choiceIndex });
|
|
323
|
+
case "provider-error":
|
|
324
|
+
return [this.errorEvent(chunk.error, chunk.recoverable)];
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
flush(options = {}) {
|
|
328
|
+
const events = [];
|
|
329
|
+
events.push(...this.flushText());
|
|
330
|
+
events.push(...this.flushReasoning());
|
|
331
|
+
events.push(...this.flushRefusal());
|
|
332
|
+
events.push(...this.flushJson());
|
|
333
|
+
events.push(...this.flushTools());
|
|
334
|
+
const forcedError = events.some(
|
|
335
|
+
(event) => event.type === "error" && event.recoverable === false
|
|
336
|
+
);
|
|
337
|
+
const reason = forcedError ? "error" : options.terminalReason;
|
|
338
|
+
if (reason && !this.finishEmitted) {
|
|
339
|
+
events.push(optionalEvent({ type: "finish", reason, choiceIndex: options.choiceIndex }));
|
|
340
|
+
this.finishEmitted = true;
|
|
341
|
+
}
|
|
342
|
+
return events;
|
|
343
|
+
}
|
|
344
|
+
reset() {
|
|
345
|
+
this.text = /* @__PURE__ */ new Map();
|
|
346
|
+
this.reasoning = "";
|
|
347
|
+
this.reasoningVariant = void 0;
|
|
348
|
+
this.refusal = "";
|
|
349
|
+
this.json = "";
|
|
350
|
+
this.tools.clear();
|
|
351
|
+
this.toolByIndex.clear();
|
|
352
|
+
this.finishEmitted = false;
|
|
353
|
+
}
|
|
354
|
+
hasFinished() {
|
|
355
|
+
return this.finishEmitted;
|
|
356
|
+
}
|
|
357
|
+
pushText(text, choiceIndex = 0) {
|
|
358
|
+
if (text.length === 0) return [];
|
|
359
|
+
const next = `${this.text.get(choiceIndex) ?? ""}${text}`;
|
|
360
|
+
this.assertBuffer(next, "text");
|
|
361
|
+
this.text.set(choiceIndex, next);
|
|
362
|
+
return [
|
|
363
|
+
optionalEvent({ type: "text.delta", text, choiceIndex: normalizeChoiceIndex(choiceIndex) })
|
|
364
|
+
];
|
|
365
|
+
}
|
|
366
|
+
pushReasoning(text, variant) {
|
|
367
|
+
if (text.length === 0) return [];
|
|
368
|
+
this.reasoning += text;
|
|
369
|
+
this.reasoningVariant = variant ?? this.reasoningVariant;
|
|
370
|
+
this.assertBuffer(this.reasoning, "reasoning");
|
|
371
|
+
return [optionalEvent({ type: "reasoning.delta", text, variant })];
|
|
372
|
+
}
|
|
373
|
+
pushRefusal(text) {
|
|
374
|
+
if (text.length === 0) return [];
|
|
375
|
+
this.refusal += text;
|
|
376
|
+
this.assertBuffer(this.refusal, "refusal");
|
|
377
|
+
return [{ type: "refusal.delta", text }];
|
|
378
|
+
}
|
|
379
|
+
pushJson(delta) {
|
|
380
|
+
if (delta.length === 0) return [];
|
|
381
|
+
this.json += delta;
|
|
382
|
+
this.assertBuffer(this.json, "json");
|
|
383
|
+
const partial = parsePartialJSON(this.json);
|
|
384
|
+
return [withOptional("partial", partial.value, { type: "json.delta", delta })];
|
|
385
|
+
}
|
|
386
|
+
pushToolStart(chunk) {
|
|
387
|
+
const state = this.getOrCreateTool(chunk);
|
|
388
|
+
if (chunk.id && !state.started) {
|
|
389
|
+
state.eventId = chunk.id;
|
|
390
|
+
}
|
|
391
|
+
state.name = chunk.name;
|
|
392
|
+
state.index = chunk.index;
|
|
393
|
+
state.choiceIndex = chunk.choiceIndex;
|
|
394
|
+
if (state.started) return [];
|
|
395
|
+
state.started = true;
|
|
396
|
+
return [
|
|
397
|
+
optionalEvent({
|
|
398
|
+
type: "tool_call.start",
|
|
399
|
+
id: state.eventId,
|
|
400
|
+
name: state.name,
|
|
401
|
+
index: state.index,
|
|
402
|
+
choiceIndex: state.choiceIndex
|
|
403
|
+
})
|
|
404
|
+
];
|
|
405
|
+
}
|
|
406
|
+
pushToolArgs(chunk) {
|
|
407
|
+
if (chunk.delta.length === 0) return [];
|
|
408
|
+
const state = this.getOrCreateTool(chunk);
|
|
409
|
+
state.args += chunk.delta;
|
|
410
|
+
this.assertBuffer(state.args, "tool args");
|
|
411
|
+
const partial = parsePartialJSON(state.args);
|
|
412
|
+
return [
|
|
413
|
+
withOptional("partial", partial.value, {
|
|
414
|
+
type: "tool_call.args.delta",
|
|
415
|
+
id: state.eventId,
|
|
416
|
+
delta: chunk.delta
|
|
417
|
+
})
|
|
418
|
+
];
|
|
419
|
+
}
|
|
420
|
+
finishTool(chunk) {
|
|
421
|
+
const state = this.getOrCreateTool(chunk);
|
|
422
|
+
const done = this.toolDoneEvent(state);
|
|
423
|
+
this.deleteTool(state);
|
|
424
|
+
return [done];
|
|
425
|
+
}
|
|
426
|
+
flushText() {
|
|
427
|
+
const events = [];
|
|
428
|
+
for (const [choiceIndex, text] of this.text) {
|
|
429
|
+
if (text.length > 0) {
|
|
430
|
+
events.push(
|
|
431
|
+
optionalEvent({
|
|
432
|
+
type: "text.done",
|
|
433
|
+
text,
|
|
434
|
+
choiceIndex: normalizeChoiceIndex(choiceIndex)
|
|
435
|
+
})
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
this.text.clear();
|
|
440
|
+
return events;
|
|
441
|
+
}
|
|
442
|
+
flushReasoning() {
|
|
443
|
+
if (this.reasoning.length === 0) return [];
|
|
444
|
+
const event = optionalEvent({
|
|
445
|
+
type: "reasoning.done",
|
|
446
|
+
text: this.reasoning,
|
|
447
|
+
variant: this.reasoningVariant
|
|
448
|
+
});
|
|
449
|
+
this.reasoning = "";
|
|
450
|
+
this.reasoningVariant = void 0;
|
|
451
|
+
return [event];
|
|
452
|
+
}
|
|
453
|
+
flushRefusal() {
|
|
454
|
+
if (this.refusal.length === 0) return [];
|
|
455
|
+
const event = { type: "refusal.done", text: this.refusal };
|
|
456
|
+
this.refusal = "";
|
|
457
|
+
return [event];
|
|
458
|
+
}
|
|
459
|
+
flushJson() {
|
|
460
|
+
if (this.json.length === 0) return [];
|
|
461
|
+
const parsed = parsePartialJSON(this.json);
|
|
462
|
+
this.json = "";
|
|
463
|
+
if (parsed.complete) {
|
|
464
|
+
return [{ type: "json.done", value: parsed.value }];
|
|
465
|
+
}
|
|
466
|
+
return [this.errorEvent(prefixedError("json stream ended with invalid JSON"), false)];
|
|
467
|
+
}
|
|
468
|
+
flushTools() {
|
|
469
|
+
const events = [];
|
|
470
|
+
for (const state of this.tools.values()) {
|
|
471
|
+
events.push(this.toolDoneEvent(state));
|
|
472
|
+
}
|
|
473
|
+
this.tools.clear();
|
|
474
|
+
this.toolByIndex.clear();
|
|
475
|
+
return events;
|
|
476
|
+
}
|
|
477
|
+
toolDoneEvent(state) {
|
|
478
|
+
const parsed = parsePartialJSON(state.args);
|
|
479
|
+
if (parsed.complete) {
|
|
480
|
+
return { type: "tool_call.done", id: state.eventId, name: state.name, args: parsed.value };
|
|
481
|
+
}
|
|
482
|
+
if (this.options.strictToolArgs) {
|
|
483
|
+
throw prefixedError(`tool args for ${state.name} ended with invalid JSON`);
|
|
484
|
+
}
|
|
485
|
+
return { type: "tool_call.done", id: state.eventId, name: state.name, args: state.args };
|
|
486
|
+
}
|
|
487
|
+
getOrCreateTool(chunk) {
|
|
488
|
+
const choiceIndex = chunk.choiceIndex ?? 0;
|
|
489
|
+
const indexKey = chunk.index === void 0 ? void 0 : `${choiceIndex}:${chunk.index}`;
|
|
490
|
+
let key = (indexKey ? this.toolByIndex.get(indexKey) : void 0) || chunk.id;
|
|
491
|
+
if (!key && indexKey) {
|
|
492
|
+
key = `tool:${indexKey}`;
|
|
493
|
+
this.toolByIndex.set(indexKey, key);
|
|
494
|
+
}
|
|
495
|
+
key ??= chunk.id ?? `tool:${choiceIndex}:${this.tools.size}`;
|
|
496
|
+
let state = this.tools.get(key);
|
|
497
|
+
if (!state) {
|
|
498
|
+
state = {
|
|
499
|
+
eventId: chunk.id || key,
|
|
500
|
+
name: "unknown",
|
|
501
|
+
args: "",
|
|
502
|
+
index: chunk.index,
|
|
503
|
+
choiceIndex: normalizeChoiceIndex(choiceIndex),
|
|
504
|
+
started: false
|
|
505
|
+
};
|
|
506
|
+
this.tools.set(key, state);
|
|
507
|
+
}
|
|
508
|
+
if (chunk.id && !state.started && state.eventId.startsWith("tool:")) {
|
|
509
|
+
this.tools.delete(key);
|
|
510
|
+
state.eventId = chunk.id;
|
|
511
|
+
this.tools.set(chunk.id, state);
|
|
512
|
+
if (indexKey) {
|
|
513
|
+
this.toolByIndex.set(indexKey, chunk.id);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return state;
|
|
517
|
+
}
|
|
518
|
+
deleteTool(state) {
|
|
519
|
+
this.tools.delete(state.eventId);
|
|
520
|
+
if (state.index !== void 0) {
|
|
521
|
+
this.toolByIndex.delete(`${state.choiceIndex ?? 0}:${state.index}`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
assertBuffer(value, label) {
|
|
525
|
+
if (this.options.maxBufferBytes === void 0) return;
|
|
526
|
+
if (utf8ByteLength(value) > this.options.maxBufferBytes) {
|
|
527
|
+
throw prefixedError(`${label} buffer exceeded maxBufferBytes`);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
errorEvent(error, recoverable) {
|
|
531
|
+
const source = errorFromUnknown(error);
|
|
532
|
+
if (!this.options.sanitizeErrors) {
|
|
533
|
+
return optionalEvent({ type: "error", error: source, recoverable });
|
|
534
|
+
}
|
|
535
|
+
return optionalEvent({
|
|
536
|
+
type: "error",
|
|
537
|
+
error: new Error("An error occurred while processing the stream."),
|
|
538
|
+
recoverable,
|
|
539
|
+
sanitized: "An error occurred while processing the stream."
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
function optionalEvent(event) {
|
|
544
|
+
return Object.fromEntries(
|
|
545
|
+
Object.entries(event).filter(([, value]) => value !== void 0 && value !== "kind")
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
function withOptional(key, value, target) {
|
|
549
|
+
if (value === void 0) return target;
|
|
550
|
+
return { ...target, [key]: value };
|
|
551
|
+
}
|
|
552
|
+
function normalizeChoiceIndex(choiceIndex) {
|
|
553
|
+
return choiceIndex === 0 ? void 0 : choiceIndex;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/core/assemble-payloads.ts
|
|
557
|
+
function assembleFromPayloads(payloads, adapter, options = {}) {
|
|
558
|
+
return assembleFromPayloadsGenerator(payloads, adapter, options);
|
|
559
|
+
}
|
|
560
|
+
async function* assembleFromPayloadsGenerator(payloads, adapter, options) {
|
|
561
|
+
const assembler = new EventAssembler(options);
|
|
562
|
+
const iterator = payloads[Symbol.asyncIterator]();
|
|
563
|
+
let sawTerminalMarker = false;
|
|
564
|
+
try {
|
|
565
|
+
while (true) {
|
|
566
|
+
if (options.signal?.aborted) {
|
|
567
|
+
yield* assembler.flush({ terminalReason: "aborted" });
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
const item = await iterator.next();
|
|
571
|
+
if (item.done) break;
|
|
572
|
+
const result = processPayload(item.value, assembler, adapter, options);
|
|
573
|
+
if (result.kind === "done-marker") {
|
|
574
|
+
sawTerminalMarker = true;
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
if (result.kind === "recoverable-error") {
|
|
578
|
+
yield result.event;
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
yield* result.events;
|
|
582
|
+
}
|
|
583
|
+
yield* resolveTerminalFlush(assembler, {
|
|
584
|
+
sawTerminalMarker,
|
|
585
|
+
aborted: options.signal?.aborted === true
|
|
586
|
+
});
|
|
587
|
+
} finally {
|
|
588
|
+
await iterator.return?.();
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// src/core/utils/sse-parser.ts
|
|
593
|
+
var SSEParser = class {
|
|
594
|
+
lineBuffer = "";
|
|
595
|
+
dataLines = [];
|
|
596
|
+
push(chunk) {
|
|
597
|
+
const payloads = [];
|
|
598
|
+
this.lineBuffer += chunk;
|
|
599
|
+
while (true) {
|
|
600
|
+
const lineEnd = this.findLineEnd();
|
|
601
|
+
if (!lineEnd) break;
|
|
602
|
+
const line = this.lineBuffer.slice(0, lineEnd.index);
|
|
603
|
+
this.lineBuffer = this.lineBuffer.slice(lineEnd.nextIndex);
|
|
604
|
+
const payload = this.processLine(line);
|
|
605
|
+
if (payload !== void 0) {
|
|
606
|
+
payloads.push(payload);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return payloads;
|
|
610
|
+
}
|
|
611
|
+
flush() {
|
|
612
|
+
const payloads = [];
|
|
613
|
+
if (this.lineBuffer.length > 0) {
|
|
614
|
+
const payload2 = this.processLine(this.lineBuffer);
|
|
615
|
+
this.lineBuffer = "";
|
|
616
|
+
if (payload2 !== void 0) {
|
|
617
|
+
payloads.push(payload2);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
const payload = this.dispatch();
|
|
621
|
+
if (payload !== void 0) {
|
|
622
|
+
payloads.push(payload);
|
|
623
|
+
}
|
|
624
|
+
return payloads;
|
|
625
|
+
}
|
|
626
|
+
findLineEnd() {
|
|
627
|
+
const lf = this.lineBuffer.indexOf("\n");
|
|
628
|
+
const cr = this.lineBuffer.indexOf("\r");
|
|
629
|
+
if (lf === -1 && cr === -1) return void 0;
|
|
630
|
+
if (cr !== -1 && (lf === -1 || cr < lf)) {
|
|
631
|
+
const nextIndex = this.lineBuffer[cr + 1] === "\n" ? cr + 2 : cr + 1;
|
|
632
|
+
return { index: cr, nextIndex };
|
|
633
|
+
}
|
|
634
|
+
return { index: lf, nextIndex: lf + 1 };
|
|
635
|
+
}
|
|
636
|
+
processLine(line) {
|
|
637
|
+
if (line === "") {
|
|
638
|
+
return this.dispatch();
|
|
639
|
+
}
|
|
640
|
+
if (line.startsWith(":")) {
|
|
641
|
+
return void 0;
|
|
642
|
+
}
|
|
643
|
+
const colon = line.indexOf(":");
|
|
644
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
645
|
+
let value = colon === -1 ? "" : line.slice(colon + 1);
|
|
646
|
+
if (value.startsWith(" ")) {
|
|
647
|
+
value = value.slice(1);
|
|
648
|
+
}
|
|
649
|
+
if (field === "data") {
|
|
650
|
+
this.dataLines.push(value);
|
|
651
|
+
}
|
|
652
|
+
return void 0;
|
|
653
|
+
}
|
|
654
|
+
dispatch() {
|
|
655
|
+
if (this.dataLines.length === 0) {
|
|
656
|
+
return void 0;
|
|
657
|
+
}
|
|
658
|
+
const payload = this.dataLines.join("\n");
|
|
659
|
+
this.dataLines = [];
|
|
660
|
+
return payload.length > 0 ? payload : void 0;
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
// src/core/parse-sse.ts
|
|
665
|
+
function parseSSE(source) {
|
|
666
|
+
return parseSSEGenerator(source);
|
|
667
|
+
}
|
|
668
|
+
async function* parseSSEGenerator(source) {
|
|
669
|
+
const parser = new SSEParser();
|
|
670
|
+
for await (const chunk of sourceToStrings(source)) {
|
|
671
|
+
for (const payload of parser.push(chunk)) {
|
|
672
|
+
yield payload;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
for (const payload of parser.flush()) {
|
|
676
|
+
yield payload;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// src/core/assemble-stream.ts
|
|
681
|
+
function assembleStream(source, adapter, options) {
|
|
682
|
+
return assembleFromPayloads(parseSSE(source), adapter, options);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// src/core/assemble-response.ts
|
|
686
|
+
function assembleResponse(body, adapter, options = {}) {
|
|
687
|
+
if (!adapter.parseResponse) {
|
|
688
|
+
throw prefixedError("adapter.parseResponse is required for assembleResponse");
|
|
689
|
+
}
|
|
690
|
+
const assembler = new EventAssembler(options);
|
|
691
|
+
const events = [];
|
|
692
|
+
for (const chunk of adapter.parseResponse(body)) {
|
|
693
|
+
events.push(...assembler.push(chunk));
|
|
694
|
+
}
|
|
695
|
+
events.push(...assembler.flush(assembler.hasFinished() ? void 0 : { terminalReason: "stop" }));
|
|
696
|
+
return events;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// src/core/assemble-from-file.ts
|
|
700
|
+
function assembleFromFile(path, adapter, options = {}) {
|
|
701
|
+
return assembleFromFileGenerator(path, adapter, options);
|
|
702
|
+
}
|
|
703
|
+
async function* assembleFromFileGenerator(path, adapter, options) {
|
|
704
|
+
const format = options.format ?? inferFormat(path);
|
|
705
|
+
const content = await readFixture(path);
|
|
706
|
+
if (format === "sse") {
|
|
707
|
+
yield* assembleStream(stringIterable(content), adapter, options);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
let body;
|
|
711
|
+
try {
|
|
712
|
+
body = JSON.parse(content);
|
|
713
|
+
} catch (error) {
|
|
714
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
715
|
+
throw new Error(
|
|
716
|
+
`llm-stream-assemble: assembleFromFile failed to parse JSON ${path}: ${message}`
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
for (const event of assembleResponse(body, adapter, options)) {
|
|
720
|
+
yield event;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
function inferFormat(path) {
|
|
724
|
+
if (path.endsWith(".sse")) return "sse";
|
|
725
|
+
if (path.endsWith(".json")) return "json";
|
|
726
|
+
throw new Error(`llm-stream-assemble: assembleFromFile cannot infer format for ${path}`);
|
|
727
|
+
}
|
|
728
|
+
async function readFixture(path) {
|
|
729
|
+
try {
|
|
730
|
+
const { readFile } = await import('fs/promises');
|
|
731
|
+
return await readFile(path, "utf8");
|
|
732
|
+
} catch (error) {
|
|
733
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
734
|
+
throw new Error(`llm-stream-assemble: assembleFromFile failed to read ${path}: ${message}`);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
async function* stringIterable(value) {
|
|
738
|
+
yield value;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// src/core/create-assembly-transform.ts
|
|
742
|
+
function createAssemblyTransform(adapter, options = {}) {
|
|
743
|
+
const decoder = new Utf8StreamDecoder();
|
|
744
|
+
const parser = new SSEParser();
|
|
745
|
+
const assembler = new EventAssembler(options);
|
|
746
|
+
let sawTerminalMarker = false;
|
|
747
|
+
let aborted = false;
|
|
748
|
+
const emit = (controller, events) => {
|
|
749
|
+
for (const event of events) {
|
|
750
|
+
controller.enqueue(event);
|
|
751
|
+
}
|
|
752
|
+
};
|
|
753
|
+
const handlePayload = (payload, controller) => {
|
|
754
|
+
const result = processPayload(payload, assembler, adapter, options);
|
|
755
|
+
if (result.kind === "done-marker") {
|
|
756
|
+
sawTerminalMarker = true;
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
if (result.kind === "recoverable-error") {
|
|
760
|
+
controller.enqueue(result.event);
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
emit(controller, result.events);
|
|
764
|
+
};
|
|
765
|
+
return new TransformStream({
|
|
766
|
+
start(controller) {
|
|
767
|
+
options.signal?.addEventListener(
|
|
768
|
+
"abort",
|
|
769
|
+
() => {
|
|
770
|
+
if (aborted || assembler.hasFinished()) return;
|
|
771
|
+
aborted = true;
|
|
772
|
+
emit(controller, assembler.flush({ terminalReason: "aborted" }));
|
|
773
|
+
controller.terminate();
|
|
774
|
+
},
|
|
775
|
+
{ once: true }
|
|
776
|
+
);
|
|
777
|
+
},
|
|
778
|
+
transform(chunk, controller) {
|
|
779
|
+
if (aborted || options.signal?.aborted) {
|
|
780
|
+
aborted = true;
|
|
781
|
+
emit(controller, assembler.flush({ terminalReason: "aborted" }));
|
|
782
|
+
controller.terminate();
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
for (const payload of parser.push(decoder.decode(chunk))) {
|
|
786
|
+
handlePayload(payload, controller);
|
|
787
|
+
}
|
|
788
|
+
},
|
|
789
|
+
flush(controller) {
|
|
790
|
+
if (aborted || options.signal?.aborted) {
|
|
791
|
+
emit(controller, assembler.flush({ terminalReason: "aborted" }));
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const decoded = decoder.flush();
|
|
795
|
+
if (decoded.length > 0) {
|
|
796
|
+
for (const payload of parser.push(decoded)) {
|
|
797
|
+
handlePayload(payload, controller);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
for (const payload of parser.flush()) {
|
|
801
|
+
handlePayload(payload, controller);
|
|
802
|
+
}
|
|
803
|
+
emit(controller, resolveTerminalFlush(assembler, { sawTerminalMarker, aborted: false }));
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
exports.assembleFromFile = assembleFromFile;
|
|
809
|
+
exports.assembleFromPayloads = assembleFromPayloads;
|
|
810
|
+
exports.assembleResponse = assembleResponse;
|
|
811
|
+
exports.assembleStream = assembleStream;
|
|
812
|
+
exports.createAssemblyTransform = createAssemblyTransform;
|
|
813
|
+
exports.parsePartialJSON = parsePartialJSON;
|
|
814
|
+
exports.parseSSE = parseSSE;
|
|
815
|
+
//# sourceMappingURL=index.cjs.map
|
|
816
|
+
//# sourceMappingURL=index.cjs.map
|