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.
126 lines (112 loc) • 3.6 kB
text/typescript
import type { LanguageModelV4FilePart } from '@ai-sdk/provider';
import {
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): 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 }, mediaType: undefined };
}
function convertInlineDataToFilePartData(content: DataContent): ConvertResult {
if (content instanceof Uint8Array) {
return { data: { type: 'data', data: content }, mediaType: undefined };
}
if (content instanceof ArrayBuffer) {
return {
data: { type: 'data', data: new Uint8Array(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);
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 convertUrlToFilePartData(new URL(content));
} catch {
return convertInlineDataToFilePartData(content);
}
}
if (isProviderReference(content)) {
return {
data: { type: 'reference', reference: content as ProviderReference },
mediaType: undefined,
};
}
return convertInlineDataToFilePartData(content as DataContent);
}