import { type LanguageModelV2Prompt, type SharedV2ProviderMetadata, UnsupportedFunctionalityError, } from "@ai-sdk/provider" import type { OpenAICompatibleChatPrompt } from "./openai-compatible-api-types" import { convertToBase64 } from "@ai-sdk/provider-utils" function getOpenAIMetadata(message: { providerOptions?: SharedV2ProviderMetadata }) { return message?.providerOptions?.copilot ?? {} } export function convertToOpenAICompatibleChatMessages(prompt: LanguageModelV2Prompt): OpenAICompatibleChatPrompt { const messages: OpenAICompatibleChatPrompt = [] for (const { role, content, ...message } of prompt) { const metadata = getOpenAIMetadata({ ...message }) switch (role) { case "system": { messages.push({ role: "system", content: [ { type: "text", text: content, }, ], ...metadata, }) break } case "user": { if (content.length === 1 && content[0].type === "text") { messages.push({ role: "user", content: content[0].text, ...getOpenAIMetadata(content[0]), }) break } messages.push({ role: "user", content: content.map((part) => { const partMetadata = getOpenAIMetadata(part) switch (part.type) { case "text": { return { type: "text", text: part.text, ...partMetadata } } case "file": { if (part.mediaType.startsWith("image/")) { const mediaType = part.mediaType === "image/*" ? "image/jpeg" : part.mediaType return { type: "image_url", image_url: { url: part.data instanceof URL ? part.data.toString() : `data:${mediaType};base64,${convertToBase64(part.data)}`, }, ...partMetadata, } } else { throw new UnsupportedFunctionalityError({ functionality: `file part media type ${part.mediaType}`, }) } } } }), ...metadata, }) break } case "assistant": { let text = "" let reasoningText: string | undefined let reasoningOpaque: string | undefined const toolCalls: Array<{ id: string type: "function" function: { name: string; arguments: string } }> = [] for (const part of content) { const partMetadata = getOpenAIMetadata(part) // Check for reasoningOpaque on any part (may be attached to text/tool-call) const partOpaque = (part.providerOptions as { copilot?: { reasoningOpaque?: string } })?.copilot ?.reasoningOpaque if (partOpaque && !reasoningOpaque) { reasoningOpaque = partOpaque } switch (part.type) { case "text": { text += part.text break } case "reasoning": { reasoningText = part.text break } case "tool-call": { toolCalls.push({ id: part.toolCallId, type: "function", function: { name: part.toolName, arguments: JSON.stringify(part.input), }, ...partMetadata, }) break } } } messages.push({ role: "assistant", content: text || null, tool_calls: toolCalls.length > 0 ? toolCalls : undefined, reasoning_text: reasoningText, reasoning_opaque: reasoningOpaque, ...metadata, }) break } case "tool": { for (const toolResponse of content) { const output = toolResponse.output let contentValue: string switch (output.type) { case "text": case "error-text": contentValue = output.value break case "content": case "json": case "error-json": contentValue = JSON.stringify(output.value) break } const toolResponseMetadata = getOpenAIMetadata(toolResponse) messages.push({ role: "tool", tool_call_id: toolResponse.toolCallId, content: contentValue, ...toolResponseMetadata, }) } break } default: { const _exhaustiveCheck: never = role throw new Error(`Unsupported role: ${_exhaustiveCheck}`) } } } return messages }