@ai-sdk/open-responses 2.0.31 → 2.0.35
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 +31 -0
- package/dist/index.d.ts +96 -3
- package/dist/index.js +560 -14
- package/dist/index.js.map +1 -1
- package/docs/06-open-responses.mdx +116 -2
- package/package.json +2 -2
- 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 +313 -17
|
@@ -16,22 +16,34 @@ import {
|
|
|
16
16
|
createEventSourceResponseHandler,
|
|
17
17
|
createJsonErrorResponseHandler,
|
|
18
18
|
createJsonResponseHandler,
|
|
19
|
+
createProviderStreamError,
|
|
19
20
|
isCustomReasoning,
|
|
20
21
|
jsonSchema,
|
|
21
22
|
mapReasoningToProviderEffort,
|
|
22
23
|
parseProviderOptions,
|
|
23
24
|
postJsonToApi,
|
|
25
|
+
SerializationError,
|
|
24
26
|
serializeModelOptions,
|
|
25
27
|
WORKFLOW_SERIALIZE,
|
|
26
28
|
WORKFLOW_DESERIALIZE,
|
|
27
29
|
type ParseResult,
|
|
28
30
|
} from '@ai-sdk/provider-utils';
|
|
29
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';
|
|
30
43
|
import { convertToOpenResponsesInput } from './convert-to-open-responses-input';
|
|
31
44
|
import {
|
|
32
45
|
openResponsesErrorSchema,
|
|
33
46
|
type Annotation,
|
|
34
|
-
type FunctionToolParam,
|
|
35
47
|
type OpenResponsesRequestBody,
|
|
36
48
|
type OpenResponsesResponseBody,
|
|
37
49
|
type OpenResponsesChunk,
|
|
@@ -48,8 +60,16 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
48
60
|
readonly modelId: string;
|
|
49
61
|
|
|
50
62
|
private readonly config: OpenResponsesConfig;
|
|
63
|
+
private readonly extensionRegistry: OpenResponsesExtensionRegistry;
|
|
51
64
|
|
|
52
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
|
+
|
|
53
73
|
return serializeModelOptions({
|
|
54
74
|
modelId: model.modelId,
|
|
55
75
|
config: model.config,
|
|
@@ -66,6 +86,8 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
66
86
|
constructor(modelId: string, config: OpenResponsesConfig) {
|
|
67
87
|
this.modelId = modelId;
|
|
68
88
|
this.config = config;
|
|
89
|
+
this.extensionRegistry =
|
|
90
|
+
config.extensionRegistry ?? createOpenResponsesExtensionRegistry();
|
|
69
91
|
}
|
|
70
92
|
|
|
71
93
|
readonly supportedUrls: Record<string, RegExp[]> = {
|
|
@@ -109,6 +131,12 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
109
131
|
warnings.push({ type: 'unsupported', feature: 'seed' });
|
|
110
132
|
}
|
|
111
133
|
|
|
134
|
+
const providerToolsByName = new Map(
|
|
135
|
+
(tools ?? [])
|
|
136
|
+
.filter(tool => tool.type === 'provider')
|
|
137
|
+
.map(tool => [tool.name, tool]),
|
|
138
|
+
);
|
|
139
|
+
|
|
112
140
|
const {
|
|
113
141
|
input,
|
|
114
142
|
instructions,
|
|
@@ -116,21 +144,63 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
116
144
|
} = await convertToOpenResponsesInput({
|
|
117
145
|
prompt,
|
|
118
146
|
providerOptionsName: this.config.providerOptionsName,
|
|
147
|
+
extensionRegistry: this.extensionRegistry,
|
|
148
|
+
providerToolsByName,
|
|
119
149
|
});
|
|
120
150
|
|
|
121
151
|
warnings.push(...inputWarnings);
|
|
122
152
|
|
|
123
|
-
|
|
124
|
-
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
|
+
>();
|
|
125
165
|
|
|
126
166
|
for (const tool of tools ?? []) {
|
|
127
167
|
if (tool.type === 'provider') {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
+
}
|
|
132
202
|
} else {
|
|
133
|
-
|
|
203
|
+
convertedTools.push({
|
|
134
204
|
type: 'function',
|
|
135
205
|
name: tool.name,
|
|
136
206
|
description: tool.description,
|
|
@@ -141,12 +211,47 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
141
211
|
}
|
|
142
212
|
|
|
143
213
|
// Convert tool choice to the Open Responses format
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
+
}
|
|
150
255
|
|
|
151
256
|
const textFormat =
|
|
152
257
|
responseFormat?.type === 'json'
|
|
@@ -209,7 +314,7 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
209
314
|
}),
|
|
210
315
|
}
|
|
211
316
|
: undefined,
|
|
212
|
-
tools:
|
|
317
|
+
tools: convertedTools.length ? convertedTools : undefined,
|
|
213
318
|
tool_choice: convertedToolChoice,
|
|
214
319
|
...(textFormat != null && { text: { format: textFormat } }),
|
|
215
320
|
},
|
|
@@ -335,6 +440,25 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
335
440
|
});
|
|
336
441
|
break;
|
|
337
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
|
+
}
|
|
338
462
|
}
|
|
339
463
|
}
|
|
340
464
|
|
|
@@ -454,6 +578,8 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
454
578
|
{ toolName?: string; toolCallId?: string; arguments?: string }
|
|
455
579
|
>();
|
|
456
580
|
const providerOptionsName = this.config.providerOptionsName;
|
|
581
|
+
const extensionRegistry = this.extensionRegistry;
|
|
582
|
+
const extensionStreamState = new Map<string, unknown>();
|
|
457
583
|
|
|
458
584
|
return {
|
|
459
585
|
stream: response.pipeThrough(
|
|
@@ -465,7 +591,7 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
465
591
|
controller.enqueue({ type: 'stream-start', warnings });
|
|
466
592
|
},
|
|
467
593
|
|
|
468
|
-
transform(parseResult, controller) {
|
|
594
|
+
async transform(parseResult, controller) {
|
|
469
595
|
if (options.includeRawChunks) {
|
|
470
596
|
controller.enqueue({
|
|
471
597
|
type: 'raw',
|
|
@@ -480,6 +606,27 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
480
606
|
|
|
481
607
|
const chunk = parseResult.value;
|
|
482
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
|
+
|
|
483
630
|
// Tool call events (single-shot tool-call when complete)
|
|
484
631
|
if (
|
|
485
632
|
chunk.type === 'response.output_item.added' &&
|
|
@@ -540,6 +687,25 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
540
687
|
hasToolCalls = true;
|
|
541
688
|
|
|
542
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
|
+
}
|
|
543
709
|
}
|
|
544
710
|
|
|
545
711
|
// Reasoning events (note: response.reasoning_text.delta is an LM Studio extension, not in official spec)
|
|
@@ -553,8 +719,9 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
553
719
|
});
|
|
554
720
|
activeReasoningId = chunk.item.id;
|
|
555
721
|
} else if (
|
|
722
|
+
chunk.type === 'response.reasoning_summary_text.delta' ||
|
|
556
723
|
(chunk as { type: string }).type ===
|
|
557
|
-
|
|
724
|
+
'response.reasoning_text.delta'
|
|
558
725
|
) {
|
|
559
726
|
const reasoningChunk = chunk as {
|
|
560
727
|
item_id: string;
|
|
@@ -631,6 +798,29 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
631
798
|
raw: chunk.response.error?.code ?? chunk.response.status,
|
|
632
799
|
};
|
|
633
800
|
updateUsage(chunk.response.usage);
|
|
801
|
+
if (chunk.response.error != null) {
|
|
802
|
+
controller.enqueue({
|
|
803
|
+
type: 'error',
|
|
804
|
+
error: createOpenResponsesStreamError({
|
|
805
|
+
type: chunk.type,
|
|
806
|
+
error: chunk.response.error,
|
|
807
|
+
data: chunk,
|
|
808
|
+
}),
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
} else if (chunk.type === 'error') {
|
|
812
|
+
finishReason = {
|
|
813
|
+
unified: 'error',
|
|
814
|
+
raw: chunk.error.code,
|
|
815
|
+
};
|
|
816
|
+
controller.enqueue({
|
|
817
|
+
type: 'error',
|
|
818
|
+
error: createOpenResponsesStreamError({
|
|
819
|
+
type: chunk.type,
|
|
820
|
+
error: chunk.error,
|
|
821
|
+
data: chunk,
|
|
822
|
+
}),
|
|
823
|
+
});
|
|
634
824
|
}
|
|
635
825
|
},
|
|
636
826
|
|
|
@@ -657,6 +847,23 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
|
|
|
657
847
|
}
|
|
658
848
|
}
|
|
659
849
|
|
|
850
|
+
function createOpenResponsesStreamError({
|
|
851
|
+
type,
|
|
852
|
+
error,
|
|
853
|
+
data,
|
|
854
|
+
}: {
|
|
855
|
+
type: 'error' | 'response.failed';
|
|
856
|
+
error: { message: string; code: string };
|
|
857
|
+
data: unknown;
|
|
858
|
+
}) {
|
|
859
|
+
return createProviderStreamError({
|
|
860
|
+
message: error.message,
|
|
861
|
+
type,
|
|
862
|
+
code: error.code,
|
|
863
|
+
data,
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
|
|
660
867
|
function createReasoningProviderMetadata({
|
|
661
868
|
part,
|
|
662
869
|
providerOptionsName,
|
|
@@ -687,6 +894,95 @@ function createReasoningProviderMetadata({
|
|
|
687
894
|
};
|
|
688
895
|
}
|
|
689
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
|
+
|
|
690
986
|
function getOutputTextAnnotations(value: unknown): Annotation[] {
|
|
691
987
|
if (
|
|
692
988
|
value == null ||
|