@codehz/ai 0.3.0 → 0.4.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/dist/index.d.mts +68 -53
- package/dist/index.mjs +671 -813
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/adapters/chat-completions.ts +150 -244
- package/src/adapters/messages.ts +171 -274
- package/src/adapters/ollama.ts +118 -205
- package/src/adapters/responses.ts +135 -226
- package/src/helpers/adapter-auxiliary.ts +1 -23
- package/src/helpers/adapter-base.ts +38 -4
- package/src/helpers/incremental-stream-parser.ts +58 -0
- package/src/helpers/index.ts +20 -7
- package/src/helpers/mapping.ts +0 -8
- package/src/helpers/provider-stream.ts +147 -0
- package/src/helpers/request-mapper.ts +30 -1
- package/src/helpers/usage-mapping.ts +36 -39
- package/src/helpers/sse-parser.ts +0 -113
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { AdapterBase } from "../helpers/adapter-base.js";
|
|
12
|
-
import {
|
|
12
|
+
import { AIRequestError } from "../core/errors.js";
|
|
13
13
|
import {
|
|
14
14
|
textBlock,
|
|
15
15
|
messageItem,
|
|
@@ -18,14 +18,18 @@ import {
|
|
|
18
18
|
opaqueItem,
|
|
19
19
|
replayFromOutput,
|
|
20
20
|
mapStopReason,
|
|
21
|
-
contentBlocksToText,
|
|
22
21
|
} from "../helpers/mapping.js";
|
|
23
|
-
import {
|
|
24
|
-
import { assertOpaqueReplayEnvelope, providerHttpError } from "../helpers/adapter-security.js";
|
|
22
|
+
import { assertOpaqueReplayEnvelope } from "../helpers/adapter-security.js";
|
|
25
23
|
import { usageFromChatCompletions } from "../helpers/usage-mapping.js";
|
|
26
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
NormalizedRequestMapper,
|
|
26
|
+
createChatCompletionsSseParser,
|
|
27
|
+
openProviderJsonStream,
|
|
28
|
+
iterateProviderStreamBatches,
|
|
29
|
+
createCompletionGate,
|
|
30
|
+
} from "../helpers/index.js";
|
|
27
31
|
|
|
28
|
-
import type { NormalizedRequest, AIStreamEvent, EventFactory, OutputItem, FetchFn } from "../index.js";
|
|
32
|
+
import type { NormalizedRequest, AIStreamEvent, EventFactory, OutputItem, FetchFn, StopReason } from "../index.js";
|
|
29
33
|
|
|
30
34
|
// ── 类型 ──────────────────────────────────────────────────────
|
|
31
35
|
|
|
@@ -260,9 +264,7 @@ export class ChatCompletionsAdapter extends AdapterBase {
|
|
|
260
264
|
switch (item.type) {
|
|
261
265
|
case "message": {
|
|
262
266
|
const role = item.role;
|
|
263
|
-
const text =
|
|
264
|
-
mapper.ensureTextBlocks(item.content, `input message (${item.role}) content`),
|
|
265
|
-
);
|
|
267
|
+
const text = mapper.textFromBlocks(item.content, `input message (${item.role}) content`);
|
|
266
268
|
messages.push({ role, content: text || null });
|
|
267
269
|
break;
|
|
268
270
|
}
|
|
@@ -289,7 +291,7 @@ export class ChatCompletionsAdapter extends AdapterBase {
|
|
|
289
291
|
role: "tool",
|
|
290
292
|
tool_call_id: item.callId,
|
|
291
293
|
name: item.toolName,
|
|
292
|
-
content:
|
|
294
|
+
content: mapper.textFromBlocks(item.content, `tool_result ${item.callId} content`),
|
|
293
295
|
});
|
|
294
296
|
break;
|
|
295
297
|
}
|
|
@@ -298,7 +300,7 @@ export class ChatCompletionsAdapter extends AdapterBase {
|
|
|
298
300
|
// Convert to a text message for best-effort
|
|
299
301
|
messages.push({
|
|
300
302
|
role: "assistant",
|
|
301
|
-
content:
|
|
303
|
+
content: mapper.textFromBlocks(item.content, "reasoning content"),
|
|
302
304
|
});
|
|
303
305
|
break;
|
|
304
306
|
}
|
|
@@ -333,26 +335,23 @@ export class ChatCompletionsAdapter extends AdapterBase {
|
|
|
333
335
|
n: 1,
|
|
334
336
|
};
|
|
335
337
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
338
|
+
body.tools = mapper.mapToolsIfPresent(
|
|
339
|
+
request.tools,
|
|
340
|
+
(t): ChatTool => ({
|
|
341
|
+
type: "function",
|
|
342
|
+
function: {
|
|
343
|
+
name: t.name,
|
|
344
|
+
description: t.description,
|
|
345
|
+
parameters: t.inputSchema as Record<string, unknown>,
|
|
346
|
+
},
|
|
347
|
+
}),
|
|
348
|
+
);
|
|
348
349
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
355
|
-
}
|
|
350
|
+
body.tool_choice = mapper.mapToolChoice<Exclude<ChatRequest["tool_choice"], undefined>>(request.toolChoice, {
|
|
351
|
+
auto: "auto",
|
|
352
|
+
none: "none",
|
|
353
|
+
tool: (name) => ({ type: "function" as const, function: { name } }),
|
|
354
|
+
});
|
|
356
355
|
|
|
357
356
|
if (request.temperature !== undefined) body.temperature = request.temperature;
|
|
358
357
|
if (request.maxOutputTokens !== undefined) body.max_tokens = request.maxOutputTokens;
|
|
@@ -369,46 +368,21 @@ export class ChatCompletionsAdapter extends AdapterBase {
|
|
|
369
368
|
request: NormalizedRequest,
|
|
370
369
|
): AsyncIterable<AIStreamEvent> {
|
|
371
370
|
const auxiliary = this.createAuxiliaryState(request);
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
});
|
|
384
|
-
} catch (err) {
|
|
385
|
-
throw new AIProviderError(err instanceof Error ? err.message : String(err), "PROVIDER_ERROR");
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
if (!response.ok) {
|
|
389
|
-
const errorBody = await response.text().catch(() => "");
|
|
390
|
-
throw providerHttpError(response.status, errorBody);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
const reader = response.body?.getReader();
|
|
394
|
-
if (!reader) {
|
|
395
|
-
throw new AIStreamError("Response body is not readable", "STREAM_ERROR");
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
const parser = new IncrementalStreamParser<ChatChunk>(splitLines, (item: string) => {
|
|
399
|
-
const trimmed = item.trim();
|
|
400
|
-
if (!trimmed.startsWith("data: ")) return { status: "ignored" };
|
|
401
|
-
const data = trimmed.slice(6).trim();
|
|
402
|
-
if (data === "[DONE]") return { status: "ignored" };
|
|
403
|
-
try {
|
|
404
|
-
return { status: "parsed", value: JSON.parse(data) as ChatChunk };
|
|
405
|
-
} catch {
|
|
406
|
-
return { status: "malformed" };
|
|
407
|
-
}
|
|
371
|
+
const gate = createCompletionGate();
|
|
372
|
+
|
|
373
|
+
const { reader } = await openProviderJsonStream({
|
|
374
|
+
fetchFn: this.fetchFn,
|
|
375
|
+
url: `${this.baseUrl}/chat/completions`,
|
|
376
|
+
headers: {
|
|
377
|
+
"Content-Type": "application/json",
|
|
378
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
379
|
+
},
|
|
380
|
+
body: providerRequest,
|
|
381
|
+
signal: request.signal,
|
|
408
382
|
});
|
|
409
383
|
|
|
384
|
+
const parser = createChatCompletionsSseParser<ChatChunk>();
|
|
410
385
|
const output: OutputItem[] = [];
|
|
411
|
-
let streamDone = false;
|
|
412
386
|
|
|
413
387
|
// 累积状态 — 支持多 choice,此处只取 index 0
|
|
414
388
|
let responseId: string | undefined;
|
|
@@ -418,9 +392,7 @@ export class ChatCompletionsAdapter extends AdapterBase {
|
|
|
418
392
|
let currentReasoningId = "";
|
|
419
393
|
let hasMessageStarted = false;
|
|
420
394
|
let hasReasoningStarted = false;
|
|
421
|
-
let completedEmitted = false;
|
|
422
395
|
let warnedNonZeroChoice = false;
|
|
423
|
-
const buildResponse = this.buildResponse.bind(this);
|
|
424
396
|
|
|
425
397
|
// tool_calls 累积: tool call index → { id, name, args }
|
|
426
398
|
const pendingToolCalls = new Map<number, PendingToolCall>();
|
|
@@ -468,19 +440,17 @@ export class ChatCompletionsAdapter extends AdapterBase {
|
|
|
468
440
|
};
|
|
469
441
|
|
|
470
442
|
const emitCompleted = async function* (
|
|
471
|
-
|
|
443
|
+
this: ChatCompletionsAdapter,
|
|
444
|
+
stopReason: StopReason | undefined,
|
|
472
445
|
assistantReplayMessage: ChatMessage | null,
|
|
473
446
|
rawResponseId: string | undefined,
|
|
474
447
|
): AsyncIterable<AIStreamEvent> {
|
|
475
|
-
if (
|
|
448
|
+
if (!gate.tryComplete()) {
|
|
476
449
|
yield factory.responseWarning("Duplicate finish signal ignored", "DUPLICATE_FINISH");
|
|
477
450
|
return;
|
|
478
451
|
}
|
|
479
452
|
|
|
480
|
-
completedEmitted = true;
|
|
481
|
-
|
|
482
453
|
const replay = [...replayFromOutput(output)];
|
|
483
|
-
|
|
484
454
|
if (assistantReplayMessage) {
|
|
485
455
|
replay.push(
|
|
486
456
|
opaqueItem("chat.completions", "replay", {
|
|
@@ -490,209 +460,145 @@ export class ChatCompletionsAdapter extends AdapterBase {
|
|
|
490
460
|
);
|
|
491
461
|
}
|
|
492
462
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
const finalResponse = buildResponse(
|
|
499
|
-
request,
|
|
500
|
-
{
|
|
501
|
-
output,
|
|
502
|
-
replay,
|
|
503
|
-
stopReason,
|
|
504
|
-
usage: auxiliaryResult.usage,
|
|
505
|
-
billing: auxiliaryResult.billing,
|
|
506
|
-
auxiliary: auxiliaryResult.auxiliary,
|
|
507
|
-
warnings: auxiliaryResult.warnings,
|
|
508
|
-
metadataSources: auxiliaryResult.metadataSources,
|
|
509
|
-
rawResponseId,
|
|
510
|
-
},
|
|
511
|
-
factory,
|
|
512
|
-
);
|
|
513
|
-
yield factory.responseCompleted({
|
|
514
|
-
replay: finalResponse.replay,
|
|
515
|
-
stopReason: finalResponse.stopReason,
|
|
516
|
-
trace: finalResponse.backend,
|
|
517
|
-
usage: finalResponse.usage,
|
|
518
|
-
billing: finalResponse.billing,
|
|
519
|
-
auxiliary: finalResponse.auxiliary,
|
|
520
|
-
warnings: finalResponse.warnings,
|
|
463
|
+
yield* this.emitStreamCompleted(factory, request, auxiliary, {
|
|
464
|
+
output,
|
|
465
|
+
replay,
|
|
466
|
+
stopReason,
|
|
467
|
+
rawResponseId,
|
|
521
468
|
});
|
|
522
|
-
};
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
});
|
|
540
|
-
if (malformedWarning) {
|
|
541
|
-
yield malformedWarning;
|
|
469
|
+
}.bind(this);
|
|
470
|
+
|
|
471
|
+
for await (const batch of iterateProviderStreamBatches({
|
|
472
|
+
reader,
|
|
473
|
+
parser,
|
|
474
|
+
factory,
|
|
475
|
+
providerLabel: "Chat Completions",
|
|
476
|
+
transportLabel: "SSE event(s)",
|
|
477
|
+
incompleteMessage: "Stream ended with an incomplete Chat Completions SSE frame",
|
|
478
|
+
})) {
|
|
479
|
+
for (const warning of batch.warnings) yield warning;
|
|
480
|
+
|
|
481
|
+
for (const chunk of batch.items) {
|
|
482
|
+
responseId = chunk.id;
|
|
483
|
+
|
|
484
|
+
if (chunk.usage) {
|
|
485
|
+
auxiliary.recordUsage(usageFromChatCompletions(chunk.usage), "final", chunk.usage);
|
|
542
486
|
}
|
|
543
487
|
|
|
544
|
-
for (const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
488
|
+
for (const choice of chunk.choices) {
|
|
489
|
+
if (choice.index !== 0) {
|
|
490
|
+
if (!warnedNonZeroChoice) {
|
|
491
|
+
yield factory.responseWarning(
|
|
492
|
+
`Chat Completions returned choice index ${choice.index}; only the first choice (index 0) is supported. This choice is ignored.`,
|
|
493
|
+
"MULTIPLE_CHOICES_IGNORED",
|
|
494
|
+
);
|
|
495
|
+
warnedNonZeroChoice = true;
|
|
496
|
+
}
|
|
497
|
+
continue;
|
|
550
498
|
}
|
|
551
499
|
|
|
552
|
-
|
|
553
|
-
if (choice.
|
|
554
|
-
|
|
555
|
-
yield factory.responseWarning(
|
|
556
|
-
`Chat Completions returned choice index ${choice.index}; only the first choice (index 0) is supported. This choice is ignored.`,
|
|
557
|
-
"MULTIPLE_CHOICES_IGNORED",
|
|
558
|
-
);
|
|
559
|
-
warnedNonZeroChoice = true;
|
|
560
|
-
}
|
|
561
|
-
continue;
|
|
500
|
+
if (gate.completed) {
|
|
501
|
+
if (choice.finish_reason) {
|
|
502
|
+
yield factory.responseWarning("Duplicate finish signal ignored", "DUPLICATE_FINISH");
|
|
562
503
|
}
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
563
506
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
568
|
-
continue;
|
|
569
|
-
}
|
|
507
|
+
const delta = choice.delta;
|
|
508
|
+
const finishReason = choice.finish_reason;
|
|
509
|
+
const reasoningDeltas = extractReasoningDeltas(delta);
|
|
570
510
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
currentMessageId = `msg-${chunk.id}`;
|
|
578
|
-
hasMessageStarted = true;
|
|
579
|
-
accumulatedContent = "";
|
|
580
|
-
};
|
|
581
|
-
|
|
582
|
-
// 处理 third-party reasoning delta
|
|
583
|
-
if (reasoningDeltas.length > 0) {
|
|
584
|
-
if (!hasReasoningStarted) {
|
|
585
|
-
currentReasoningId = `reason-${chunk.id}`;
|
|
586
|
-
hasReasoningStarted = true;
|
|
587
|
-
accumulatedReasoning = "";
|
|
588
|
-
yield factory.reasoningStarted(currentReasoningId, "full");
|
|
589
|
-
}
|
|
511
|
+
const ensureMessageStarted = (): void => {
|
|
512
|
+
if (hasMessageStarted) return;
|
|
513
|
+
currentMessageId = `msg-${chunk.id}`;
|
|
514
|
+
hasMessageStarted = true;
|
|
515
|
+
accumulatedContent = "";
|
|
516
|
+
};
|
|
590
517
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
yield factory.reasoningDelta(currentReasoningId, textBlock(reasoningDelta.text));
|
|
598
|
-
}
|
|
518
|
+
if (reasoningDeltas.length > 0) {
|
|
519
|
+
if (!hasReasoningStarted) {
|
|
520
|
+
currentReasoningId = `reason-${chunk.id}`;
|
|
521
|
+
hasReasoningStarted = true;
|
|
522
|
+
accumulatedReasoning = "";
|
|
523
|
+
yield factory.reasoningStarted(currentReasoningId, "full");
|
|
599
524
|
}
|
|
600
525
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
yield factory.messageDelta(currentMessageId, textBlock(delta.content));
|
|
526
|
+
for (const reasoningDelta of reasoningDeltas) {
|
|
527
|
+
accumulatedReasoning += reasoningDelta.text;
|
|
528
|
+
reasoningByField.set(
|
|
529
|
+
reasoningDelta.field,
|
|
530
|
+
(reasoningByField.get(reasoningDelta.field) ?? "") + reasoningDelta.text,
|
|
531
|
+
);
|
|
532
|
+
yield factory.reasoningDelta(currentReasoningId, textBlock(reasoningDelta.text));
|
|
609
533
|
}
|
|
534
|
+
}
|
|
610
535
|
|
|
611
|
-
|
|
612
|
-
if (
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
const idx = tc.index;
|
|
620
|
-
|
|
621
|
-
if (tc.id) {
|
|
622
|
-
pendingToolCalls.set(idx, { id: tc.id, name: tc.function?.name ?? "", args: "" });
|
|
623
|
-
yield factory.toolCallStarted(tc.id, tc.function?.name ?? "");
|
|
624
|
-
}
|
|
536
|
+
if (delta.content) {
|
|
537
|
+
if (!hasMessageStarted) {
|
|
538
|
+
ensureMessageStarted();
|
|
539
|
+
yield factory.messageStarted(currentMessageId);
|
|
540
|
+
}
|
|
541
|
+
accumulatedContent += delta.content;
|
|
542
|
+
yield factory.messageDelta(currentMessageId, textBlock(delta.content));
|
|
543
|
+
}
|
|
625
544
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
yield factory.toolCallDelta(pending.id, { argumentsText: tc.function.arguments });
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
545
|
+
if (delta.tool_calls) {
|
|
546
|
+
if (!hasMessageStarted) {
|
|
547
|
+
ensureMessageStarted();
|
|
548
|
+
yield factory.messageStarted(currentMessageId);
|
|
634
549
|
}
|
|
635
550
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
if (!hasMessageStarted) {
|
|
639
|
-
ensureMessageStarted();
|
|
640
|
-
yield factory.messageStarted(currentMessageId);
|
|
641
|
-
}
|
|
551
|
+
for (const tc of delta.tool_calls) {
|
|
552
|
+
const idx = tc.index;
|
|
642
553
|
|
|
643
|
-
if (
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
yield factory.toolCallStarted(fcId, delta.function_call.name);
|
|
554
|
+
if (tc.id) {
|
|
555
|
+
pendingToolCalls.set(idx, { id: tc.id, name: tc.function?.name ?? "", args: "" });
|
|
556
|
+
yield factory.toolCallStarted(tc.id, tc.function?.name ?? "");
|
|
647
557
|
}
|
|
648
|
-
|
|
649
|
-
|
|
558
|
+
|
|
559
|
+
if (tc.function?.arguments) {
|
|
560
|
+
const pending = pendingToolCalls.get(idx);
|
|
650
561
|
if (pending) {
|
|
651
|
-
pending.args +=
|
|
652
|
-
yield factory.toolCallDelta(pending.id, { argumentsText:
|
|
562
|
+
pending.args += tc.function.arguments;
|
|
563
|
+
yield factory.toolCallDelta(pending.id, { argumentsText: tc.function.arguments });
|
|
653
564
|
}
|
|
654
565
|
}
|
|
655
566
|
}
|
|
567
|
+
}
|
|
656
568
|
|
|
657
|
-
|
|
658
|
-
if (
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
}
|
|
569
|
+
if (delta.function_call) {
|
|
570
|
+
if (!hasMessageStarted) {
|
|
571
|
+
ensureMessageStarted();
|
|
572
|
+
yield factory.messageStarted(currentMessageId);
|
|
573
|
+
}
|
|
663
574
|
|
|
664
|
-
|
|
665
|
-
|
|
575
|
+
if (delta.function_call.name) {
|
|
576
|
+
const fcId = `fc-${chunk.id}-0`;
|
|
577
|
+
pendingToolCalls.set(0, { id: fcId, name: delta.function_call.name, args: "" });
|
|
578
|
+
yield factory.toolCallStarted(fcId, delta.function_call.name);
|
|
579
|
+
}
|
|
580
|
+
if (delta.function_call.arguments) {
|
|
581
|
+
const pending = pendingToolCalls.get(0);
|
|
582
|
+
if (pending) {
|
|
583
|
+
pending.args += delta.function_call.arguments;
|
|
584
|
+
yield factory.toolCallDelta(pending.id, { argumentsText: delta.function_call.arguments });
|
|
585
|
+
}
|
|
666
586
|
}
|
|
667
587
|
}
|
|
668
|
-
}
|
|
669
588
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
589
|
+
if (finishReason && finishReason !== null) {
|
|
590
|
+
const { events, assistantReplayMessage } = finalizePendingTurn();
|
|
591
|
+
for (const event of events) yield event;
|
|
592
|
+
yield* emitCompleted(mapStopReason(finishReason), assistantReplayMessage, chunk.id);
|
|
593
|
+
}
|
|
673
594
|
}
|
|
674
595
|
}
|
|
675
|
-
} finally {
|
|
676
|
-
try {
|
|
677
|
-
if (!streamDone) await reader.cancel().catch(() => undefined);
|
|
678
|
-
} finally {
|
|
679
|
-
reader.releaseLock();
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
if (parser.getRemaining().trim().length > 0) {
|
|
684
|
-
yield factory.responseWarning("Stream ended with an incomplete Chat Completions SSE frame", "STREAM_ERROR");
|
|
685
596
|
}
|
|
686
597
|
|
|
687
|
-
|
|
688
|
-
if (!completedEmitted && (hasMessageStarted || hasReasoningStarted || pendingToolCalls.size > 0)) {
|
|
598
|
+
if (!gate.completed && (hasMessageStarted || hasReasoningStarted || pendingToolCalls.size > 0)) {
|
|
689
599
|
yield factory.responseWarning("Stream ended without a finish_reason", "INCOMPLETE_STREAM");
|
|
690
|
-
|
|
691
600
|
const { events, assistantReplayMessage } = finalizePendingTurn();
|
|
692
|
-
for (const event of events)
|
|
693
|
-
yield event;
|
|
694
|
-
}
|
|
695
|
-
|
|
601
|
+
for (const event of events) yield event;
|
|
696
602
|
yield* emitCompleted(undefined, assistantReplayMessage, responseId);
|
|
697
603
|
}
|
|
698
604
|
}
|