@ai-sdk/openai-compatible 3.0.0-beta.4 → 3.0.0-beta.57
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 +440 -8
- package/README.md +2 -0
- package/dist/index.d.ts +69 -8
- package/dist/index.js +548 -428
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +20 -2
- package/dist/internal/index.js +91 -91
- package/dist/internal/index.js.map +1 -1
- package/docs/index.mdx +1 -0
- package/package.json +16 -17
- package/src/chat/convert-openai-compatible-chat-usage.ts +1 -1
- package/src/chat/convert-to-openai-compatible-chat-messages.ts +94 -73
- package/src/chat/map-openai-compatible-finish-reason.ts +1 -1
- package/src/chat/openai-compatible-api-types.ts +3 -5
- package/src/chat/openai-compatible-chat-language-model.ts +205 -191
- package/src/chat/openai-compatible-metadata-extractor.ts +1 -1
- package/src/chat/openai-compatible-prepare-tools.ts +2 -3
- package/src/completion/convert-openai-compatible-completion-usage.ts +1 -1
- package/src/completion/convert-to-openai-compatible-completion-prompt.ts +1 -2
- package/src/completion/map-openai-compatible-finish-reason.ts +1 -1
- package/src/completion/openai-compatible-completion-language-model.ts +52 -17
- package/src/embedding/openai-compatible-embedding-model.ts +36 -10
- package/src/image/openai-compatible-image-model.ts +35 -13
- package/src/index.ts +3 -3
- package/src/openai-compatible-error.ts +1 -2
- package/src/openai-compatible-provider.ts +18 -5
- package/src/utils/to-camel-case.ts +43 -0
- package/dist/index.d.mts +0 -290
- package/dist/index.mjs +0 -1742
- package/dist/index.mjs.map +0 -1
- package/dist/internal/index.d.mts +0 -193
- package/dist/internal/index.mjs +0 -340
- package/dist/internal/index.mjs.map +0 -1
- /package/src/chat/{openai-compatible-chat-options.ts → openai-compatible-chat-language-model-options.ts} +0 -0
- /package/src/completion/{openai-compatible-completion-options.ts → openai-compatible-completion-language-model-options.ts} +0 -0
- /package/src/embedding/{openai-compatible-embedding-options.ts → openai-compatible-embedding-model-options.ts} +0 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type {
|
|
2
2
|
APICallError,
|
|
3
|
-
InvalidResponseDataError,
|
|
4
3
|
LanguageModelV4,
|
|
5
4
|
LanguageModelV4CallOptions,
|
|
6
5
|
LanguageModelV4Content,
|
|
@@ -8,6 +7,7 @@ import {
|
|
|
8
7
|
LanguageModelV4GenerateResult,
|
|
9
8
|
LanguageModelV4StreamPart,
|
|
10
9
|
LanguageModelV4StreamResult,
|
|
10
|
+
LanguageModelV4Usage,
|
|
11
11
|
SharedV4ProviderMetadata,
|
|
12
12
|
SharedV4Warning,
|
|
13
13
|
} from '@ai-sdk/provider';
|
|
@@ -16,33 +16,51 @@ import {
|
|
|
16
16
|
createEventSourceResponseHandler,
|
|
17
17
|
createJsonErrorResponseHandler,
|
|
18
18
|
createJsonResponseHandler,
|
|
19
|
-
FetchFunction,
|
|
20
19
|
generateId,
|
|
21
|
-
|
|
20
|
+
isCustomReasoning,
|
|
22
21
|
parseProviderOptions,
|
|
23
|
-
ParseResult,
|
|
24
22
|
postJsonToApi,
|
|
25
|
-
|
|
23
|
+
serializeModelOptions,
|
|
24
|
+
StreamingToolCallTracker,
|
|
25
|
+
WORKFLOW_SERIALIZE,
|
|
26
|
+
WORKFLOW_DESERIALIZE,
|
|
27
|
+
type StreamingToolCallDelta,
|
|
28
|
+
type FetchFunction,
|
|
29
|
+
type ParseResult,
|
|
30
|
+
type ResponseHandler,
|
|
26
31
|
} from '@ai-sdk/provider-utils';
|
|
27
32
|
import { z } from 'zod/v4';
|
|
33
|
+
import {
|
|
34
|
+
resolveProviderOptionsKey,
|
|
35
|
+
toCamelCase,
|
|
36
|
+
warnIfDeprecatedProviderOptionsKey,
|
|
37
|
+
} from '../utils/to-camel-case';
|
|
28
38
|
import {
|
|
29
39
|
defaultOpenAICompatibleErrorStructure,
|
|
30
|
-
ProviderErrorStructure,
|
|
40
|
+
type ProviderErrorStructure,
|
|
31
41
|
} from '../openai-compatible-error';
|
|
32
42
|
import { convertOpenAICompatibleChatUsage } from './convert-openai-compatible-chat-usage';
|
|
33
43
|
import { convertToOpenAICompatibleChatMessages } from './convert-to-openai-compatible-chat-messages';
|
|
34
44
|
import { getResponseMetadata } from './get-response-metadata';
|
|
35
45
|
import { mapOpenAICompatibleFinishReason } from './map-openai-compatible-finish-reason';
|
|
36
46
|
import {
|
|
37
|
-
OpenAICompatibleChatModelId,
|
|
38
47
|
openaiCompatibleLanguageModelChatOptions,
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
type OpenAICompatibleChatModelId,
|
|
49
|
+
} from './openai-compatible-chat-language-model-options';
|
|
50
|
+
import type { MetadataExtractor } from './openai-compatible-metadata-extractor';
|
|
41
51
|
import { prepareTools } from './openai-compatible-prepare-tools';
|
|
42
52
|
|
|
53
|
+
type OpenAICompatibleStreamingToolCallDelta = StreamingToolCallDelta & {
|
|
54
|
+
extra_content?: {
|
|
55
|
+
google?: {
|
|
56
|
+
thought_signature?: string | null;
|
|
57
|
+
} | null;
|
|
58
|
+
} | null;
|
|
59
|
+
};
|
|
60
|
+
|
|
43
61
|
export type OpenAICompatibleChatConfig = {
|
|
44
62
|
provider: string;
|
|
45
|
-
headers
|
|
63
|
+
headers?: () => Record<string, string | undefined>;
|
|
46
64
|
url: (options: { modelId: string; path: string }) => string;
|
|
47
65
|
fetch?: FetchFunction;
|
|
48
66
|
includeUsage?: boolean;
|
|
@@ -65,6 +83,20 @@ export type OpenAICompatibleChatConfig = {
|
|
|
65
83
|
* than the official OpenAI API.
|
|
66
84
|
*/
|
|
67
85
|
transformRequestBody?: (args: Record<string, any>) => Record<string, any>;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Optional usage converter for OpenAI-compatible providers with different
|
|
89
|
+
* token accounting semantics.
|
|
90
|
+
*/
|
|
91
|
+
convertUsage?: (
|
|
92
|
+
usage: z.infer<typeof openaiCompatibleTokenUsageSchema>,
|
|
93
|
+
) => LanguageModelV4Usage;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
type PendingToolCall = {
|
|
97
|
+
id: string | null;
|
|
98
|
+
bufferedArguments: string;
|
|
99
|
+
extraContent: OpenAICompatibleStreamingToolCallDelta['extra_content'];
|
|
68
100
|
};
|
|
69
101
|
|
|
70
102
|
export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
@@ -73,10 +105,27 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
73
105
|
readonly supportsStructuredOutputs: boolean;
|
|
74
106
|
|
|
75
107
|
readonly modelId: OpenAICompatibleChatModelId;
|
|
76
|
-
|
|
108
|
+
protected readonly config: OpenAICompatibleChatConfig;
|
|
77
109
|
private readonly failedResponseHandler: ResponseHandler<APICallError>;
|
|
78
110
|
private readonly chunkSchema; // type inferred via constructor
|
|
79
111
|
|
|
112
|
+
static [WORKFLOW_SERIALIZE](model: OpenAICompatibleChatLanguageModel) {
|
|
113
|
+
return serializeModelOptions({
|
|
114
|
+
modelId: model.modelId,
|
|
115
|
+
config: model.config,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
static [WORKFLOW_DESERIALIZE](options: {
|
|
120
|
+
modelId: string;
|
|
121
|
+
config: OpenAICompatibleChatConfig;
|
|
122
|
+
}) {
|
|
123
|
+
return new OpenAICompatibleChatLanguageModel(
|
|
124
|
+
options.modelId,
|
|
125
|
+
options.config,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
80
129
|
constructor(
|
|
81
130
|
modelId: OpenAICompatibleChatModelId,
|
|
82
131
|
config: OpenAICompatibleChatConfig,
|
|
@@ -111,6 +160,15 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
111
160
|
return this.config.transformRequestBody?.(args) ?? args;
|
|
112
161
|
}
|
|
113
162
|
|
|
163
|
+
private convertUsage(
|
|
164
|
+
usage: z.infer<typeof openaiCompatibleTokenUsageSchema>,
|
|
165
|
+
): LanguageModelV4Usage {
|
|
166
|
+
return (
|
|
167
|
+
this.config.convertUsage?.(usage) ??
|
|
168
|
+
convertOpenAICompatibleChatUsage(usage)
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
114
172
|
private async getArgs({
|
|
115
173
|
prompt,
|
|
116
174
|
maxOutputTokens,
|
|
@@ -119,6 +177,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
119
177
|
topK,
|
|
120
178
|
frequencyPenalty,
|
|
121
179
|
presencePenalty,
|
|
180
|
+
reasoning,
|
|
122
181
|
providerOptions,
|
|
123
182
|
stopSequences,
|
|
124
183
|
responseFormat,
|
|
@@ -137,11 +196,19 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
137
196
|
|
|
138
197
|
if (deprecatedOptions != null) {
|
|
139
198
|
warnings.push({
|
|
140
|
-
type: '
|
|
141
|
-
|
|
199
|
+
type: 'deprecated',
|
|
200
|
+
setting: "providerOptions key 'openai-compatible'",
|
|
201
|
+
message: "Use 'openaiCompatible' instead.",
|
|
142
202
|
});
|
|
143
203
|
}
|
|
144
204
|
|
|
205
|
+
// Warn when the raw (non-camelCase) provider name is used
|
|
206
|
+
warnIfDeprecatedProviderOptionsKey({
|
|
207
|
+
rawName: this.providerOptionsName,
|
|
208
|
+
providerOptions,
|
|
209
|
+
warnings,
|
|
210
|
+
});
|
|
211
|
+
|
|
145
212
|
const compatibleOptions = Object.assign(
|
|
146
213
|
deprecatedOptions ?? {},
|
|
147
214
|
(await parseProviderOptions({
|
|
@@ -154,6 +221,11 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
154
221
|
providerOptions,
|
|
155
222
|
schema: openaiCompatibleLanguageModelChatOptions,
|
|
156
223
|
})) ?? {},
|
|
224
|
+
(await parseProviderOptions({
|
|
225
|
+
provider: toCamelCase(this.providerOptionsName),
|
|
226
|
+
providerOptions,
|
|
227
|
+
schema: openaiCompatibleLanguageModelChatOptions,
|
|
228
|
+
})) ?? {},
|
|
157
229
|
);
|
|
158
230
|
|
|
159
231
|
const strictJsonSchema = compatibleOptions?.strictJsonSchema ?? true;
|
|
@@ -184,7 +256,13 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
184
256
|
toolChoice,
|
|
185
257
|
});
|
|
186
258
|
|
|
259
|
+
const metadataKey = resolveProviderOptionsKey(
|
|
260
|
+
this.providerOptionsName,
|
|
261
|
+
providerOptions,
|
|
262
|
+
);
|
|
263
|
+
|
|
187
264
|
return {
|
|
265
|
+
metadataKey,
|
|
188
266
|
args: {
|
|
189
267
|
// model id:
|
|
190
268
|
model: this.modelId,
|
|
@@ -217,9 +295,10 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
217
295
|
stop: stopSequences,
|
|
218
296
|
seed,
|
|
219
297
|
...Object.fromEntries(
|
|
220
|
-
Object.entries(
|
|
221
|
-
providerOptions?.[this.providerOptionsName]
|
|
222
|
-
|
|
298
|
+
Object.entries({
|
|
299
|
+
...providerOptions?.[this.providerOptionsName],
|
|
300
|
+
...providerOptions?.[toCamelCase(this.providerOptionsName)],
|
|
301
|
+
}).filter(
|
|
223
302
|
([key]) =>
|
|
224
303
|
!Object.keys(
|
|
225
304
|
openaiCompatibleLanguageModelChatOptions.shape,
|
|
@@ -227,7 +306,11 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
227
306
|
),
|
|
228
307
|
),
|
|
229
308
|
|
|
230
|
-
reasoning_effort:
|
|
309
|
+
reasoning_effort:
|
|
310
|
+
compatibleOptions.reasoningEffort ??
|
|
311
|
+
(isCustomReasoning(reasoning) && reasoning !== 'none'
|
|
312
|
+
? reasoning
|
|
313
|
+
: undefined),
|
|
231
314
|
verbosity: compatibleOptions.textVerbosity,
|
|
232
315
|
|
|
233
316
|
// messages:
|
|
@@ -244,7 +327,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
244
327
|
async doGenerate(
|
|
245
328
|
options: LanguageModelV4CallOptions,
|
|
246
329
|
): Promise<LanguageModelV4GenerateResult> {
|
|
247
|
-
const { args, warnings } = await this.getArgs({ ...options });
|
|
330
|
+
const { args, warnings, metadataKey } = await this.getArgs({ ...options });
|
|
248
331
|
|
|
249
332
|
const transformedBody = this.transformRequestBody(args);
|
|
250
333
|
const body = JSON.stringify(transformedBody);
|
|
@@ -258,7 +341,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
258
341
|
path: '/chat/completions',
|
|
259
342
|
modelId: this.modelId,
|
|
260
343
|
}),
|
|
261
|
-
headers: combineHeaders(this.config.headers(), options.headers),
|
|
344
|
+
headers: combineHeaders(this.config.headers?.(), options.headers),
|
|
262
345
|
body: transformedBody,
|
|
263
346
|
failedResponseHandler: this.failedResponseHandler,
|
|
264
347
|
successfulResponseHandler: createJsonResponseHandler(
|
|
@@ -300,7 +383,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
300
383
|
...(thoughtSignature
|
|
301
384
|
? {
|
|
302
385
|
providerMetadata: {
|
|
303
|
-
[
|
|
386
|
+
[metadataKey]: { thoughtSignature },
|
|
304
387
|
},
|
|
305
388
|
}
|
|
306
389
|
: {}),
|
|
@@ -310,7 +393,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
310
393
|
|
|
311
394
|
// provider metadata:
|
|
312
395
|
const providerMetadata: SharedV4ProviderMetadata = {
|
|
313
|
-
[
|
|
396
|
+
[metadataKey]: {},
|
|
314
397
|
...(await this.config.metadataExtractor?.extractMetadata?.({
|
|
315
398
|
parsedBody: rawResponse,
|
|
316
399
|
})),
|
|
@@ -318,11 +401,11 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
318
401
|
const completionTokenDetails =
|
|
319
402
|
responseBody.usage?.completion_tokens_details;
|
|
320
403
|
if (completionTokenDetails?.accepted_prediction_tokens != null) {
|
|
321
|
-
providerMetadata[
|
|
404
|
+
providerMetadata[metadataKey].acceptedPredictionTokens =
|
|
322
405
|
completionTokenDetails?.accepted_prediction_tokens;
|
|
323
406
|
}
|
|
324
407
|
if (completionTokenDetails?.rejected_prediction_tokens != null) {
|
|
325
|
-
providerMetadata[
|
|
408
|
+
providerMetadata[metadataKey].rejectedPredictionTokens =
|
|
326
409
|
completionTokenDetails?.rejected_prediction_tokens;
|
|
327
410
|
}
|
|
328
411
|
|
|
@@ -332,7 +415,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
332
415
|
unified: mapOpenAICompatibleFinishReason(choice.finish_reason),
|
|
333
416
|
raw: choice.finish_reason ?? undefined,
|
|
334
417
|
},
|
|
335
|
-
usage:
|
|
418
|
+
usage: this.convertUsage(responseBody.usage),
|
|
336
419
|
providerMetadata,
|
|
337
420
|
request: { body },
|
|
338
421
|
response: {
|
|
@@ -347,7 +430,9 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
347
430
|
async doStream(
|
|
348
431
|
options: LanguageModelV4CallOptions,
|
|
349
432
|
): Promise<LanguageModelV4StreamResult> {
|
|
350
|
-
const { args, warnings } = await this.getArgs({
|
|
433
|
+
const { args, warnings, metadataKey } = await this.getArgs({
|
|
434
|
+
...options,
|
|
435
|
+
});
|
|
351
436
|
|
|
352
437
|
const body = this.transformRequestBody({
|
|
353
438
|
...args,
|
|
@@ -367,7 +452,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
367
452
|
path: '/chat/completions',
|
|
368
453
|
modelId: this.modelId,
|
|
369
454
|
}),
|
|
370
|
-
headers: combineHeaders(this.config.headers(), options.headers),
|
|
455
|
+
headers: combineHeaders(this.config.headers?.(), options.headers),
|
|
371
456
|
body,
|
|
372
457
|
failedResponseHandler: this.failedResponseHandler,
|
|
373
458
|
successfulResponseHandler: createEventSourceResponseHandler(
|
|
@@ -377,16 +462,66 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
377
462
|
fetch: this.config.fetch,
|
|
378
463
|
});
|
|
379
464
|
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
465
|
+
const providerOptionsName = metadataKey;
|
|
466
|
+
let toolCallTracker: StreamingToolCallTracker<OpenAICompatibleStreamingToolCallDelta>;
|
|
467
|
+
|
|
468
|
+
// Buffers tool-call deltas by `index` until `function.name` is known.
|
|
469
|
+
// Some OpenAI-compatible providers send the first delta without
|
|
470
|
+
// `function.name`, which the shared tracker rejects on first chunk.
|
|
471
|
+
const pendingToolCalls = new Map<number, PendingToolCall>();
|
|
472
|
+
const forwardedToolCallIndices = new Set<number>();
|
|
473
|
+
|
|
474
|
+
const processToolCallDelta = (
|
|
475
|
+
toolCallDelta: OpenAICompatibleStreamingToolCallDelta,
|
|
476
|
+
) => {
|
|
477
|
+
const index = toolCallDelta.index;
|
|
478
|
+
|
|
479
|
+
if (index == null || forwardedToolCallIndices.has(index)) {
|
|
480
|
+
toolCallTracker.processDelta(toolCallDelta);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
let pending = pendingToolCalls.get(index);
|
|
485
|
+
if (pending == null) {
|
|
486
|
+
pending = {
|
|
487
|
+
id: toolCallDelta.id ?? null,
|
|
488
|
+
bufferedArguments: '',
|
|
489
|
+
extraContent: toolCallDelta.extra_content ?? null,
|
|
490
|
+
};
|
|
491
|
+
pendingToolCalls.set(index, pending);
|
|
492
|
+
} else {
|
|
493
|
+
if (pending.id == null && toolCallDelta.id != null) {
|
|
494
|
+
pending.id = toolCallDelta.id;
|
|
495
|
+
}
|
|
496
|
+
if (
|
|
497
|
+
pending.extraContent == null &&
|
|
498
|
+
toolCallDelta.extra_content != null
|
|
499
|
+
) {
|
|
500
|
+
pending.extraContent = toolCallDelta.extra_content;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const argumentsDelta = toolCallDelta.function?.arguments;
|
|
505
|
+
if (argumentsDelta != null) {
|
|
506
|
+
pending.bufferedArguments += argumentsDelta;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const name = toolCallDelta.function?.name;
|
|
510
|
+
if (name != null) {
|
|
511
|
+
const forwardDelta: OpenAICompatibleStreamingToolCallDelta = {
|
|
512
|
+
index,
|
|
513
|
+
id: pending.id,
|
|
514
|
+
function: {
|
|
515
|
+
name,
|
|
516
|
+
arguments: pending.bufferedArguments,
|
|
517
|
+
},
|
|
518
|
+
extra_content: pending.extraContent ?? undefined,
|
|
519
|
+
};
|
|
520
|
+
toolCallTracker.processDelta(forwardDelta);
|
|
521
|
+
pendingToolCalls.delete(index);
|
|
522
|
+
forwardedToolCallIndices.add(index);
|
|
523
|
+
}
|
|
524
|
+
};
|
|
390
525
|
|
|
391
526
|
let finishReason: LanguageModelV4FinishReason = {
|
|
392
527
|
unified: 'other',
|
|
@@ -395,9 +530,11 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
395
530
|
let usage: z.infer<typeof openaiCompatibleTokenUsageSchema> | undefined =
|
|
396
531
|
undefined;
|
|
397
532
|
let isFirstChunk = true;
|
|
398
|
-
const providerOptionsName = this.providerOptionsName;
|
|
399
533
|
let isActiveReasoning = false;
|
|
400
534
|
let isActiveText = false;
|
|
535
|
+
const convertUsage = (
|
|
536
|
+
usage: z.infer<typeof openaiCompatibleTokenUsageSchema>,
|
|
537
|
+
) => this.convertUsage(usage);
|
|
401
538
|
|
|
402
539
|
return {
|
|
403
540
|
stream: response.pipeThrough(
|
|
@@ -406,6 +543,22 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
406
543
|
LanguageModelV4StreamPart
|
|
407
544
|
>({
|
|
408
545
|
start(controller) {
|
|
546
|
+
toolCallTracker =
|
|
547
|
+
new StreamingToolCallTracker<OpenAICompatibleStreamingToolCallDelta>(
|
|
548
|
+
controller,
|
|
549
|
+
{
|
|
550
|
+
generateId,
|
|
551
|
+
extractMetadata: delta => {
|
|
552
|
+
const thoughtSignature =
|
|
553
|
+
delta.extra_content?.google?.thought_signature;
|
|
554
|
+
|
|
555
|
+
return thoughtSignature
|
|
556
|
+
? { [providerOptionsName]: { thoughtSignature } }
|
|
557
|
+
: undefined;
|
|
558
|
+
},
|
|
559
|
+
buildToolCallProviderMetadata: metadata => metadata,
|
|
560
|
+
},
|
|
561
|
+
);
|
|
409
562
|
controller.enqueue({ type: 'stream-start', warnings });
|
|
410
563
|
},
|
|
411
564
|
|
|
@@ -517,134 +670,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
517
670
|
}
|
|
518
671
|
|
|
519
672
|
for (const toolCallDelta of delta.tool_calls) {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
if (toolCalls[index] == null) {
|
|
523
|
-
if (toolCallDelta.id == null) {
|
|
524
|
-
throw new InvalidResponseDataError({
|
|
525
|
-
data: toolCallDelta,
|
|
526
|
-
message: `Expected 'id' to be a string.`,
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
if (toolCallDelta.function?.name == null) {
|
|
531
|
-
throw new InvalidResponseDataError({
|
|
532
|
-
data: toolCallDelta,
|
|
533
|
-
message: `Expected 'function.name' to be a string.`,
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
controller.enqueue({
|
|
538
|
-
type: 'tool-input-start',
|
|
539
|
-
id: toolCallDelta.id,
|
|
540
|
-
toolName: toolCallDelta.function.name,
|
|
541
|
-
});
|
|
542
|
-
|
|
543
|
-
toolCalls[index] = {
|
|
544
|
-
id: toolCallDelta.id,
|
|
545
|
-
type: 'function',
|
|
546
|
-
function: {
|
|
547
|
-
name: toolCallDelta.function.name,
|
|
548
|
-
arguments: toolCallDelta.function.arguments ?? '',
|
|
549
|
-
},
|
|
550
|
-
hasFinished: false,
|
|
551
|
-
thoughtSignature:
|
|
552
|
-
toolCallDelta.extra_content?.google?.thought_signature ??
|
|
553
|
-
undefined,
|
|
554
|
-
};
|
|
555
|
-
|
|
556
|
-
const toolCall = toolCalls[index];
|
|
557
|
-
|
|
558
|
-
if (
|
|
559
|
-
toolCall.function?.name != null &&
|
|
560
|
-
toolCall.function?.arguments != null
|
|
561
|
-
) {
|
|
562
|
-
// send delta if the argument text has already started:
|
|
563
|
-
if (toolCall.function.arguments.length > 0) {
|
|
564
|
-
controller.enqueue({
|
|
565
|
-
type: 'tool-input-delta',
|
|
566
|
-
id: toolCall.id,
|
|
567
|
-
delta: toolCall.function.arguments,
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// check if tool call is complete
|
|
572
|
-
// (some providers send the full tool call in one chunk):
|
|
573
|
-
if (isParsableJson(toolCall.function.arguments)) {
|
|
574
|
-
controller.enqueue({
|
|
575
|
-
type: 'tool-input-end',
|
|
576
|
-
id: toolCall.id,
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
controller.enqueue({
|
|
580
|
-
type: 'tool-call',
|
|
581
|
-
toolCallId: toolCall.id ?? generateId(),
|
|
582
|
-
toolName: toolCall.function.name,
|
|
583
|
-
input: toolCall.function.arguments,
|
|
584
|
-
...(toolCall.thoughtSignature
|
|
585
|
-
? {
|
|
586
|
-
providerMetadata: {
|
|
587
|
-
[providerOptionsName]: {
|
|
588
|
-
thoughtSignature: toolCall.thoughtSignature,
|
|
589
|
-
},
|
|
590
|
-
},
|
|
591
|
-
}
|
|
592
|
-
: {}),
|
|
593
|
-
});
|
|
594
|
-
toolCall.hasFinished = true;
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
continue;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
// existing tool call, merge if not finished
|
|
602
|
-
const toolCall = toolCalls[index];
|
|
603
|
-
|
|
604
|
-
if (toolCall.hasFinished) {
|
|
605
|
-
continue;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
if (toolCallDelta.function?.arguments != null) {
|
|
609
|
-
toolCall.function!.arguments +=
|
|
610
|
-
toolCallDelta.function?.arguments ?? '';
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// send delta
|
|
614
|
-
controller.enqueue({
|
|
615
|
-
type: 'tool-input-delta',
|
|
616
|
-
id: toolCall.id,
|
|
617
|
-
delta: toolCallDelta.function.arguments ?? '',
|
|
618
|
-
});
|
|
619
|
-
|
|
620
|
-
// check if tool call is complete
|
|
621
|
-
if (
|
|
622
|
-
toolCall.function?.name != null &&
|
|
623
|
-
toolCall.function?.arguments != null &&
|
|
624
|
-
isParsableJson(toolCall.function.arguments)
|
|
625
|
-
) {
|
|
626
|
-
controller.enqueue({
|
|
627
|
-
type: 'tool-input-end',
|
|
628
|
-
id: toolCall.id,
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
controller.enqueue({
|
|
632
|
-
type: 'tool-call',
|
|
633
|
-
toolCallId: toolCall.id ?? generateId(),
|
|
634
|
-
toolName: toolCall.function.name,
|
|
635
|
-
input: toolCall.function.arguments,
|
|
636
|
-
...(toolCall.thoughtSignature
|
|
637
|
-
? {
|
|
638
|
-
providerMetadata: {
|
|
639
|
-
[providerOptionsName]: {
|
|
640
|
-
thoughtSignature: toolCall.thoughtSignature,
|
|
641
|
-
},
|
|
642
|
-
},
|
|
643
|
-
}
|
|
644
|
-
: {}),
|
|
645
|
-
});
|
|
646
|
-
toolCall.hasFinished = true;
|
|
647
|
-
}
|
|
673
|
+
processToolCallDelta(toolCallDelta);
|
|
648
674
|
}
|
|
649
675
|
}
|
|
650
676
|
},
|
|
@@ -658,31 +684,19 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
658
684
|
controller.enqueue({ type: 'text-end', id: 'txt-0' });
|
|
659
685
|
}
|
|
660
686
|
|
|
661
|
-
//
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
)
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
id:
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
controller.enqueue({
|
|
671
|
-
type: 'tool-call',
|
|
672
|
-
toolCallId: toolCall.id ?? generateId(),
|
|
673
|
-
toolName: toolCall.function.name,
|
|
674
|
-
input: toolCall.function.arguments,
|
|
675
|
-
...(toolCall.thoughtSignature
|
|
676
|
-
? {
|
|
677
|
-
providerMetadata: {
|
|
678
|
-
[providerOptionsName]: {
|
|
679
|
-
thoughtSignature: toolCall.thoughtSignature,
|
|
680
|
-
},
|
|
681
|
-
},
|
|
682
|
-
}
|
|
683
|
-
: {}),
|
|
687
|
+
// Forward any tool-call deltas that never received a
|
|
688
|
+
// `function.name`. The tracker will throw on the missing name,
|
|
689
|
+
// preserving the original invalid-response semantics.
|
|
690
|
+
for (const [index, pending] of pendingToolCalls) {
|
|
691
|
+
toolCallTracker.processDelta({
|
|
692
|
+
index,
|
|
693
|
+
id: pending.id,
|
|
694
|
+
function: { arguments: pending.bufferedArguments },
|
|
684
695
|
});
|
|
685
696
|
}
|
|
697
|
+
pendingToolCalls.clear();
|
|
698
|
+
|
|
699
|
+
toolCallTracker.flush();
|
|
686
700
|
|
|
687
701
|
const providerMetadata: SharedV4ProviderMetadata = {
|
|
688
702
|
[providerOptionsName]: {},
|
|
@@ -706,7 +720,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV4 {
|
|
|
706
720
|
controller.enqueue({
|
|
707
721
|
type: 'finish',
|
|
708
722
|
finishReason,
|
|
709
|
-
usage:
|
|
723
|
+
usage: convertUsage(usage),
|
|
710
724
|
providerMetadata,
|
|
711
725
|
});
|
|
712
726
|
},
|
|
@@ -787,7 +801,7 @@ const chunkBaseSchema = z.looseObject({
|
|
|
787
801
|
z.object({
|
|
788
802
|
delta: z
|
|
789
803
|
.object({
|
|
790
|
-
role: z.enum(['assistant']).nullish(),
|
|
804
|
+
role: z.enum(['assistant', '']).nullish(),
|
|
791
805
|
content: z.string().nullish(),
|
|
792
806
|
// Most openai-compatible models set `reasoning_content`, but some
|
|
793
807
|
// providers serving `gpt-oss` set `reasoning`. See #7866
|