@ai-sdk/open-responses 2.0.34 → 2.0.36
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 +16 -0
- package/dist/index.d.ts +96 -3
- package/dist/index.js +523 -13
- package/dist/index.js.map +1 -1
- package/docs/06-open-responses.mdx +116 -2
- package/package.json +3 -3
- package/src/index.ts +10 -0
- package/src/open-responses-extension.ts +387 -0
- package/src/open-responses-provider.ts +15 -0
- package/src/responses/convert-to-open-responses-input.ts +193 -0
- package/src/responses/open-responses-api.ts +14 -4
- package/src/responses/open-responses-config.ts +2 -0
- package/src/responses/open-responses-language-model.ts +270 -16
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
UnsupportedFunctionalityError,
|
|
3
|
+
type LanguageModelV4ProviderTool,
|
|
3
4
|
type LanguageModelV4Prompt,
|
|
4
5
|
type SharedV4Warning,
|
|
5
6
|
} from '@ai-sdk/provider';
|
|
@@ -8,6 +9,12 @@ import {
|
|
|
8
9
|
getTopLevelMediaType,
|
|
9
10
|
resolveFullMediaType,
|
|
10
11
|
} from '@ai-sdk/provider-utils';
|
|
12
|
+
import {
|
|
13
|
+
isOpenResponsesExtensionItem,
|
|
14
|
+
type OpenResponsesExtensionInputPart,
|
|
15
|
+
type OpenResponsesExtensionItem,
|
|
16
|
+
type OpenResponsesExtensionRegistry,
|
|
17
|
+
} from '../open-responses-extension';
|
|
11
18
|
import type {
|
|
12
19
|
FunctionCallOutputItemParam,
|
|
13
20
|
InputFileContentParam,
|
|
@@ -22,9 +29,13 @@ import type {
|
|
|
22
29
|
export async function convertToOpenResponsesInput({
|
|
23
30
|
prompt,
|
|
24
31
|
providerOptionsName = 'open-responses',
|
|
32
|
+
extensionRegistry,
|
|
33
|
+
providerToolsByName = new Map(),
|
|
25
34
|
}: {
|
|
26
35
|
prompt: LanguageModelV4Prompt;
|
|
27
36
|
providerOptionsName?: string;
|
|
37
|
+
extensionRegistry?: OpenResponsesExtensionRegistry;
|
|
38
|
+
providerToolsByName?: Map<string, LanguageModelV4ProviderTool>;
|
|
28
39
|
}): Promise<{
|
|
29
40
|
input: OpenResponsesRequestBody['input'];
|
|
30
41
|
instructions: string | undefined;
|
|
@@ -33,6 +44,7 @@ export async function convertToOpenResponsesInput({
|
|
|
33
44
|
const input: OpenResponsesRequestBody['input'] = [];
|
|
34
45
|
const warnings: Array<SharedV4Warning> = [];
|
|
35
46
|
const systemMessages: string[] = [];
|
|
47
|
+
const replayedExtensionItems = new Set<string>();
|
|
36
48
|
|
|
37
49
|
for (const { role, content } of prompt) {
|
|
38
50
|
switch (role) {
|
|
@@ -125,6 +137,52 @@ export async function convertToOpenResponsesInput({
|
|
|
125
137
|
};
|
|
126
138
|
|
|
127
139
|
for (const part of content) {
|
|
140
|
+
const extensionReplay = getExtensionReplay({
|
|
141
|
+
part,
|
|
142
|
+
providerOptionsName,
|
|
143
|
+
extensionRegistry,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (extensionReplay != null) {
|
|
147
|
+
flushAssistantContent();
|
|
148
|
+
const replayItem = extensionReplay.item;
|
|
149
|
+
if (replayItem != null) {
|
|
150
|
+
const replayKey = `${replayItem.type}:${replayItem.id}`;
|
|
151
|
+
if (!replayedExtensionItems.has(replayKey)) {
|
|
152
|
+
input.push(replayItem);
|
|
153
|
+
replayedExtensionItems.add(replayKey);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (part.type === 'tool-call' || part.type === 'tool-result') {
|
|
160
|
+
const providerTool = providerToolsByName.get(part.toolName);
|
|
161
|
+
const extension =
|
|
162
|
+
providerTool == null
|
|
163
|
+
? undefined
|
|
164
|
+
: extensionRegistry?.byProviderToolId.get(providerTool.id);
|
|
165
|
+
|
|
166
|
+
if (providerTool != null && extension != null) {
|
|
167
|
+
flushAssistantContent();
|
|
168
|
+
const encoded = await encodeExtensionInputPart({
|
|
169
|
+
extensionRegistry,
|
|
170
|
+
part,
|
|
171
|
+
providerTool,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
if (encoded == null) {
|
|
175
|
+
warnings.push({
|
|
176
|
+
type: 'unsupported',
|
|
177
|
+
feature: `provider-defined tool ${providerTool.id} ${part.type} history`,
|
|
178
|
+
});
|
|
179
|
+
} else {
|
|
180
|
+
input.push(...encoded);
|
|
181
|
+
}
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
128
186
|
switch (part.type) {
|
|
129
187
|
case 'reasoning': {
|
|
130
188
|
flushAssistantContent();
|
|
@@ -243,6 +301,48 @@ export async function convertToOpenResponsesInput({
|
|
|
243
301
|
case 'tool': {
|
|
244
302
|
for (const part of content) {
|
|
245
303
|
if (part.type === 'tool-result') {
|
|
304
|
+
const extensionReplay = getExtensionReplay({
|
|
305
|
+
part,
|
|
306
|
+
providerOptionsName,
|
|
307
|
+
extensionRegistry,
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
if (extensionReplay != null) {
|
|
311
|
+
const replayItem = extensionReplay.item;
|
|
312
|
+
if (replayItem != null) {
|
|
313
|
+
const replayKey = `${replayItem.type}:${replayItem.id}`;
|
|
314
|
+
if (!replayedExtensionItems.has(replayKey)) {
|
|
315
|
+
input.push(replayItem);
|
|
316
|
+
replayedExtensionItems.add(replayKey);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const providerTool = providerToolsByName.get(part.toolName);
|
|
323
|
+
const extension =
|
|
324
|
+
providerTool == null
|
|
325
|
+
? undefined
|
|
326
|
+
: extensionRegistry?.byProviderToolId.get(providerTool.id);
|
|
327
|
+
|
|
328
|
+
if (providerTool != null && extension != null) {
|
|
329
|
+
const encoded = await encodeExtensionInputPart({
|
|
330
|
+
extensionRegistry,
|
|
331
|
+
part,
|
|
332
|
+
providerTool,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
if (encoded == null) {
|
|
336
|
+
warnings.push({
|
|
337
|
+
type: 'unsupported',
|
|
338
|
+
feature: `provider-defined tool ${providerTool.id} tool-result history`,
|
|
339
|
+
});
|
|
340
|
+
} else {
|
|
341
|
+
input.push(...encoded);
|
|
342
|
+
}
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
|
|
246
346
|
const output = part.output;
|
|
247
347
|
let contentValue: FunctionCallOutputItemParam['output'];
|
|
248
348
|
|
|
@@ -346,6 +446,99 @@ export async function convertToOpenResponsesInput({
|
|
|
346
446
|
};
|
|
347
447
|
}
|
|
348
448
|
|
|
449
|
+
async function encodeExtensionInputPart({
|
|
450
|
+
extensionRegistry,
|
|
451
|
+
part,
|
|
452
|
+
providerTool,
|
|
453
|
+
}: {
|
|
454
|
+
extensionRegistry: OpenResponsesExtensionRegistry | undefined;
|
|
455
|
+
part: OpenResponsesExtensionInputPart;
|
|
456
|
+
providerTool: LanguageModelV4ProviderTool;
|
|
457
|
+
}): Promise<OpenResponsesExtensionItem[] | undefined> {
|
|
458
|
+
const extension = extensionRegistry?.byProviderToolId.get(providerTool.id);
|
|
459
|
+
const encodeInputItem = extension?.encodeInputItem;
|
|
460
|
+
const itemTypes = extension?.itemTypes;
|
|
461
|
+
if (encodeInputItem == null || itemTypes == null) {
|
|
462
|
+
return undefined;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
try {
|
|
466
|
+
const value = await encodeInputItem({
|
|
467
|
+
part,
|
|
468
|
+
tool: providerTool,
|
|
469
|
+
});
|
|
470
|
+
const items =
|
|
471
|
+
value == null ? undefined : Array.isArray(value) ? value : [value];
|
|
472
|
+
|
|
473
|
+
if (
|
|
474
|
+
items == null ||
|
|
475
|
+
items.length === 0 ||
|
|
476
|
+
!items.every(
|
|
477
|
+
item =>
|
|
478
|
+
isOpenResponsesExtensionItem(item) && itemTypes.includes(item.type),
|
|
479
|
+
)
|
|
480
|
+
) {
|
|
481
|
+
return undefined;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return items;
|
|
485
|
+
} catch {
|
|
486
|
+
return undefined;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function getExtensionReplay({
|
|
491
|
+
part,
|
|
492
|
+
providerOptionsName,
|
|
493
|
+
extensionRegistry,
|
|
494
|
+
}: {
|
|
495
|
+
part: {
|
|
496
|
+
providerOptions?: Record<string, unknown>;
|
|
497
|
+
};
|
|
498
|
+
providerOptionsName: string;
|
|
499
|
+
extensionRegistry: OpenResponsesExtensionRegistry | undefined;
|
|
500
|
+
}): { item?: OpenResponsesExtensionItem } | undefined {
|
|
501
|
+
const extensionData = getProviderData(
|
|
502
|
+
part,
|
|
503
|
+
providerOptionsName,
|
|
504
|
+
)?.openResponsesExtension;
|
|
505
|
+
|
|
506
|
+
if (
|
|
507
|
+
extensionData == null ||
|
|
508
|
+
typeof extensionData !== 'object' ||
|
|
509
|
+
Array.isArray(extensionData)
|
|
510
|
+
) {
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const { id, item, itemId } = extensionData as {
|
|
515
|
+
id?: unknown;
|
|
516
|
+
item?: unknown;
|
|
517
|
+
itemId?: unknown;
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
if (typeof id !== 'string') {
|
|
521
|
+
return undefined;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const extension = extensionRegistry?.byExtensionId.get(
|
|
525
|
+
id as LanguageModelV4ProviderTool['id'],
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
if (extension == null) {
|
|
529
|
+
return undefined;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (
|
|
533
|
+
isOpenResponsesExtensionItem(item) &&
|
|
534
|
+
extension.itemTypes?.includes(item.type)
|
|
535
|
+
) {
|
|
536
|
+
return { item };
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return typeof itemId === 'string' ? {} : undefined;
|
|
540
|
+
}
|
|
541
|
+
|
|
349
542
|
function getProviderData(
|
|
350
543
|
part: {
|
|
351
544
|
providerOptions?: Record<string, unknown>;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { JSONSchema7 } from '@ai-sdk/provider';
|
|
2
2
|
import { lazySchema, zodSchema } from '@ai-sdk/provider-utils';
|
|
3
3
|
import { z } from 'zod/v4';
|
|
4
|
+
import type {
|
|
5
|
+
OpenResponsesExtensionEvent,
|
|
6
|
+
OpenResponsesExtensionItem,
|
|
7
|
+
OpenResponsesExtensionRecord,
|
|
8
|
+
} from '../open-responses-extension';
|
|
4
9
|
|
|
5
10
|
export const openResponsesErrorSchema = lazySchema(() =>
|
|
6
11
|
zodSchema(
|
|
@@ -263,7 +268,8 @@ export type AllowedToolsParam = {
|
|
|
263
268
|
export type ToolChoiceParam =
|
|
264
269
|
| ToolChoiceValueEnum
|
|
265
270
|
| SpecificFunctionParam
|
|
266
|
-
| AllowedToolsParam
|
|
271
|
+
| AllowedToolsParam
|
|
272
|
+
| OpenResponsesExtensionRecord;
|
|
267
273
|
|
|
268
274
|
// ============================================================================
|
|
269
275
|
// Configuration Types
|
|
@@ -483,7 +489,8 @@ export type OutputItem =
|
|
|
483
489
|
| FunctionCall
|
|
484
490
|
| FunctionCallOutput
|
|
485
491
|
| Message
|
|
486
|
-
| ReasoningBody
|
|
492
|
+
| ReasoningBody
|
|
493
|
+
| OpenResponsesExtensionItem;
|
|
487
494
|
|
|
488
495
|
/**
|
|
489
496
|
* Details about why the response was incomplete.
|
|
@@ -626,6 +633,7 @@ export type OpenResponsesRequestBody = {
|
|
|
626
633
|
| AssistantMessageItemParam
|
|
627
634
|
| FunctionCallItemParam
|
|
628
635
|
| FunctionCallOutputItemParam
|
|
636
|
+
| OpenResponsesExtensionRecord
|
|
629
637
|
>;
|
|
630
638
|
|
|
631
639
|
/**
|
|
@@ -643,7 +651,7 @@ export type OpenResponsesRequestBody = {
|
|
|
643
651
|
/**
|
|
644
652
|
* A list of tools that the model may call while generating the response.
|
|
645
653
|
*/
|
|
646
|
-
tools?: FunctionToolParam
|
|
654
|
+
tools?: Array<FunctionToolParam | OpenResponsesExtensionRecord>;
|
|
647
655
|
|
|
648
656
|
/**
|
|
649
657
|
* Controls which tool the model should use, if any.
|
|
@@ -1235,4 +1243,6 @@ export type OpenResponsesChunk =
|
|
|
1235
1243
|
| ResponseReasoningSummaryPartAddedEvent
|
|
1236
1244
|
| ResponseReasoningSummaryPartDoneEvent
|
|
1237
1245
|
// Error Event
|
|
1238
|
-
| ResponseErrorEvent
|
|
1246
|
+
| ResponseErrorEvent
|
|
1247
|
+
// Registered extension event
|
|
1248
|
+
| OpenResponsesExtensionEvent;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { FetchFunction } from '@ai-sdk/provider-utils';
|
|
2
|
+
import type { OpenResponsesExtensionRegistry } from '../open-responses-extension';
|
|
2
3
|
|
|
3
4
|
export type OpenResponsesConfig = {
|
|
4
5
|
provider: string;
|
|
@@ -7,4 +8,5 @@ export type OpenResponsesConfig = {
|
|
|
7
8
|
headers?: () => Record<string, string | undefined>;
|
|
8
9
|
fetch?: FetchFunction;
|
|
9
10
|
generateId: () => string;
|
|
11
|
+
extensionRegistry?: OpenResponsesExtensionRegistry;
|
|
10
12
|
};
|
|
@@ -22,17 +22,28 @@ import {
|
|
|
22
22
|
mapReasoningToProviderEffort,
|
|
23
23
|
parseProviderOptions,
|
|
24
24
|
postJsonToApi,
|
|
25
|
+
SerializationError,
|
|
25
26
|
serializeModelOptions,
|
|
26
27
|
WORKFLOW_SERIALIZE,
|
|
27
28
|
WORKFLOW_DESERIALIZE,
|
|
28
29
|
type ParseResult,
|
|
29
30
|
} from '@ai-sdk/provider-utils';
|
|
30
31
|
import { z } from 'zod/v4';
|
|
32
|
+
import {
|
|
33
|
+
createOpenResponsesExtensionRegistry,
|
|
34
|
+
isOpenResponsesExtensionEvent,
|
|
35
|
+
isOpenResponsesExtensionItem,
|
|
36
|
+
isOpenResponsesJSONObject,
|
|
37
|
+
type OpenResponsesExtension,
|
|
38
|
+
type OpenResponsesExtensionContentPart,
|
|
39
|
+
type OpenResponsesExtensionItem,
|
|
40
|
+
type OpenResponsesExtensionRecord,
|
|
41
|
+
type OpenResponsesExtensionRegistry,
|
|
42
|
+
} from '../open-responses-extension';
|
|
31
43
|
import { convertToOpenResponsesInput } from './convert-to-open-responses-input';
|
|
32
44
|
import {
|
|
33
45
|
openResponsesErrorSchema,
|
|
34
46
|
type Annotation,
|
|
35
|
-
type FunctionToolParam,
|
|
36
47
|
type OpenResponsesRequestBody,
|
|
37
48
|
type OpenResponsesResponseBody,
|
|
38
49
|
type OpenResponsesChunk,
|
|
@@ -49,8 +60,16 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
49
60
|
readonly modelId: string;
|
|
50
61
|
|
|
51
62
|
private readonly config: OpenResponsesConfig;
|
|
63
|
+
private readonly extensionRegistry: OpenResponsesExtensionRegistry;
|
|
52
64
|
|
|
53
65
|
static [WORKFLOW_SERIALIZE](model: OpenResponsesLanguageModel) {
|
|
66
|
+
if (model.extensionRegistry.byExtensionId.size > 0) {
|
|
67
|
+
throw new SerializationError({
|
|
68
|
+
message:
|
|
69
|
+
'Open Responses models with registered extensions cannot be serialized across workflow boundaries. Recreate the provider with its extension codecs inside the workflow step.',
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
54
73
|
return serializeModelOptions({
|
|
55
74
|
modelId: model.modelId,
|
|
56
75
|
config: model.config,
|
|
@@ -67,6 +86,8 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
67
86
|
constructor(modelId: string, config: OpenResponsesConfig) {
|
|
68
87
|
this.modelId = modelId;
|
|
69
88
|
this.config = config;
|
|
89
|
+
this.extensionRegistry =
|
|
90
|
+
config.extensionRegistry ?? createOpenResponsesExtensionRegistry();
|
|
70
91
|
}
|
|
71
92
|
|
|
72
93
|
readonly supportedUrls: Record<string, RegExp[]> = {
|
|
@@ -110,6 +131,12 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
110
131
|
warnings.push({ type: 'unsupported', feature: 'seed' });
|
|
111
132
|
}
|
|
112
133
|
|
|
134
|
+
const providerToolsByName = new Map(
|
|
135
|
+
(tools ?? [])
|
|
136
|
+
.filter(tool => tool.type === 'provider')
|
|
137
|
+
.map(tool => [tool.name, tool]),
|
|
138
|
+
);
|
|
139
|
+
|
|
113
140
|
const {
|
|
114
141
|
input,
|
|
115
142
|
instructions,
|
|
@@ -117,21 +144,63 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
117
144
|
} = await convertToOpenResponsesInput({
|
|
118
145
|
prompt,
|
|
119
146
|
providerOptionsName: this.config.providerOptionsName,
|
|
147
|
+
extensionRegistry: this.extensionRegistry,
|
|
148
|
+
providerToolsByName,
|
|
120
149
|
});
|
|
121
150
|
|
|
122
151
|
warnings.push(...inputWarnings);
|
|
123
152
|
|
|
124
|
-
|
|
125
|
-
const
|
|
153
|
+
const convertedTools: NonNullable<OpenResponsesRequestBody['tools']> = [];
|
|
154
|
+
const encodedProviderToolsByName = new Map<
|
|
155
|
+
string,
|
|
156
|
+
{
|
|
157
|
+
toolType: OpenResponsesExtensionRecord['type'];
|
|
158
|
+
encodeToolChoice: OpenResponsesExtension['encodeToolChoice'];
|
|
159
|
+
tool: Extract<
|
|
160
|
+
NonNullable<LanguageModelV4CallOptions['tools']>[number],
|
|
161
|
+
{ type: 'provider' }
|
|
162
|
+
>;
|
|
163
|
+
}
|
|
164
|
+
>();
|
|
126
165
|
|
|
127
166
|
for (const tool of tools ?? []) {
|
|
128
167
|
if (tool.type === 'provider') {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
168
|
+
const extension = this.extensionRegistry.byProviderToolId.get(tool.id);
|
|
169
|
+
let encoded: OpenResponsesExtensionRecord | undefined;
|
|
170
|
+
|
|
171
|
+
if (extension != null) {
|
|
172
|
+
try {
|
|
173
|
+
const fields = await extension.encodeTool({
|
|
174
|
+
name: tool.name,
|
|
175
|
+
args: tool.args,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
if (isOpenResponsesJSONObject(fields)) {
|
|
179
|
+
encoded = {
|
|
180
|
+
...fields,
|
|
181
|
+
type: extension.toolType,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
} catch {
|
|
185
|
+
// Encoding failures are reported as unsupported below.
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (encoded == null) {
|
|
190
|
+
warnings.push({
|
|
191
|
+
type: 'unsupported',
|
|
192
|
+
feature: `provider-defined tool ${tool.id}`,
|
|
193
|
+
});
|
|
194
|
+
} else if (extension != null) {
|
|
195
|
+
convertedTools.push(encoded);
|
|
196
|
+
encodedProviderToolsByName.set(tool.name, {
|
|
197
|
+
toolType: extension.toolType,
|
|
198
|
+
encodeToolChoice: extension.encodeToolChoice,
|
|
199
|
+
tool,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
133
202
|
} else {
|
|
134
|
-
|
|
203
|
+
convertedTools.push({
|
|
135
204
|
type: 'function',
|
|
136
205
|
name: tool.name,
|
|
137
206
|
description: tool.description,
|
|
@@ -142,12 +211,47 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
142
211
|
}
|
|
143
212
|
|
|
144
213
|
// Convert tool choice to the Open Responses format
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
214
|
+
let convertedToolChoice: ToolChoiceParam | undefined;
|
|
215
|
+
if (toolChoice?.type === 'tool') {
|
|
216
|
+
const registeredTool = encodedProviderToolsByName.get(
|
|
217
|
+
toolChoice.toolName,
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
if (registeredTool == null) {
|
|
221
|
+
if (!providerToolsByName.has(toolChoice.toolName)) {
|
|
222
|
+
convertedToolChoice = {
|
|
223
|
+
type: 'function',
|
|
224
|
+
name: toolChoice.toolName,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
const { encodeToolChoice, tool, toolType } = registeredTool;
|
|
229
|
+
let fields: unknown = {};
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
fields = await encodeToolChoice?.({
|
|
233
|
+
name: tool.name,
|
|
234
|
+
args: tool.args,
|
|
235
|
+
});
|
|
236
|
+
} catch {
|
|
237
|
+
fields = undefined;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (encodeToolChoice != null && !isOpenResponsesJSONObject(fields)) {
|
|
241
|
+
warnings.push({
|
|
242
|
+
type: 'unsupported',
|
|
243
|
+
feature: `tool choice for provider-defined tool ${tool.id}`,
|
|
244
|
+
});
|
|
245
|
+
} else {
|
|
246
|
+
convertedToolChoice = {
|
|
247
|
+
...(isOpenResponsesJSONObject(fields) ? fields : {}),
|
|
248
|
+
type: toolType,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
convertedToolChoice = toolChoice?.type;
|
|
254
|
+
}
|
|
151
255
|
|
|
152
256
|
const textFormat =
|
|
153
257
|
responseFormat?.type === 'json'
|
|
@@ -210,7 +314,7 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
210
314
|
}),
|
|
211
315
|
}
|
|
212
316
|
: undefined,
|
|
213
|
-
tools:
|
|
317
|
+
tools: convertedTools.length ? convertedTools : undefined,
|
|
214
318
|
tool_choice: convertedToolChoice,
|
|
215
319
|
...(textFormat != null && { text: { format: textFormat } }),
|
|
216
320
|
},
|
|
@@ -336,6 +440,25 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
336
440
|
});
|
|
337
441
|
break;
|
|
338
442
|
}
|
|
443
|
+
|
|
444
|
+
default: {
|
|
445
|
+
if (!isOpenResponsesExtensionItem(part)) {
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const decoded = await decodeExtensionItem({
|
|
450
|
+
extensionRegistry: this.extensionRegistry,
|
|
451
|
+
item: part,
|
|
452
|
+
mode: 'generate',
|
|
453
|
+
providerOptionsName: this.config.providerOptionsName,
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
if (decoded != null) {
|
|
457
|
+
content.push(...decoded);
|
|
458
|
+
hasToolCalls ||= decoded.some(part => part.type === 'tool-call');
|
|
459
|
+
}
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
339
462
|
}
|
|
340
463
|
}
|
|
341
464
|
|
|
@@ -455,6 +578,8 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
455
578
|
{ toolName?: string; toolCallId?: string; arguments?: string }
|
|
456
579
|
>();
|
|
457
580
|
const providerOptionsName = this.config.providerOptionsName;
|
|
581
|
+
const extensionRegistry = this.extensionRegistry;
|
|
582
|
+
const extensionStreamState = new Map<string, unknown>();
|
|
458
583
|
|
|
459
584
|
return {
|
|
460
585
|
stream: response.pipeThrough(
|
|
@@ -466,7 +591,7 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
466
591
|
controller.enqueue({ type: 'stream-start', warnings });
|
|
467
592
|
},
|
|
468
593
|
|
|
469
|
-
transform(parseResult, controller) {
|
|
594
|
+
async transform(parseResult, controller) {
|
|
470
595
|
if (options.includeRawChunks) {
|
|
471
596
|
controller.enqueue({
|
|
472
597
|
type: 'raw',
|
|
@@ -481,6 +606,27 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
481
606
|
|
|
482
607
|
const chunk = parseResult.value;
|
|
483
608
|
|
|
609
|
+
if (isOpenResponsesExtensionEvent(chunk)) {
|
|
610
|
+
const extension = extensionRegistry.byEventType.get(chunk.type);
|
|
611
|
+
if (extension?.decodeEvent != null) {
|
|
612
|
+
try {
|
|
613
|
+
const decoded = await extension.decodeEvent({
|
|
614
|
+
event: chunk,
|
|
615
|
+
state: extensionStreamState,
|
|
616
|
+
});
|
|
617
|
+
for (const part of decoded ?? []) {
|
|
618
|
+
controller.enqueue(part);
|
|
619
|
+
hasToolCalls ||=
|
|
620
|
+
part.type === 'tool-call' ||
|
|
621
|
+
part.type === 'tool-input-start';
|
|
622
|
+
}
|
|
623
|
+
} catch (error) {
|
|
624
|
+
controller.enqueue({ type: 'error', error });
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
|
|
484
630
|
// Tool call events (single-shot tool-call when complete)
|
|
485
631
|
if (
|
|
486
632
|
chunk.type === 'response.output_item.added' &&
|
|
@@ -541,6 +687,25 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
541
687
|
hasToolCalls = true;
|
|
542
688
|
|
|
543
689
|
toolCallsByItemId.delete(chunk.item.id);
|
|
690
|
+
} else if (
|
|
691
|
+
chunk.type === 'response.output_item.done' &&
|
|
692
|
+
isOpenResponsesExtensionItem(chunk.item)
|
|
693
|
+
) {
|
|
694
|
+
try {
|
|
695
|
+
const decoded = await decodeExtensionItem({
|
|
696
|
+
extensionRegistry,
|
|
697
|
+
item: chunk.item,
|
|
698
|
+
mode: 'stream',
|
|
699
|
+
providerOptionsName,
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
for (const part of decoded ?? []) {
|
|
703
|
+
controller.enqueue(part);
|
|
704
|
+
hasToolCalls ||= part.type === 'tool-call';
|
|
705
|
+
}
|
|
706
|
+
} catch (error) {
|
|
707
|
+
controller.enqueue({ type: 'error', error });
|
|
708
|
+
}
|
|
544
709
|
}
|
|
545
710
|
|
|
546
711
|
// Reasoning events (note: response.reasoning_text.delta is an LM Studio extension, not in official spec)
|
|
@@ -729,6 +894,95 @@ function createReasoningProviderMetadata({
|
|
|
729
894
|
};
|
|
730
895
|
}
|
|
731
896
|
|
|
897
|
+
async function decodeExtensionItem({
|
|
898
|
+
extensionRegistry,
|
|
899
|
+
item,
|
|
900
|
+
mode,
|
|
901
|
+
providerOptionsName,
|
|
902
|
+
}: {
|
|
903
|
+
extensionRegistry: OpenResponsesExtensionRegistry;
|
|
904
|
+
item: OpenResponsesExtensionItem;
|
|
905
|
+
mode: 'generate' | 'stream';
|
|
906
|
+
providerOptionsName: string;
|
|
907
|
+
}): Promise<OpenResponsesExtensionContentPart[] | undefined> {
|
|
908
|
+
const extension = extensionRegistry.byItemType.get(item.type);
|
|
909
|
+
if (extension == null) {
|
|
910
|
+
return undefined;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
const decoded = await extension.decodeItem({ item, mode });
|
|
914
|
+
if (decoded == null) {
|
|
915
|
+
return undefined;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
return [
|
|
919
|
+
createExtensionReplayCarrier({
|
|
920
|
+
extension,
|
|
921
|
+
item,
|
|
922
|
+
providerOptionsName,
|
|
923
|
+
}),
|
|
924
|
+
...decoded.map(part =>
|
|
925
|
+
addExtensionItemReferenceMetadata({
|
|
926
|
+
extension,
|
|
927
|
+
item,
|
|
928
|
+
part,
|
|
929
|
+
providerOptionsName,
|
|
930
|
+
}),
|
|
931
|
+
),
|
|
932
|
+
];
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function createExtensionReplayCarrier({
|
|
936
|
+
extension,
|
|
937
|
+
item,
|
|
938
|
+
providerOptionsName,
|
|
939
|
+
}: {
|
|
940
|
+
extension: OpenResponsesExtension;
|
|
941
|
+
item: OpenResponsesExtensionItem;
|
|
942
|
+
providerOptionsName: string;
|
|
943
|
+
}): OpenResponsesExtensionContentPart {
|
|
944
|
+
return {
|
|
945
|
+
type: 'custom',
|
|
946
|
+
kind: 'open-responses.extension-replay',
|
|
947
|
+
providerMetadata: {
|
|
948
|
+
[providerOptionsName]: {
|
|
949
|
+
openResponsesExtension: {
|
|
950
|
+
id: extension.id,
|
|
951
|
+
item,
|
|
952
|
+
},
|
|
953
|
+
},
|
|
954
|
+
},
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function addExtensionItemReferenceMetadata({
|
|
959
|
+
extension,
|
|
960
|
+
item,
|
|
961
|
+
part,
|
|
962
|
+
providerOptionsName,
|
|
963
|
+
}: {
|
|
964
|
+
extension: OpenResponsesExtension;
|
|
965
|
+
item: OpenResponsesExtensionItem;
|
|
966
|
+
part: OpenResponsesExtensionContentPart;
|
|
967
|
+
providerOptionsName: string;
|
|
968
|
+
}): OpenResponsesExtensionContentPart {
|
|
969
|
+
const providerMetadata = part.providerMetadata ?? {};
|
|
970
|
+
|
|
971
|
+
return {
|
|
972
|
+
...part,
|
|
973
|
+
providerMetadata: {
|
|
974
|
+
...providerMetadata,
|
|
975
|
+
[providerOptionsName]: {
|
|
976
|
+
...providerMetadata[providerOptionsName],
|
|
977
|
+
openResponsesExtension: {
|
|
978
|
+
id: extension.id,
|
|
979
|
+
itemId: item.id,
|
|
980
|
+
},
|
|
981
|
+
},
|
|
982
|
+
},
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
|
|
732
986
|
function getOutputTextAnnotations(value: unknown): Annotation[] {
|
|
733
987
|
if (
|
|
734
988
|
value == null ||
|