@ai-sdk/open-responses 1.0.39 → 1.0.41

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.
@@ -13,8 +13,12 @@ import type {
13
13
 
14
14
  export async function convertToOpenResponsesInput({
15
15
  prompt,
16
+ providerOptionsName = 'open-responses',
17
+ strictResponseInput = false,
16
18
  }: {
17
19
  prompt: LanguageModelV3Prompt;
20
+ providerOptionsName?: string;
21
+ strictResponseInput?: boolean;
18
22
  }): Promise<{
19
23
  input: OpenResponsesRequestBody['input'];
20
24
  instructions: string | undefined;
@@ -54,6 +58,7 @@ export async function convertToOpenResponsesInput({
54
58
  : {
55
59
  image_url: `data:${mediaType};base64,${convertToBase64(part.data)}`,
56
60
  }),
61
+ detail: getImageDetail(part, providerOptionsName),
57
62
  });
58
63
  } else if (part.data instanceof URL) {
59
64
  userContent.push({
@@ -77,14 +82,72 @@ export async function convertToOpenResponsesInput({
77
82
  }
78
83
 
79
84
  case 'assistant': {
80
- const assistantContent: Array<
85
+ let assistantContent: Array<
81
86
  OutputTextContentParam | RefusalContentParam
82
87
  > = [];
83
88
  const toolCalls: Array<FunctionCallItemParam> = [];
89
+ let assistantMessageId: string | undefined;
90
+
91
+ const flushAssistantContent = () => {
92
+ if (assistantContent.length === 0) {
93
+ return;
94
+ }
95
+
96
+ if (strictResponseInput && assistantMessageId == null) {
97
+ input.push({
98
+ type: 'message',
99
+ role: 'assistant',
100
+ content: assistantContent
101
+ .map(part =>
102
+ part.type === 'output_text' ? part.text : part.refusal,
103
+ )
104
+ .join(''),
105
+ });
106
+ } else if (strictResponseInput) {
107
+ input.push({
108
+ id: assistantMessageId,
109
+ type: 'message',
110
+ status: 'completed',
111
+ role: 'assistant',
112
+ content: assistantContent.map(part =>
113
+ part.type === 'output_text'
114
+ ? {
115
+ ...part,
116
+ annotations: part.annotations ?? [],
117
+ logprobs: part.logprobs ?? [],
118
+ }
119
+ : part,
120
+ ),
121
+ });
122
+ } else {
123
+ input.push({
124
+ type: 'message',
125
+ role: 'assistant',
126
+ content: assistantContent,
127
+ ...(assistantMessageId != null && { id: assistantMessageId }),
128
+ });
129
+ }
130
+ assistantContent = [];
131
+ assistantMessageId = undefined;
132
+ };
84
133
 
85
134
  for (const part of content) {
86
135
  switch (part.type) {
87
136
  case 'text': {
137
+ const providerData = part.providerOptions?.[providerOptionsName];
138
+ const itemId =
139
+ typeof providerData?.itemId === 'string'
140
+ ? providerData.itemId
141
+ : undefined;
142
+
143
+ if (
144
+ assistantContent.length > 0 &&
145
+ assistantMessageId !== itemId
146
+ ) {
147
+ flushAssistantContent();
148
+ }
149
+
150
+ assistantMessageId = itemId;
88
151
  assistantContent.push({ type: 'output_text', text: part.text });
89
152
  break;
90
153
  }
@@ -104,14 +167,7 @@ export async function convertToOpenResponsesInput({
104
167
  }
105
168
  }
106
169
 
107
- // Push assistant message with text content if any
108
- if (assistantContent.length > 0) {
109
- input.push({
110
- type: 'message',
111
- role: 'assistant',
112
- content: assistantContent,
113
- });
114
- }
170
+ flushAssistantContent();
115
171
 
116
172
  // Push function calls as separate items
117
173
  for (const toolCall of toolCalls) {
@@ -158,6 +214,7 @@ export async function convertToOpenResponsesInput({
158
214
  contentParts.push({
159
215
  type: 'input_image',
160
216
  image_url: `data:${item.mediaType};base64,${item.data}`,
217
+ detail: getImageDetail(item, providerOptionsName),
161
218
  });
162
219
  break;
163
220
  }
@@ -165,6 +222,7 @@ export async function convertToOpenResponsesInput({
165
222
  contentParts.push({
166
223
  type: 'input_image',
167
224
  image_url: item.url,
225
+ detail: getImageDetail(item, providerOptionsName),
168
226
  });
169
227
  break;
170
228
  }
@@ -209,3 +267,18 @@ export async function convertToOpenResponsesInput({
209
267
  warnings,
210
268
  };
211
269
  }
270
+
271
+ function getImageDetail(
272
+ part: {
273
+ providerOptions?: Record<string, Record<string, unknown>>;
274
+ },
275
+ providerOptionsName: string,
276
+ ): InputImageContentParam['detail'] {
277
+ const imageDetail = part.providerOptions?.[providerOptionsName]?.imageDetail;
278
+
279
+ return imageDetail === 'low' ||
280
+ imageDetail === 'high' ||
281
+ imageDetail === 'auto'
282
+ ? imageDetail
283
+ : 'auto';
284
+ }
@@ -95,6 +95,7 @@ export type OutputTextContentParam = {
95
95
  type: 'output_text';
96
96
  text: string;
97
97
  annotations?: UrlCitationParam[];
98
+ logprobs?: LogProb[];
98
99
  };
99
100
 
100
101
  /**
@@ -7,4 +7,5 @@ export type OpenResponsesConfig = {
7
7
  headers: () => Record<string, string | undefined>;
8
8
  fetch?: FetchFunction;
9
9
  generateId: () => string;
10
+ strictResponseInput?: boolean;
10
11
  };
@@ -91,6 +91,8 @@ export class OpenResponsesLanguageModel implements LanguageModelV3 {
91
91
  warnings: inputWarnings,
92
92
  } = await convertToOpenResponsesInput({
93
93
  prompt,
94
+ providerOptionsName: this.config.providerOptionsName,
95
+ strictResponseInput: this.config.strictResponseInput,
94
96
  });
95
97
 
96
98
  warnings.push(...inputWarnings);