ai
Version:
AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.
146 lines (128 loc) • 4.12 kB
text/typescript
import { convertDataContentToUint8Array } from './data-content';
import type { LanguageModelV4FilePart } from '@ai-sdk/provider';
import {
isUint8Array,
isArrayBuffer,
isBuffer,
isProviderReference,
type DataContent,
type FilePart,
type ProviderReference,
} from '@ai-sdk/provider-utils';
import { InvalidDataContentError } from './invalid-data-content-error';
import { splitDataUrl } from './split-data-url';
type TaggedFileData = Extract<FilePart['data'], { type: string }>;
function isTaggedFileData(value: unknown): value is TaggedFileData {
if (typeof value !== 'object' || value === null) return false;
const type = (value as { type?: unknown }).type;
return (
type === 'data' || type === 'url' || type === 'reference' || type === 'text'
);
}
type ConvertResult = {
data: LanguageModelV4FilePart['data'];
mediaType: string | undefined;
};
function convertUrlToFilePartData(
url: URL,
originalUrl?: string,
): ConvertResult {
if (url.protocol === 'data:') {
const { mediaType, base64Content } = splitDataUrl(url.toString());
if (mediaType == null || base64Content == null) {
throw new InvalidDataContentError({
content: url,
message: `Invalid data URL format in content ${url.toString()}`,
});
}
return { data: { type: 'data', data: base64Content }, mediaType };
}
return {
data: { type: 'url', url, ...(originalUrl != null ? { originalUrl } : {}) },
mediaType: undefined,
};
}
function convertUrlStringToFilePartData(content: string): ConvertResult {
const result = convertUrlToFilePartData(new URL(content));
if (result.data.type === 'url' && result.data.url.toString() !== content) {
result.data.originalUrl = content;
}
return result;
}
function convertInlineDataToFilePartData(content: DataContent): ConvertResult {
if (isUint8Array(content) || isArrayBuffer(content)) {
return {
data: { type: 'data', data: convertDataContentToUint8Array(content) },
mediaType: undefined,
};
}
if (isBuffer(content)) {
return {
data: { type: 'data', data: new Uint8Array(content) },
mediaType: undefined,
};
}
return {
data: { type: 'data', data: content as string },
mediaType: undefined,
};
}
/**
* Converts any legacy-or-tagged top-level `FilePart.data` /
* `ReasoningFilePart.data` value into the tagged v4 provider prompt shape.
*
* Returns the tagged `data` together with the resolved mediaType (extracted
* from a `data:` URL when applicable).
*/
export function convertToLanguageModelV4FilePart(
content: FilePart['data'],
): ConvertResult {
if (isTaggedFileData(content)) {
switch (content.type) {
case 'data':
if (
typeof content.data === 'string' &&
content.data.startsWith('data:')
) {
throw new InvalidDataContentError({
content: content.data,
message:
'Data URLs are not valid inline data. Pass them as { type: "url", url } instead.',
});
}
return convertInlineDataToFilePartData(content.data);
case 'url':
return convertUrlToFilePartData(content.url, content.originalUrl);
case 'reference':
return {
data: { type: 'reference', reference: content.reference },
mediaType: undefined,
};
case 'text':
return {
data: { type: 'text', text: content.text },
mediaType: undefined,
};
}
}
if (content instanceof URL) {
return convertUrlToFilePartData(content);
}
if (typeof content === 'string') {
try {
return convertUrlStringToFilePartData(content);
} catch {
return convertInlineDataToFilePartData(content);
}
}
if (isUint8Array(content) || isArrayBuffer(content)) {
return convertInlineDataToFilePartData(content);
}
if (isProviderReference(content)) {
return {
data: { type: 'reference', reference: content as ProviderReference },
mediaType: undefined,
};
}
return convertInlineDataToFilePartData(content as DataContent);
}