From 4487fbf52fad433276b67379f0e9ce9f5c81b9be Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Wed, 20 May 2026 20:46:31 -0500 Subject: [PATCH] fix(provider): support PDF attachments for xAI/Grok (#28561) --- package.json | 3 +- packages/opencode/src/session/message-v2.ts | 1 + patches/@ai-sdk%2Fxai@3.0.82.patch | 99 +++++++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 patches/@ai-sdk%2Fxai@3.0.82.patch diff --git a/package.json b/package.json index b48dedad89..47a35a1556 100644 --- a/package.json +++ b/package.json @@ -139,6 +139,7 @@ "@npmcli/agent@4.0.0": "patches/@npmcli%2Fagent@4.0.0.patch", "@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch", "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", - "solid-js@1.9.10": "patches/solid-js@1.9.10.patch" + "solid-js@1.9.10": "patches/solid-js@1.9.10.patch", + "@ai-sdk/xai@3.0.82": "patches/@ai-sdk%2Fxai@3.0.82.patch" } } diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index d3d6a1dfcc..2745ff4f45 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -647,6 +647,7 @@ export const toModelMessagesEffect = Effect.fnUntraced(function* ( if (model.api.npm === "@ai-sdk/anthropic") return true if (model.api.npm === "@ai-sdk/openai") return true if (model.api.npm === "@ai-sdk/amazon-bedrock") return attachment.mime.startsWith("image/") + if (model.api.npm === "@ai-sdk/xai") return attachment.mime.startsWith("image/") if (model.api.npm === "@ai-sdk/google-vertex/anthropic") return true if (model.api.npm === "@ai-sdk/google") { const id = model.api.id.toLowerCase() diff --git a/patches/@ai-sdk%2Fxai@3.0.82.patch b/patches/@ai-sdk%2Fxai@3.0.82.patch new file mode 100644 index 0000000000..dbe1207bcd --- /dev/null +++ b/patches/@ai-sdk%2Fxai@3.0.82.patch @@ -0,0 +1,99 @@ +diff --git a/dist/index.js b/dist/index.js +index 135b95946139bbd1fc4b62239032931586189da0..7913520ad2f1c26b0f9621e7654f5fb570cba926 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -1077,6 +1077,20 @@ async function convertToXaiResponsesInput({ + const mediaType = block.mediaType === "image/*" ? "image/jpeg" : block.mediaType; + const imageUrl = block.data instanceof URL ? block.data.toString() : `data:${mediaType};base64,${(0, import_provider_utils5.convertToBase64)(block.data)}`; + contentParts.push({ type: "input_image", image_url: imageUrl }); ++ } else if (block.mediaType === "application/pdf") { ++ if (block.data instanceof URL) { ++ contentParts.push({ type: "input_file", file_url: block.data.toString() }); ++ } else { ++ contentParts.push({ ++ type: "input_file", ++ ...(typeof block.data === "string" && block.data.startsWith("file-") ++ ? { file_id: block.data } ++ : { ++ filename: block.filename ?? "file", ++ file_data: `data:application/pdf;base64,${(0, import_provider_utils5.convertToBase64)(block.data)}`, ++ }), ++ }); ++ } + } else { + throw new import_provider4.UnsupportedFunctionalityError({ + functionality: `file part media type ${block.mediaType}` +diff --git a/dist/index.mjs b/dist/index.mjs +index 61be60d452682b94dcdda39ffc47cb994eb8bfb1..d928f7f46e91057cd97fade9cc238db611345bcf 100644 +--- a/dist/index.mjs ++++ b/dist/index.mjs +@@ -1080,6 +1080,20 @@ async function convertToXaiResponsesInput({ + const mediaType = block.mediaType === "image/*" ? "image/jpeg" : block.mediaType; + const imageUrl = block.data instanceof URL ? block.data.toString() : `data:${mediaType};base64,${convertToBase642(block.data)}`; + contentParts.push({ type: "input_image", image_url: imageUrl }); ++ } else if (block.mediaType === "application/pdf") { ++ if (block.data instanceof URL) { ++ contentParts.push({ type: "input_file", file_url: block.data.toString() }); ++ } else { ++ contentParts.push({ ++ type: "input_file", ++ ...(typeof block.data === "string" && block.data.startsWith("file-") ++ ? { file_id: block.data } ++ : { ++ filename: block.filename ?? "file", ++ file_data: `data:application/pdf;base64,${convertToBase642(block.data)}`, ++ }), ++ }); ++ } + } else { + throw new UnsupportedFunctionalityError3({ + functionality: `file part media type ${block.mediaType}` +diff --git a/src/responses/convert-to-xai-responses-input.ts b/src/responses/convert-to-xai-responses-input.ts +index 19958d9fd90f7ce61bca70ad2d5f7b89a59fa63b..9329a1d56210af8aa33e5dbcb2916e24847070c1 100644 +--- a/src/responses/convert-to-xai-responses-input.ts ++++ b/src/responses/convert-to-xai-responses-input.ts +@@ -54,6 +54,24 @@ export async function convertToXaiResponsesInput({ + : `data:${mediaType};base64,${convertToBase64(block.data)}`; + + contentParts.push({ type: 'input_image', image_url: imageUrl }); ++ } else if (block.mediaType === 'application/pdf') { ++ if (block.data instanceof URL) { ++ contentParts.push({ ++ type: 'input_file', ++ file_url: block.data.toString(), ++ }); ++ } else { ++ contentParts.push({ ++ type: 'input_file', ++ ...(typeof block.data === 'string' && ++ block.data.startsWith('file-') ++ ? { file_id: block.data } ++ : { ++ filename: block.filename ?? 'file', ++ file_data: `data:application/pdf;base64,${convertToBase64(block.data)}`, ++ }), ++ }); ++ } + } else { + throw new UnsupportedFunctionalityError({ + functionality: `file part media type ${block.mediaType}`, +diff --git a/src/responses/xai-responses-api.ts b/src/responses/xai-responses-api.ts +index df24c42d29fe7fc1dd7649cc2c4712a68c3a536b..00195468a83d43c1bfb50854ea040b55ea352433 100644 +--- a/src/responses/xai-responses-api.ts ++++ b/src/responses/xai-responses-api.ts +@@ -26,7 +26,14 @@ export type XaiResponsesSystemMessage = { + + export type XaiResponsesUserMessageContentPart = + | { type: 'input_text'; text: string } +- | { type: 'input_image'; image_url: string }; ++ | { type: 'input_image'; image_url: string } ++ | { ++ type: 'input_file'; ++ file_url?: string; ++ file_id?: string; ++ file_data?: string; ++ filename?: string; ++ }; + + export type XaiResponsesUserMessage = { + role: 'user';