From 67223eaa53f46718f3415d07b7034e90b425480a Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Mon, 1 Jun 2026 01:55:48 -0500 Subject: [PATCH] fix(opencode): update xai provider --- bun.lock | 18 ++-- package.json | 2 +- packages/core/package.json | 2 +- packages/opencode/package.json | 2 +- packages/opencode/test/provider/xai.test.ts | 53 +++++++++++ patches/@ai-sdk%2Fxai@3.0.82.patch | 99 --------------------- patches/@ai-sdk%2Fxai@3.0.92.patch | 75 ++++++++++++++++ 7 files changed, 144 insertions(+), 107 deletions(-) create mode 100644 packages/opencode/test/provider/xai.test.ts delete mode 100644 patches/@ai-sdk%2Fxai@3.0.82.patch create mode 100644 patches/@ai-sdk%2Fxai@3.0.92.patch diff --git a/bun.lock b/bun.lock index 74b6df0fc8b..d5f9d8015f3 100644 --- a/bun.lock +++ b/bun.lock @@ -255,7 +255,7 @@ "@ai-sdk/provider-utils": "4.0.23", "@ai-sdk/togetherai": "2.0.41", "@ai-sdk/vercel": "2.0.39", - "@ai-sdk/xai": "3.0.82", + "@ai-sdk/xai": "3.0.92", "@aws-sdk/credential-providers": "3.993.0", "@effect/opentelemetry": "catalog:", "@effect/platform-node": "catalog:", @@ -482,7 +482,7 @@ "@ai-sdk/provider": "3.0.8", "@ai-sdk/togetherai": "2.0.41", "@ai-sdk/vercel": "2.0.39", - "@ai-sdk/xai": "3.0.82", + "@ai-sdk/xai": "3.0.92", "@aws-sdk/credential-providers": "3.993.0", "@clack/prompts": "1.0.0-alpha.1", "@effect/opentelemetry": "catalog:", @@ -837,9 +837,9 @@ ], "patchedDependencies": { "solid-js@1.9.10": "patches/solid-js@1.9.10.patch", + "@ai-sdk/xai@3.0.92": "patches/@ai-sdk%2Fxai@3.0.92.patch", "virtua@0.49.1": "patches/virtua@0.49.1.patch", "gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch", - "@ai-sdk/xai@3.0.82": "patches/@ai-sdk%2Fxai@3.0.82.patch", "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", "@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", @@ -976,7 +976,7 @@ "@ai-sdk/vercel": ["@ai-sdk/vercel@2.0.39", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8eu3ljJpkCTP4ppcyYB+NcBrkcBoSOFthCSgk5VnjaxnDaOJFaxnPwfddM7wx3RwMk2CiK1O61Px/LlqNc7QkQ=="], - "@ai-sdk/xai": ["@ai-sdk/xai@3.0.82", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-A0VFMufnVf4wODcT3SPQUUzvYXiIO1VhFuXj9r6z/vP4rlo+QRDPw3WSTchcz93ROQWSfBE3I6Szqz342OHi5w=="], + "@ai-sdk/xai": ["@ai-sdk/xai@3.0.92", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.48", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yqRMdEVfSkZCXs5mqeKfYOuQElo93igwz/4zWbYkhZWAW7tDh+5uqRef6MtQVYF/bTC1EsdbeO9DtsOtAZMOLA=="], "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], @@ -5300,7 +5300,11 @@ "@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], - "@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], + "@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.48", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z9MC6M4Oh/yUY/F/eszOtO8wc2nMz99XmZQKd2gWTtyIfe716xTfrKe3aYZKg20NZDtyjqPPKPSR+wqz7q1T7Q=="], + + "@ai-sdk/xai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="], + + "@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="], "@astrojs/check/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], @@ -6246,6 +6250,10 @@ "@ai-sdk/vercel/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@ai-sdk/xai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/xai/@ai-sdk/provider-utils/eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="], + "@astrojs/check/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], "@astrojs/check/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], diff --git a/package.json b/package.json index 0b5119d94c4..7287bd77fa6 100644 --- a/package.json +++ b/package.json @@ -144,7 +144,7 @@ "@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", "virtua@0.49.1": "patches/virtua@0.49.1.patch", - "@ai-sdk/xai@3.0.82": "patches/@ai-sdk%2Fxai@3.0.82.patch", + "@ai-sdk/xai@3.0.92": "patches/@ai-sdk%2Fxai@3.0.92.patch", "gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch" } } diff --git a/packages/core/package.json b/packages/core/package.json index 1c3f2a3f002..018ea6b84e6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -54,7 +54,7 @@ "@ai-sdk/provider-utils": "4.0.23", "@ai-sdk/togetherai": "2.0.41", "@ai-sdk/vercel": "2.0.39", - "@ai-sdk/xai": "3.0.82", + "@ai-sdk/xai": "3.0.92", "@aws-sdk/credential-providers": "3.993.0", "@effect/opentelemetry": "catalog:", "@effect/platform-node": "catalog:", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index b56bdf187a1..fc39b63921b 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -89,7 +89,7 @@ "@ai-sdk/provider": "3.0.8", "@ai-sdk/togetherai": "2.0.41", "@ai-sdk/vercel": "2.0.39", - "@ai-sdk/xai": "3.0.82", + "@ai-sdk/xai": "3.0.92", "@aws-sdk/credential-providers": "3.993.0", "@clack/prompts": "1.0.0-alpha.1", "@effect/opentelemetry": "catalog:", diff --git a/packages/opencode/test/provider/xai.test.ts b/packages/opencode/test/provider/xai.test.ts new file mode 100644 index 00000000000..79a098ce143 --- /dev/null +++ b/packages/opencode/test/provider/xai.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, test } from "bun:test" +import { createXai } from "@ai-sdk/xai" + +describe("@ai-sdk/xai", () => { + test("sends inline PDF attachments through the Responses API", async () => { + let input: unknown + const handle = async (_url: Parameters[0], options?: Parameters[1]) => { + input = JSON.parse(String(options?.body)).input + return new Response( + JSON.stringify({ + object: "response", + output: [], + status: "completed", + usage: { input_tokens: 0, output_tokens: 0 }, + }), + { status: 200, headers: { "content-type": "application/json" } }, + ) + } + const xai = createXai({ + apiKey: "test", + fetch: Object.assign(handle, { preconnect: fetch.preconnect.bind(fetch) }), + }) + + await xai.responses("grok-4").doGenerate({ + prompt: [ + { + role: "user", + content: [ + { + type: "file", + mediaType: "application/pdf", + filename: "sample.pdf", + data: new Uint8Array([0x25, 0x50, 0x44, 0x46]), + }, + ], + }, + ], + }) + + expect(input).toEqual([ + { + role: "user", + content: [ + { + type: "input_file", + filename: "sample.pdf", + file_data: "data:application/pdf;base64,JVBERg==", + }, + ], + }, + ]) + }) +}) diff --git a/patches/@ai-sdk%2Fxai@3.0.82.patch b/patches/@ai-sdk%2Fxai@3.0.82.patch deleted file mode 100644 index dbe1207bcd1..00000000000 --- a/patches/@ai-sdk%2Fxai@3.0.82.patch +++ /dev/null @@ -1,99 +0,0 @@ -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'; diff --git a/patches/@ai-sdk%2Fxai@3.0.92.patch b/patches/@ai-sdk%2Fxai@3.0.92.patch new file mode 100644 index 00000000000..f3afbd06907 --- /dev/null +++ b/patches/@ai-sdk%2Fxai@3.0.92.patch @@ -0,0 +1,75 @@ +diff --git a/dist/index.js b/dist/index.js +--- a/dist/index.js ++++ b/dist/index.js +@@ -1110,6 +1110,14 @@ async function convertToXaiResponsesInput({ + type: "input_file", + file_url: block.data.toString() + }); ++ } else if (block.mediaType === "application/pdf") { ++ 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} as inline data (xAI Responses requires a URL or a Files API reference for non-image files)` +diff --git a/dist/index.mjs b/dist/index.mjs +--- a/dist/index.mjs ++++ b/dist/index.mjs +@@ -1113,6 +1113,14 @@ async function convertToXaiResponsesInput({ + type: "input_file", + file_url: block.data.toString() + }); ++ } else if (block.mediaType === "application/pdf") { ++ 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} as inline data (xAI Responses requires a URL or a Files API reference for non-image files)` +diff --git a/src/responses/convert-to-xai-responses-input.ts b/src/responses/convert-to-xai-responses-input.ts +--- a/src/responses/convert-to-xai-responses-input.ts ++++ b/src/responses/convert-to-xai-responses-input.ts +@@ -64,6 +64,17 @@ export async function convertToXaiResponsesInput({ + type: 'input_file', + file_url: block.data.toString(), + }); ++ } else if (block.mediaType === 'application/pdf') { ++ 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} as inline data (xAI Responses requires a URL or a Files API reference for non-image files)`, +diff --git a/src/responses/xai-responses-api.ts b/src/responses/xai-responses-api.ts +--- a/src/responses/xai-responses-api.ts ++++ b/src/responses/xai-responses-api.ts +@@ -27,6 +27,12 @@ export type XaiResponsesSystemMessage = { + export type XaiResponsesUserMessageContentPart = + | { type: 'input_text'; text: string } + | { type: 'input_image'; image_url: string } +- | { type: 'input_file'; file_url: string }; +- ++ | { ++ type: 'input_file'; ++ file_url?: string; ++ file_id?: string; ++ file_data?: string; ++ filename?: string; ++ }; ++ + export type XaiResponsesUserMessage = {