@ai-sdk/openai-compatible 2.0.79 → 2.0.81
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 +17 -0
- package/dist/index.js +71 -139
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +72 -139
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/chat/openai-compatible-chat-language-model.ts +96 -191
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type SharedV3Warning,
|
|
14
14
|
} from '@ai-sdk/provider';
|
|
15
15
|
import {
|
|
16
|
+
StreamingToolCallTracker,
|
|
16
17
|
combineHeaders,
|
|
17
18
|
createEventSourceResponseHandler,
|
|
18
19
|
createJsonErrorResponseHandler,
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
type FetchFunction,
|
|
24
25
|
type ParseResult,
|
|
25
26
|
type ResponseHandler,
|
|
27
|
+
type StreamingToolCallDelta,
|
|
26
28
|
} from '@ai-sdk/provider-utils';
|
|
27
29
|
import { z } from 'zod/v4';
|
|
28
30
|
import { resolveProviderOptionsKey, toCamelCase } from '../utils/to-camel-case';
|
|
@@ -41,6 +43,20 @@ import {
|
|
|
41
43
|
import type { MetadataExtractor } from './openai-compatible-metadata-extractor';
|
|
42
44
|
import { prepareTools } from './openai-compatible-prepare-tools';
|
|
43
45
|
|
|
46
|
+
type OpenAICompatibleStreamingToolCallDelta = StreamingToolCallDelta & {
|
|
47
|
+
extra_content?: {
|
|
48
|
+
google?: {
|
|
49
|
+
thought_signature?: string | null;
|
|
50
|
+
} | null;
|
|
51
|
+
} | null;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type PendingToolCall = {
|
|
55
|
+
id: string | null;
|
|
56
|
+
bufferedArguments: string;
|
|
57
|
+
extraContent: OpenAICompatibleStreamingToolCallDelta['extra_content'];
|
|
58
|
+
};
|
|
59
|
+
|
|
44
60
|
export type OpenAICompatibleChatConfig = {
|
|
45
61
|
provider: string;
|
|
46
62
|
headers: () => Record<string, string | undefined>;
|
|
@@ -410,34 +426,70 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 {
|
|
|
410
426
|
fetch: this.config.fetch,
|
|
411
427
|
});
|
|
412
428
|
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
type: 'function';
|
|
416
|
-
function: {
|
|
417
|
-
name: string;
|
|
418
|
-
arguments: string;
|
|
419
|
-
};
|
|
420
|
-
hasFinished: boolean;
|
|
421
|
-
thoughtSignature?: string;
|
|
422
|
-
}> = [];
|
|
429
|
+
const providerOptionsName = metadataKey;
|
|
430
|
+
let toolCallTracker: StreamingToolCallTracker<OpenAICompatibleStreamingToolCallDelta>;
|
|
423
431
|
|
|
424
432
|
// Buffers tool-call deltas by `index` until `function.name` is known.
|
|
425
433
|
// Some OpenAI-compatible providers send the first delta without
|
|
426
|
-
// `function.name
|
|
427
|
-
const pendingToolCalls = new Map<
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
434
|
+
// `function.name`, which the shared tracker rejects on first chunk.
|
|
435
|
+
const pendingToolCalls = new Map<number, PendingToolCall>();
|
|
436
|
+
const forwardedToolCallIndices = new Set<number>();
|
|
437
|
+
|
|
438
|
+
const processToolCallDelta = (
|
|
439
|
+
toolCallDelta: OpenAICompatibleStreamingToolCallDelta,
|
|
440
|
+
) => {
|
|
441
|
+
const index = toolCallDelta.index;
|
|
442
|
+
|
|
443
|
+
if (index == null || forwardedToolCallIndices.has(index)) {
|
|
444
|
+
toolCallTracker.processDelta(toolCallDelta);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
let pending = pendingToolCalls.get(index);
|
|
449
|
+
if (pending == null) {
|
|
450
|
+
pending = {
|
|
451
|
+
id: toolCallDelta.id ?? null,
|
|
452
|
+
bufferedArguments: '',
|
|
453
|
+
extraContent: toolCallDelta.extra_content ?? null,
|
|
454
|
+
};
|
|
455
|
+
pendingToolCalls.set(index, pending);
|
|
456
|
+
} else {
|
|
457
|
+
if (pending.id == null && toolCallDelta.id != null) {
|
|
458
|
+
pending.id = toolCallDelta.id;
|
|
459
|
+
}
|
|
460
|
+
if (
|
|
461
|
+
pending.extraContent == null &&
|
|
462
|
+
toolCallDelta.extra_content != null
|
|
463
|
+
) {
|
|
464
|
+
pending.extraContent = toolCallDelta.extra_content;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const argumentsDelta = toolCallDelta.function?.arguments;
|
|
469
|
+
if (argumentsDelta != null) {
|
|
470
|
+
pending.bufferedArguments += argumentsDelta;
|
|
433
471
|
}
|
|
434
|
-
|
|
472
|
+
|
|
473
|
+
const name = toolCallDelta.function?.name;
|
|
474
|
+
if (name != null) {
|
|
475
|
+
toolCallTracker.processDelta({
|
|
476
|
+
index,
|
|
477
|
+
id: pending.id,
|
|
478
|
+
function: {
|
|
479
|
+
name,
|
|
480
|
+
arguments: pending.bufferedArguments,
|
|
481
|
+
},
|
|
482
|
+
extra_content: pending.extraContent ?? undefined,
|
|
483
|
+
});
|
|
484
|
+
pendingToolCalls.delete(index);
|
|
485
|
+
forwardedToolCallIndices.add(index);
|
|
486
|
+
}
|
|
487
|
+
};
|
|
435
488
|
|
|
436
489
|
let finishReason: LanguageModelV3FinishReason | undefined;
|
|
437
490
|
let usage: z.infer<typeof openaiCompatibleTokenUsageSchema> | undefined =
|
|
438
491
|
undefined;
|
|
439
492
|
let isFirstChunk = true;
|
|
440
|
-
const providerOptionsName = metadataKey;
|
|
441
493
|
let isActiveReasoning = false;
|
|
442
494
|
let isActiveText = false;
|
|
443
495
|
const convertUsage = (
|
|
@@ -451,6 +503,22 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 {
|
|
|
451
503
|
LanguageModelV3StreamPart
|
|
452
504
|
>({
|
|
453
505
|
start(controller) {
|
|
506
|
+
toolCallTracker =
|
|
507
|
+
new StreamingToolCallTracker<OpenAICompatibleStreamingToolCallDelta>(
|
|
508
|
+
controller,
|
|
509
|
+
{
|
|
510
|
+
generateId,
|
|
511
|
+
extractMetadata: delta => {
|
|
512
|
+
const thoughtSignature =
|
|
513
|
+
delta.extra_content?.google?.thought_signature;
|
|
514
|
+
|
|
515
|
+
return thoughtSignature
|
|
516
|
+
? { [providerOptionsName]: { thoughtSignature } }
|
|
517
|
+
: undefined;
|
|
518
|
+
},
|
|
519
|
+
buildToolCallProviderMetadata: metadata => metadata,
|
|
520
|
+
},
|
|
521
|
+
);
|
|
454
522
|
controller.enqueue({ type: 'stream-start', warnings });
|
|
455
523
|
},
|
|
456
524
|
|
|
@@ -580,144 +648,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 {
|
|
|
580
648
|
}
|
|
581
649
|
|
|
582
650
|
for (const toolCallDelta of delta.tool_calls) {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
if (toolCalls[index] == null) {
|
|
586
|
-
if (toolCallDelta.index != null) {
|
|
587
|
-
// Buffer deltas until `function.name` is known. Some
|
|
588
|
-
// OpenAI-compatible providers send the first delta
|
|
589
|
-
// without `function.name`.
|
|
590
|
-
let pending = pendingToolCalls.get(index);
|
|
591
|
-
if (pending == null) {
|
|
592
|
-
pending = {
|
|
593
|
-
id: toolCallDelta.id ?? null,
|
|
594
|
-
bufferedArguments: '',
|
|
595
|
-
thoughtSignature:
|
|
596
|
-
toolCallDelta.extra_content?.google
|
|
597
|
-
?.thought_signature ?? undefined,
|
|
598
|
-
};
|
|
599
|
-
pendingToolCalls.set(index, pending);
|
|
600
|
-
} else {
|
|
601
|
-
if (pending.id == null && toolCallDelta.id != null) {
|
|
602
|
-
pending.id = toolCallDelta.id;
|
|
603
|
-
}
|
|
604
|
-
if (
|
|
605
|
-
pending.thoughtSignature == null &&
|
|
606
|
-
toolCallDelta.extra_content?.google
|
|
607
|
-
?.thought_signature != null
|
|
608
|
-
) {
|
|
609
|
-
pending.thoughtSignature =
|
|
610
|
-
toolCallDelta.extra_content.google.thought_signature;
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
const argumentsDelta = toolCallDelta.function?.arguments;
|
|
615
|
-
if (argumentsDelta != null) {
|
|
616
|
-
pending.bufferedArguments += argumentsDelta;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
const name = toolCallDelta.function?.name;
|
|
620
|
-
if (name == null) {
|
|
621
|
-
continue; // wait for the delta that carries the name
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
pendingToolCalls.delete(index);
|
|
625
|
-
|
|
626
|
-
if (pending.id == null) {
|
|
627
|
-
throw new InvalidResponseDataError({
|
|
628
|
-
data: toolCallDelta,
|
|
629
|
-
message: `Expected 'id' to be a string.`,
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
controller.enqueue({
|
|
634
|
-
type: 'tool-input-start',
|
|
635
|
-
id: pending.id,
|
|
636
|
-
toolName: name,
|
|
637
|
-
});
|
|
638
|
-
|
|
639
|
-
toolCalls[index] = {
|
|
640
|
-
id: pending.id,
|
|
641
|
-
type: 'function',
|
|
642
|
-
function: {
|
|
643
|
-
name,
|
|
644
|
-
arguments: pending.bufferedArguments,
|
|
645
|
-
},
|
|
646
|
-
hasFinished: false,
|
|
647
|
-
thoughtSignature: pending.thoughtSignature,
|
|
648
|
-
};
|
|
649
|
-
} else {
|
|
650
|
-
if (toolCallDelta.id == null) {
|
|
651
|
-
throw new InvalidResponseDataError({
|
|
652
|
-
data: toolCallDelta,
|
|
653
|
-
message: `Expected 'id' to be a string.`,
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
if (toolCallDelta.function?.name == null) {
|
|
658
|
-
throw new InvalidResponseDataError({
|
|
659
|
-
data: toolCallDelta,
|
|
660
|
-
message: `Expected 'function.name' to be a string.`,
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
controller.enqueue({
|
|
665
|
-
type: 'tool-input-start',
|
|
666
|
-
id: toolCallDelta.id,
|
|
667
|
-
toolName: toolCallDelta.function.name,
|
|
668
|
-
});
|
|
669
|
-
|
|
670
|
-
toolCalls[index] = {
|
|
671
|
-
id: toolCallDelta.id,
|
|
672
|
-
type: 'function',
|
|
673
|
-
function: {
|
|
674
|
-
name: toolCallDelta.function.name,
|
|
675
|
-
arguments: toolCallDelta.function.arguments ?? '',
|
|
676
|
-
},
|
|
677
|
-
hasFinished: false,
|
|
678
|
-
thoughtSignature:
|
|
679
|
-
toolCallDelta.extra_content?.google
|
|
680
|
-
?.thought_signature ?? undefined,
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
const toolCall = toolCalls[index];
|
|
685
|
-
|
|
686
|
-
if (
|
|
687
|
-
toolCall.function?.name != null &&
|
|
688
|
-
toolCall.function?.arguments != null
|
|
689
|
-
) {
|
|
690
|
-
// send delta if the argument text has already started:
|
|
691
|
-
if (toolCall.function.arguments.length > 0) {
|
|
692
|
-
controller.enqueue({
|
|
693
|
-
type: 'tool-input-delta',
|
|
694
|
-
id: toolCall.id,
|
|
695
|
-
delta: toolCall.function.arguments,
|
|
696
|
-
});
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
continue;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
// existing tool call, merge if not finished
|
|
704
|
-
const toolCall = toolCalls[index];
|
|
705
|
-
|
|
706
|
-
if (toolCall.hasFinished) {
|
|
707
|
-
continue;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
if (toolCallDelta.function?.arguments != null) {
|
|
711
|
-
toolCall.function!.arguments +=
|
|
712
|
-
toolCallDelta.function?.arguments ?? '';
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
// send delta
|
|
716
|
-
controller.enqueue({
|
|
717
|
-
type: 'tool-input-delta',
|
|
718
|
-
id: toolCall.id,
|
|
719
|
-
delta: toolCallDelta.function.arguments ?? '',
|
|
720
|
-
});
|
|
651
|
+
processToolCallDelta(toolCallDelta);
|
|
721
652
|
}
|
|
722
653
|
}
|
|
723
654
|
},
|
|
@@ -731,44 +662,18 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 {
|
|
|
731
662
|
controller.enqueue({ type: 'text-end', id: 'txt-0' });
|
|
732
663
|
}
|
|
733
664
|
|
|
734
|
-
//
|
|
735
|
-
// invalid
|
|
665
|
+
// Forward pending deltas so the tracker preserves the existing
|
|
666
|
+
// invalid-response behavior for calls that never receive a name.
|
|
736
667
|
for (const [index, pending] of pendingToolCalls) {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
function: { arguments: pending.bufferedArguments },
|
|
742
|
-
},
|
|
743
|
-
message: `Expected 'function.name' to be a string.`,
|
|
668
|
+
toolCallTracker.processDelta({
|
|
669
|
+
index,
|
|
670
|
+
id: pending.id,
|
|
671
|
+
function: { arguments: pending.bufferedArguments },
|
|
744
672
|
});
|
|
745
673
|
}
|
|
674
|
+
pendingToolCalls.clear();
|
|
746
675
|
|
|
747
|
-
|
|
748
|
-
for (const toolCall of toolCalls.filter(
|
|
749
|
-
toolCall => !toolCall.hasFinished,
|
|
750
|
-
)) {
|
|
751
|
-
controller.enqueue({
|
|
752
|
-
type: 'tool-input-end',
|
|
753
|
-
id: toolCall.id,
|
|
754
|
-
});
|
|
755
|
-
|
|
756
|
-
controller.enqueue({
|
|
757
|
-
type: 'tool-call',
|
|
758
|
-
toolCallId: toolCall.id ?? generateId(),
|
|
759
|
-
toolName: toolCall.function.name,
|
|
760
|
-
input: toolCall.function.arguments,
|
|
761
|
-
...(toolCall.thoughtSignature
|
|
762
|
-
? {
|
|
763
|
-
providerMetadata: {
|
|
764
|
-
[providerOptionsName]: {
|
|
765
|
-
thoughtSignature: toolCall.thoughtSignature,
|
|
766
|
-
},
|
|
767
|
-
},
|
|
768
|
-
}
|
|
769
|
-
: {}),
|
|
770
|
-
});
|
|
771
|
-
}
|
|
676
|
+
toolCallTracker.flush();
|
|
772
677
|
|
|
773
678
|
if (finishReason == null) {
|
|
774
679
|
finishReason = { unified: 'error', raw: undefined };
|