@ai-sdk/openai-compatible 2.0.84 → 2.0.85

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.
@@ -1,17 +1,21 @@
1
- import type {
2
- ImageModelV3,
3
- ImageModelV3File,
4
- SharedV3ProviderOptions,
5
- SharedV3Warning,
1
+ import {
2
+ InvalidResponseDataError,
3
+ type ImageModelV3,
4
+ type ImageModelV3File,
5
+ type SharedV3ProviderOptions,
6
+ type SharedV3Warning,
6
7
  } from '@ai-sdk/provider';
7
8
  import { toCamelCase } from '../utils/to-camel-case';
8
9
  import {
9
10
  combineHeaders,
10
11
  convertBase64ToUint8Array,
11
12
  convertToFormData,
13
+ createBinaryResponseHandler,
12
14
  createJsonErrorResponseHandler,
13
15
  createJsonResponseHandler,
16
+ createStatusCodeErrorResponseHandler,
14
17
  downloadBlob,
18
+ getFromApi,
15
19
  postFormDataToApi,
16
20
  postJsonToApi,
17
21
  type FetchFunction,
@@ -96,15 +100,18 @@ export class OpenAICompatibleImageModel implements ImageModelV3 {
96
100
  const currentDate = this.config._internal?.currentDate?.() ?? new Date();
97
101
 
98
102
  const args = this.getArgs(providerOptions);
103
+ const isEditing = files != null && files.length > 0;
104
+ const url = this.config.url({
105
+ path: isEditing ? '/images/edits' : '/images/generations',
106
+ modelId: this.modelId,
107
+ });
108
+ const requestHeaders = combineHeaders(this.config.headers(), headers);
99
109
 
100
110
  // Image editing mode - use form data and /images/edits endpoint
101
- if (files != null && files.length > 0) {
111
+ if (isEditing) {
102
112
  const { value: response, responseHeaders } = await postFormDataToApi({
103
- url: this.config.url({
104
- path: '/images/edits',
105
- modelId: this.modelId,
106
- }),
107
- headers: combineHeaders(this.config.headers(), headers),
113
+ url,
114
+ headers: requestHeaders,
108
115
  formData: convertToFormData<OpenAICompatibleFormDataInput>({
109
116
  model: this.modelId,
110
117
  prompt,
@@ -125,7 +132,11 @@ export class OpenAICompatibleImageModel implements ImageModelV3 {
125
132
  });
126
133
 
127
134
  return {
128
- images: response.data.map(item => item.b64_json),
135
+ images: await this.getImages(response.data, {
136
+ url,
137
+ headers: requestHeaders,
138
+ abortSignal,
139
+ }),
129
140
  warnings,
130
141
  response: {
131
142
  timestamp: currentDate,
@@ -137,11 +148,8 @@ export class OpenAICompatibleImageModel implements ImageModelV3 {
137
148
 
138
149
  // Standard image generation mode - use JSON and /images/generations endpoint
139
150
  const { value: response, responseHeaders } = await postJsonToApi({
140
- url: this.config.url({
141
- path: '/images/generations',
142
- modelId: this.modelId,
143
- }),
144
- headers: combineHeaders(this.config.headers(), headers),
151
+ url,
152
+ headers: requestHeaders,
145
153
  body: {
146
154
  model: this.modelId,
147
155
  prompt,
@@ -161,7 +169,11 @@ export class OpenAICompatibleImageModel implements ImageModelV3 {
161
169
  });
162
170
 
163
171
  return {
164
- images: response.data.map(item => item.b64_json),
172
+ images: await this.getImages(response.data, {
173
+ url,
174
+ headers: requestHeaders,
175
+ abortSignal,
176
+ }),
165
177
  warnings,
166
178
  response: {
167
179
  timestamp: currentDate,
@@ -170,12 +182,63 @@ export class OpenAICompatibleImageModel implements ImageModelV3 {
170
182
  },
171
183
  };
172
184
  }
185
+
186
+ private async getImages(
187
+ data: z.infer<typeof openaiCompatibleImageResponseSchema>['data'],
188
+ options: {
189
+ url: string;
190
+ headers: Record<string, string | undefined>;
191
+ abortSignal: AbortSignal | undefined;
192
+ },
193
+ ): Promise<string[] | Uint8Array[]> {
194
+ // Preserve base64 responses without conversion
195
+ if (data.every(image => image.b64_json != null)) {
196
+ return data.map(image => image.b64_json!);
197
+ }
198
+
199
+ return Promise.all(
200
+ data.map(async image => {
201
+ if (image.b64_json != null) {
202
+ return convertBase64ToUint8Array(image.b64_json);
203
+ }
204
+
205
+ if (image.url == null) {
206
+ throw new InvalidResponseDataError({
207
+ data: image,
208
+ message: 'Expected url in the image response',
209
+ });
210
+ }
211
+
212
+ const { value } = await getFromApi({
213
+ url: image.url,
214
+ validateUrl: true,
215
+ trustedOrigin: options.url,
216
+ credentialedOrigin: options.url,
217
+ headers: options.headers,
218
+ abortSignal: options.abortSignal,
219
+ fetch: this.config.fetch,
220
+ successfulResponseHandler: createBinaryResponseHandler(),
221
+ failedResponseHandler: createStatusCodeErrorResponseHandler(),
222
+ });
223
+ return value;
224
+ }),
225
+ );
226
+ }
173
227
  }
174
228
 
175
229
  // minimal version of the schema, focussed on what is needed for the implementation
176
230
  // this approach limits breakages when the API changes and increases efficiency
177
231
  const openaiCompatibleImageResponseSchema = z.object({
178
- data: z.array(z.object({ b64_json: z.string() })),
232
+ data: z.array(
233
+ z
234
+ .object({
235
+ b64_json: z.string().nullish(),
236
+ url: z.string().nullish(),
237
+ })
238
+ .refine(image => image.b64_json != null || image.url != null, {
239
+ message: 'Expected b64_json or url in the image response',
240
+ }),
241
+ ),
179
242
  });
180
243
 
181
244
  type OpenAICompatibleFormDataInput = {