@gajae-code/ai 0.6.3 → 0.6.5
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/CHANGELOG.md +6 -0
- package/dist/types/provider-models/openai-compat.d.ts +2 -0
- package/package.json +2 -2
- package/src/model-thinking.ts +1 -1
- package/src/models.json +4975 -702
- package/src/models.ts +12 -5
- package/src/provider-models/openai-compat.ts +297 -59
- package/src/providers/openai-responses-shared.ts +165 -85
|
@@ -370,15 +370,66 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
370
370
|
model: Model<TApi>,
|
|
371
371
|
options?: ProcessResponsesStreamOptions,
|
|
372
372
|
): Promise<void> {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
373
|
+
type StreamItem = ResponseReasoningItem | ResponseOutputMessage | ResponseFunctionToolCall | ResponseCustomToolCall;
|
|
374
|
+
type StreamBlock = ThinkingContent | TextContent | (ToolCall & { partialJson: string });
|
|
375
|
+
interface ItemEntry {
|
|
376
|
+
item: StreamItem;
|
|
377
|
+
block: StreamBlock;
|
|
378
|
+
blockContentIndex: number;
|
|
379
|
+
}
|
|
380
|
+
// Per-item argument buffer keyed on stable item identity. Multiple tool-call
|
|
381
|
+
// items can stream interleaved argument deltas in one response, so a single
|
|
382
|
+
// most-recent slot would mis-attribute deltas to the wrong item.
|
|
383
|
+
const items = new Map<string, ItemEntry>();
|
|
384
|
+
let lastKey: string | null = null;
|
|
385
|
+
const idKey = (id: string) => `id:${id}`;
|
|
386
|
+
const idxKey = (n: number) => `idx:${n}`;
|
|
387
|
+
const hasIndex = (n: number | undefined): n is number => typeof n === "number" && Number.isFinite(n);
|
|
388
|
+
const resolveEntry = (
|
|
389
|
+
itemId: string | undefined,
|
|
390
|
+
outputIndex: number | undefined,
|
|
391
|
+
// Fallback to the most-recently-added entry (`lastKey`) when the event
|
|
392
|
+
// cannot be resolved by identity:
|
|
393
|
+
// - "never": tool ghost events with an explicit but unmatched key are ignored.
|
|
394
|
+
// - "no-key": only when BOTH item_id and a finite output_index are absent —
|
|
395
|
+
// the legacy single continuation-style tool delta/done shape.
|
|
396
|
+
// - "always": continuation-style non-tool events (reasoning/text), which may
|
|
397
|
+
// legitimately omit identity and target the open block.
|
|
398
|
+
fallback: "never" | "no-key" | "always",
|
|
399
|
+
): ItemEntry | undefined => {
|
|
400
|
+
if (itemId) {
|
|
401
|
+
const byId = items.get(idKey(itemId));
|
|
402
|
+
if (byId) return byId;
|
|
403
|
+
}
|
|
404
|
+
if (hasIndex(outputIndex)) {
|
|
405
|
+
const byIdx = items.get(idxKey(outputIndex));
|
|
406
|
+
if (byIdx) return byIdx;
|
|
407
|
+
}
|
|
408
|
+
const hasExplicitKey = !!itemId || hasIndex(outputIndex);
|
|
409
|
+
const allowLastKey = fallback === "always" || (fallback === "no-key" && !hasExplicitKey);
|
|
410
|
+
if (allowLastKey && lastKey) return items.get(lastKey);
|
|
411
|
+
return undefined;
|
|
412
|
+
};
|
|
413
|
+
const registerEntry = (item: StreamItem, block: StreamBlock, outputIndex: number | undefined): ItemEntry => {
|
|
414
|
+
output.content.push(block);
|
|
415
|
+
const entry: ItemEntry = { item, block, blockContentIndex: output.content.length - 1 };
|
|
416
|
+
// Primary key prefers the stable item id; if the wire omits it, fall back to
|
|
417
|
+
// the positional index. A synthetic key keeps the entry addressable as lastKey
|
|
418
|
+
// for continuation-style non-tool events even when neither is present.
|
|
419
|
+
const key = item.id ? idKey(item.id) : hasIndex(outputIndex) ? idxKey(outputIndex) : `seq:${items.size}`;
|
|
420
|
+
items.set(key, entry);
|
|
421
|
+
if (item.id && hasIndex(outputIndex)) items.set(idxKey(outputIndex), entry);
|
|
422
|
+
lastKey = key;
|
|
423
|
+
return entry;
|
|
424
|
+
};
|
|
425
|
+
const dropEntry = (itemId: string | undefined, outputIndex: number | undefined): void => {
|
|
426
|
+
const key = itemId ? idKey(itemId) : hasIndex(outputIndex) ? idxKey(outputIndex) : null;
|
|
427
|
+
if (key) {
|
|
428
|
+
items.delete(key);
|
|
429
|
+
if (lastKey === key) lastKey = null;
|
|
430
|
+
}
|
|
431
|
+
if (itemId && hasIndex(outputIndex)) items.delete(idxKey(outputIndex));
|
|
432
|
+
};
|
|
382
433
|
let sawFirstToken = false;
|
|
383
434
|
|
|
384
435
|
for await (const event of openaiStream) {
|
|
@@ -390,30 +441,27 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
390
441
|
options?.onFirstToken?.();
|
|
391
442
|
}
|
|
392
443
|
const item = event.item;
|
|
444
|
+
const outputIndex = event.output_index;
|
|
393
445
|
if (item.type === "reasoning") {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
stream.push({ type: "thinking_start", contentIndex: blockIndex(), partial: output });
|
|
446
|
+
const block: ThinkingContent = { type: "thinking", thinking: "", itemId: item.id };
|
|
447
|
+
const entry = registerEntry(item, block, outputIndex);
|
|
448
|
+
stream.push({ type: "thinking_start", contentIndex: entry.blockContentIndex, partial: output });
|
|
398
449
|
} else if (item.type === "message") {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
stream.push({ type: "text_start", contentIndex: blockIndex(), partial: output });
|
|
450
|
+
const block: TextContent = { type: "text", text: "" };
|
|
451
|
+
const entry = registerEntry(item, block, outputIndex);
|
|
452
|
+
stream.push({ type: "text_start", contentIndex: entry.blockContentIndex, partial: output });
|
|
403
453
|
} else if (item.type === "function_call") {
|
|
404
|
-
|
|
405
|
-
currentBlock = {
|
|
454
|
+
const block: ToolCall & { partialJson: string } = {
|
|
406
455
|
type: "toolCall",
|
|
407
456
|
id: encodeResponsesToolCallId(item.call_id, item.id),
|
|
408
457
|
name: item.name,
|
|
409
458
|
arguments: {},
|
|
410
459
|
partialJson: item.arguments || "",
|
|
411
460
|
};
|
|
412
|
-
|
|
413
|
-
stream.push({ type: "toolcall_start", contentIndex:
|
|
461
|
+
const entry = registerEntry(item, block, outputIndex);
|
|
462
|
+
stream.push({ type: "toolcall_start", contentIndex: entry.blockContentIndex, partial: output });
|
|
414
463
|
} else if (item.type === "custom_tool_call") {
|
|
415
|
-
|
|
416
|
-
currentBlock = {
|
|
464
|
+
const block: ToolCall & { partialJson: string } = {
|
|
417
465
|
type: "toolCall",
|
|
418
466
|
id: encodeResponsesToolCallId(item.call_id, item.id),
|
|
419
467
|
// Preserve the raw wire name (e.g. `apply_patch`). The agent-loop
|
|
@@ -427,39 +475,42 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
427
475
|
// accumulation buffer so later code that inspects the field still works.
|
|
428
476
|
partialJson: item.input ?? "",
|
|
429
477
|
};
|
|
430
|
-
|
|
431
|
-
stream.push({ type: "toolcall_start", contentIndex:
|
|
478
|
+
const entry = registerEntry(item, block, outputIndex);
|
|
479
|
+
stream.push({ type: "toolcall_start", contentIndex: entry.blockContentIndex, partial: output });
|
|
432
480
|
}
|
|
433
481
|
} else if (event.type === "response.reasoning_summary_part.added") {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
482
|
+
const entry = resolveEntry(event.item_id, event.output_index, "always");
|
|
483
|
+
if (entry?.item.type === "reasoning") {
|
|
484
|
+
entry.item.summary = entry.item.summary || [];
|
|
485
|
+
entry.item.summary.push(event.part);
|
|
437
486
|
}
|
|
438
487
|
} else if (event.type === "response.reasoning_summary_text.delta") {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
488
|
+
const entry = resolveEntry(event.item_id, event.output_index, "always");
|
|
489
|
+
if (entry?.item.type === "reasoning" && entry.block.type === "thinking") {
|
|
490
|
+
entry.item.summary = entry.item.summary || [];
|
|
491
|
+
const lastPart = entry.item.summary[entry.item.summary.length - 1];
|
|
442
492
|
if (lastPart) {
|
|
443
|
-
|
|
493
|
+
entry.block.thinking += event.delta;
|
|
444
494
|
lastPart.text += event.delta;
|
|
445
495
|
stream.push({
|
|
446
496
|
type: "thinking_delta",
|
|
447
|
-
contentIndex:
|
|
497
|
+
contentIndex: entry.blockContentIndex,
|
|
448
498
|
delta: event.delta,
|
|
449
499
|
partial: output,
|
|
450
500
|
});
|
|
451
501
|
}
|
|
452
502
|
}
|
|
453
503
|
} else if (event.type === "response.reasoning_summary_part.done") {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
504
|
+
const entry = resolveEntry(event.item_id, event.output_index, "always");
|
|
505
|
+
if (entry?.item.type === "reasoning" && entry.block.type === "thinking") {
|
|
506
|
+
entry.item.summary = entry.item.summary || [];
|
|
507
|
+
const lastPart = entry.item.summary[entry.item.summary.length - 1];
|
|
457
508
|
if (lastPart) {
|
|
458
|
-
|
|
509
|
+
entry.block.thinking += "\n\n";
|
|
459
510
|
lastPart.text += "\n\n";
|
|
460
511
|
stream.push({
|
|
461
512
|
type: "thinking_delta",
|
|
462
|
-
contentIndex:
|
|
513
|
+
contentIndex: entry.blockContentIndex,
|
|
463
514
|
delta: "\n\n",
|
|
464
515
|
partial: output,
|
|
465
516
|
});
|
|
@@ -468,85 +519,94 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
468
519
|
} else if (event.type === "response.reasoning_text.delta") {
|
|
469
520
|
// Raw reasoning text delta from local providers that stream thinking
|
|
470
521
|
// directly rather than via the OpenAI summary tracking protocol.
|
|
471
|
-
|
|
472
|
-
|
|
522
|
+
const entry = resolveEntry(event.item_id, event.output_index, "always");
|
|
523
|
+
if (entry?.item.type === "reasoning" && entry.block.type === "thinking") {
|
|
524
|
+
entry.block.thinking += event.delta;
|
|
473
525
|
stream.push({
|
|
474
526
|
type: "thinking_delta",
|
|
475
|
-
contentIndex:
|
|
527
|
+
contentIndex: entry.blockContentIndex,
|
|
476
528
|
delta: event.delta,
|
|
477
529
|
partial: output,
|
|
478
530
|
});
|
|
479
531
|
}
|
|
480
532
|
} else if (event.type === "response.content_part.added") {
|
|
481
|
-
|
|
482
|
-
|
|
533
|
+
const entry = resolveEntry(event.item_id, event.output_index, "always");
|
|
534
|
+
if (entry?.item.type === "message") {
|
|
535
|
+
entry.item.content = entry.item.content || [];
|
|
483
536
|
if (event.part.type === "output_text" || event.part.type === "refusal") {
|
|
484
|
-
|
|
537
|
+
entry.item.content.push(event.part);
|
|
485
538
|
}
|
|
486
539
|
}
|
|
487
540
|
} else if (event.type === "response.output_text.delta") {
|
|
488
|
-
|
|
489
|
-
|
|
541
|
+
const entry = resolveEntry(event.item_id, event.output_index, "always");
|
|
542
|
+
if (entry?.item.type === "message" && entry.block.type === "text") {
|
|
543
|
+
const lastPart = entry.item.content?.[entry.item.content.length - 1];
|
|
490
544
|
if (lastPart?.type === "output_text") {
|
|
491
|
-
|
|
545
|
+
entry.block.text += event.delta;
|
|
492
546
|
lastPart.text += event.delta;
|
|
493
547
|
stream.push({
|
|
494
548
|
type: "text_delta",
|
|
495
|
-
contentIndex:
|
|
549
|
+
contentIndex: entry.blockContentIndex,
|
|
496
550
|
delta: event.delta,
|
|
497
551
|
partial: output,
|
|
498
552
|
});
|
|
499
553
|
}
|
|
500
554
|
}
|
|
501
555
|
} else if (event.type === "response.refusal.delta") {
|
|
502
|
-
|
|
503
|
-
|
|
556
|
+
const entry = resolveEntry(event.item_id, event.output_index, "always");
|
|
557
|
+
if (entry?.item.type === "message" && entry.block.type === "text") {
|
|
558
|
+
const lastPart = entry.item.content?.[entry.item.content.length - 1];
|
|
504
559
|
if (lastPart?.type === "refusal") {
|
|
505
|
-
|
|
560
|
+
entry.block.text += event.delta;
|
|
506
561
|
lastPart.refusal += event.delta;
|
|
507
562
|
stream.push({
|
|
508
563
|
type: "text_delta",
|
|
509
|
-
contentIndex:
|
|
564
|
+
contentIndex: entry.blockContentIndex,
|
|
510
565
|
delta: event.delta,
|
|
511
566
|
partial: output,
|
|
512
567
|
});
|
|
513
568
|
}
|
|
514
569
|
}
|
|
515
570
|
} else if (event.type === "response.function_call_arguments.delta") {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
571
|
+
const entry = resolveEntry(event.item_id, event.output_index, "no-key");
|
|
572
|
+
if (entry?.item.type === "function_call" && entry.block.type === "toolCall") {
|
|
573
|
+
entry.block.partialJson += event.delta;
|
|
574
|
+
entry.block.arguments = parseStreamingJson(entry.block.partialJson);
|
|
519
575
|
stream.push({
|
|
520
576
|
type: "toolcall_delta",
|
|
521
|
-
contentIndex:
|
|
577
|
+
contentIndex: entry.blockContentIndex,
|
|
522
578
|
delta: event.delta,
|
|
523
579
|
partial: output,
|
|
524
580
|
});
|
|
525
581
|
}
|
|
526
582
|
} else if (event.type === "response.function_call_arguments.done") {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
583
|
+
const entry = resolveEntry(event.item_id, event.output_index, "no-key");
|
|
584
|
+
if (entry?.item.type === "function_call" && entry.block.type === "toolCall") {
|
|
585
|
+
entry.block.partialJson = event.arguments;
|
|
586
|
+
entry.block.arguments = parseStreamingJson(entry.block.partialJson);
|
|
530
587
|
}
|
|
531
588
|
} else if (event.type === "response.custom_tool_call_input.delta") {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
589
|
+
const entry = resolveEntry(event.item_id, event.output_index, "no-key");
|
|
590
|
+
if (entry?.item.type === "custom_tool_call" && entry.block.type === "toolCall") {
|
|
591
|
+
entry.block.partialJson += event.delta;
|
|
592
|
+
entry.block.arguments = { input: entry.block.partialJson };
|
|
535
593
|
stream.push({
|
|
536
594
|
type: "toolcall_delta",
|
|
537
|
-
contentIndex:
|
|
595
|
+
contentIndex: entry.blockContentIndex,
|
|
538
596
|
delta: event.delta,
|
|
539
597
|
partial: output,
|
|
540
598
|
});
|
|
541
599
|
}
|
|
542
600
|
} else if (event.type === "response.custom_tool_call_input.done") {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
601
|
+
const entry = resolveEntry(event.item_id, event.output_index, "no-key");
|
|
602
|
+
if (entry?.item.type === "custom_tool_call" && entry.block.type === "toolCall") {
|
|
603
|
+
entry.block.partialJson = event.input;
|
|
604
|
+
entry.block.arguments = { input: event.input };
|
|
546
605
|
}
|
|
547
606
|
} else if (event.type === "response.output_item.done") {
|
|
548
607
|
const item = structuredCloneJSON(event.item);
|
|
549
608
|
options?.onOutputItemDone?.(item);
|
|
609
|
+
const entry = resolveEntry(item.id, event.output_index, "never");
|
|
550
610
|
if (item.type === "reasoning") {
|
|
551
611
|
const thinking =
|
|
552
612
|
item.summary?.length > 0
|
|
@@ -554,13 +614,17 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
554
614
|
: item.content?.[0]?.type === "reasoning_text"
|
|
555
615
|
? (item.content[0].text ?? "")
|
|
556
616
|
: "";
|
|
557
|
-
const reasoningBlock =
|
|
558
|
-
|
|
559
|
-
|
|
617
|
+
const reasoningBlock =
|
|
618
|
+
entry?.block.type === "thinking"
|
|
619
|
+
? entry.block
|
|
620
|
+
: (output.content.find(b => b.type === "thinking" && (b as ThinkingContent).itemId === item.id) as
|
|
621
|
+
| ThinkingContent
|
|
622
|
+
| undefined);
|
|
560
623
|
if (reasoningBlock) {
|
|
561
624
|
reasoningBlock.thinking = thinking;
|
|
562
625
|
reasoningBlock.thinkingSignature = JSON.stringify(item);
|
|
563
|
-
const reasoningBlockIndex =
|
|
626
|
+
const reasoningBlockIndex =
|
|
627
|
+
entry?.block === reasoningBlock ? entry.blockContentIndex : output.content.indexOf(reasoningBlock);
|
|
564
628
|
stream.push({
|
|
565
629
|
type: "thinking_end",
|
|
566
630
|
contentIndex: reasoningBlockIndex,
|
|
@@ -568,23 +632,27 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
568
632
|
partial: output,
|
|
569
633
|
});
|
|
570
634
|
}
|
|
571
|
-
|
|
572
|
-
} else if (item.type === "message" &&
|
|
573
|
-
|
|
635
|
+
dropEntry(item.id, event.output_index);
|
|
636
|
+
} else if (item.type === "message" && entry?.block.type === "text") {
|
|
637
|
+
const block = entry.block;
|
|
638
|
+
block.text = item.content
|
|
574
639
|
.map(part => (part.type === "output_text" ? (part.text ?? "") : (part.refusal ?? "")))
|
|
575
640
|
.join("");
|
|
576
|
-
|
|
641
|
+
block.textSignature = encodeTextSignatureV1(item.id, item.phase ?? undefined);
|
|
577
642
|
stream.push({
|
|
578
643
|
type: "text_end",
|
|
579
|
-
contentIndex:
|
|
580
|
-
content:
|
|
644
|
+
contentIndex: entry.blockContentIndex,
|
|
645
|
+
content: block.text,
|
|
581
646
|
partial: output,
|
|
582
647
|
});
|
|
583
|
-
|
|
648
|
+
dropEntry(item.id, event.output_index);
|
|
584
649
|
} else if (item.type === "function_call") {
|
|
650
|
+
// Finalize onto the same block object stored in output.content, reading
|
|
651
|
+
// the matching entry's buffered partialJson first and only then the done
|
|
652
|
+
// item's arguments — never an adjacent item's buffer.
|
|
585
653
|
const args =
|
|
586
|
-
|
|
587
|
-
? parseStreamingJson(
|
|
654
|
+
entry?.block.type === "toolCall" && entry.block.partialJson
|
|
655
|
+
? parseStreamingJson(entry.block.partialJson)
|
|
588
656
|
: parseStreamingJson(item.arguments || "{}");
|
|
589
657
|
const toolCall: ToolCall = {
|
|
590
658
|
type: "toolCall",
|
|
@@ -592,12 +660,18 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
592
660
|
name: item.name,
|
|
593
661
|
arguments: args,
|
|
594
662
|
};
|
|
595
|
-
|
|
596
|
-
|
|
663
|
+
if (entry?.block.type === "toolCall") {
|
|
664
|
+
entry.block.id = toolCall.id;
|
|
665
|
+
entry.block.name = toolCall.name;
|
|
666
|
+
entry.block.arguments = args;
|
|
667
|
+
}
|
|
668
|
+
const contentIndex = entry?.blockContentIndex ?? output.content.length - 1;
|
|
669
|
+
dropEntry(item.id, event.output_index);
|
|
670
|
+
stream.push({ type: "toolcall_end", contentIndex, toolCall, partial: output });
|
|
597
671
|
} else if (item.type === "custom_tool_call") {
|
|
598
672
|
const rawInput =
|
|
599
|
-
|
|
600
|
-
?
|
|
673
|
+
entry?.block.type === "toolCall" && entry.block.partialJson
|
|
674
|
+
? entry.block.partialJson
|
|
601
675
|
: (item.input ?? "");
|
|
602
676
|
const toolCall: ToolCall = {
|
|
603
677
|
type: "toolCall",
|
|
@@ -606,8 +680,14 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
606
680
|
arguments: { input: rawInput },
|
|
607
681
|
customWireName: item.name,
|
|
608
682
|
};
|
|
609
|
-
|
|
610
|
-
|
|
683
|
+
if (entry?.block.type === "toolCall") {
|
|
684
|
+
entry.block.id = toolCall.id;
|
|
685
|
+
entry.block.name = toolCall.name;
|
|
686
|
+
entry.block.arguments = { input: rawInput };
|
|
687
|
+
}
|
|
688
|
+
const contentIndex = entry?.blockContentIndex ?? output.content.length - 1;
|
|
689
|
+
dropEntry(item.id, event.output_index);
|
|
690
|
+
stream.push({ type: "toolcall_end", contentIndex, toolCall, partial: output });
|
|
611
691
|
}
|
|
612
692
|
} else if (event.type === "response.completed") {
|
|
613
693
|
const response = event.response;
|