@ai-sdk/langchain 2.0.241 → 2.0.242

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/src/utils.ts CHANGED
@@ -122,25 +122,46 @@ function getDefaultFilename(
122
122
  return `${prefix}.${ext}`;
123
123
  }
124
124
 
125
- /**
126
- * OpenAI-native content block type for images.
127
- * This format is passed through directly by ChatOpenAI to OpenAI's API.
128
- */
129
- type OpenAIImageBlock = {
130
- type: 'image_url';
131
- image_url: {
132
- url: string;
133
- detail?: 'auto' | 'low' | 'high';
134
- };
135
- };
125
+ function convertImageToContentBlock(
126
+ data: string | Uint8Array | URL | ArrayBuffer,
127
+ mediaType: string = 'image/png',
128
+ ): ContentBlock.Multimodal.Image {
129
+ if (data instanceof URL) {
130
+ if (data.protocol !== 'data:') {
131
+ return { type: 'image', url: data.toString() };
132
+ }
136
133
 
137
- /**
138
- * Content block type for HumanMessage that supports both text and OpenAI images.
139
- */
140
- type HumanMessageContentBlock =
141
- | { type: 'text'; text: string }
142
- | OpenAIImageBlock
143
- | ContentBlock;
134
+ data = data.toString();
135
+ }
136
+
137
+ if (typeof data === 'string') {
138
+ if (data.startsWith('http://') || data.startsWith('https://')) {
139
+ return { type: 'image', url: data };
140
+ }
141
+
142
+ if (data.startsWith('data:')) {
143
+ const matches = data.match(/^data:([^;]+);base64,(.+)$/);
144
+ if (matches) {
145
+ return {
146
+ type: 'image',
147
+ data: matches[2],
148
+ mimeType: matches[1],
149
+ };
150
+ }
151
+
152
+ return { type: 'image', url: data };
153
+ }
154
+
155
+ return { type: 'image', data, mimeType: mediaType };
156
+ }
157
+
158
+ const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
159
+ return {
160
+ type: 'image',
161
+ data: btoa(String.fromCharCode(...bytes)),
162
+ mimeType: mediaType,
163
+ };
164
+ }
144
165
 
145
166
  /**
146
167
  * Converts UserContent to LangChain HumanMessage
@@ -152,7 +173,7 @@ export function convertUserContent(content: UserContent): HumanMessage {
152
173
  return new HumanMessage({ content });
153
174
  }
154
175
 
155
- const contentBlocks: HumanMessageContentBlock[] = [];
176
+ const contentBlocks: ContentBlock.Standard[] = [];
156
177
 
157
178
  for (const part of content) {
158
179
  if (part.type === 'text') {
@@ -164,64 +185,15 @@ export function convertUserContent(content: UserContent): HumanMessage {
164
185
  mediaType?: string;
165
186
  };
166
187
 
167
- /**
168
- * Use OpenAI's native image_url format which is passed through directly
169
- * handle URL objects
170
- */
171
- if (imagePart.image instanceof URL) {
172
- contentBlocks.push({
173
- type: 'image_url',
174
- image_url: { url: imagePart.image.toString() },
175
- });
176
- } else if (typeof imagePart.image === 'string') {
177
- /**
178
- * Handle string (could be URL or base64)
179
- */
180
- /**
181
- * Check if it's a URL (including data: URLs)
182
- */
183
- if (
184
- imagePart.image.startsWith('http://') ||
185
- imagePart.image.startsWith('https://') ||
186
- imagePart.image.startsWith('data:')
187
- ) {
188
- /**
189
- * OpenAI accepts both http URLs and data URLs directly
190
- */
191
- contentBlocks.push({
192
- type: 'image_url',
193
- image_url: { url: imagePart.image },
194
- });
195
- } else {
196
- /**
197
- * Assume base64 encoded data - wrap in data URL
198
- */
199
- const mimeType = imagePart.mediaType || 'image/png';
200
- contentBlocks.push({
201
- type: 'image_url',
202
- image_url: { url: `data:${mimeType};base64,${imagePart.image}` },
203
- });
204
- }
205
- } else if (
206
- /**
207
- * Handle Uint8Array or ArrayBuffer (binary data)
208
- */
188
+ if (
189
+ imagePart.image instanceof URL ||
190
+ typeof imagePart.image === 'string' ||
209
191
  imagePart.image instanceof Uint8Array ||
210
192
  imagePart.image instanceof ArrayBuffer
211
193
  ) {
212
- const bytes =
213
- imagePart.image instanceof ArrayBuffer
214
- ? new Uint8Array(imagePart.image)
215
- : imagePart.image;
216
- /**
217
- * Convert to base64 data URL
218
- */
219
- const base64 = btoa(String.fromCharCode(...bytes));
220
- const mimeType = imagePart.mediaType || 'image/png';
221
- contentBlocks.push({
222
- type: 'image_url',
223
- image_url: { url: `data:${mimeType};base64,${base64}` },
224
- });
194
+ contentBlocks.push(
195
+ convertImageToContentBlock(imagePart.image, imagePart.mediaType),
196
+ );
225
197
  }
226
198
  } else if (part.type === 'file') {
227
199
  const filePart = part as {
@@ -231,58 +203,12 @@ export function convertUserContent(content: UserContent): HumanMessage {
231
203
  filename?: string;
232
204
  };
233
205
 
234
- /**
235
- * Check if this is an image file - if so, use OpenAI's image_url format
236
- */
237
206
  const isImage = filePart.mediaType?.startsWith('image/');
238
207
 
239
208
  if (isImage) {
240
- /**
241
- * Handle image files using OpenAI's native image_url format
242
- */
243
- if (filePart.data instanceof URL) {
244
- contentBlocks.push({
245
- type: 'image_url',
246
- image_url: { url: filePart.data.toString() },
247
- });
248
- } else if (typeof filePart.data === 'string') {
249
- /**
250
- * URLs (including data URLs) can be passed directly
251
- */
252
- if (
253
- filePart.data.startsWith('http://') ||
254
- filePart.data.startsWith('https://') ||
255
- filePart.data.startsWith('data:')
256
- ) {
257
- contentBlocks.push({
258
- type: 'image_url',
259
- image_url: { url: filePart.data },
260
- });
261
- } else {
262
- /**
263
- * Assume base64 - wrap in data URL
264
- */
265
- contentBlocks.push({
266
- type: 'image_url',
267
- image_url: {
268
- url: `data:${filePart.mediaType};base64,${filePart.data}`,
269
- },
270
- });
271
- }
272
- } else if (
273
- filePart.data instanceof Uint8Array ||
274
- filePart.data instanceof ArrayBuffer
275
- ) {
276
- const bytes =
277
- filePart.data instanceof ArrayBuffer
278
- ? new Uint8Array(filePart.data)
279
- : filePart.data;
280
- const base64 = btoa(String.fromCharCode(...bytes));
281
- contentBlocks.push({
282
- type: 'image_url',
283
- image_url: { url: `data:${filePart.mediaType};base64,${base64}` },
284
- });
285
- }
209
+ contentBlocks.push(
210
+ convertImageToContentBlock(filePart.data, filePart.mediaType),
211
+ );
286
212
  } else {
287
213
  // Handle non-image files using LangChain's ContentBlock format
288
214
  const filename =
@@ -362,7 +288,7 @@ export function convertUserContent(content: UserContent): HumanMessage {
362
288
  });
363
289
  }
364
290
 
365
- return new HumanMessage({ content: contentBlocks });
291
+ return new HumanMessage({ contentBlocks });
366
292
  }
367
293
 
368
294
  /**