fix(opencode): bump @ai-sdk/xai to 3.0.139 so tool-result images reach xAI (#53549)

This commit is contained in:
Milosz Jankiewicz
2026-10-06 11:11:21 -05:00
committed by GitHub
parent 3f393d78bf
commit 83802d800e
7 changed files with 319 additions and 94 deletions
+19 -7
View File
@@ -313,7 +313,7 @@
"@ai-sdk/provider-utils": "4.0.51",
"@ai-sdk/togetherai": "2.0.68",
"@ai-sdk/vercel": "2.0.39",
"@ai-sdk/xai": "3.0.102",
"@ai-sdk/xai": "3.0.139",
"@aws-sdk/credential-providers": "3.1057.0",
"@effect/opentelemetry": "catalog:",
"@effect/platform-node": "catalog:",
@@ -588,7 +588,7 @@
"@ai-sdk/provider": "3.0.16",
"@ai-sdk/togetherai": "2.0.68",
"@ai-sdk/vercel": "2.0.39",
"@ai-sdk/xai": "3.0.102",
"@ai-sdk/xai": "3.0.139",
"@aws-sdk/credential-providers": "3.1057.0",
"@clack/prompts": "1.0.0-alpha.1",
"@effect/opentelemetry": "catalog:",
@@ -1058,7 +1058,6 @@
"patchedDependencies": {
"@pierre/[email protected]": "patches/@pierre%[email protected]",
"@tanstack/[email protected]": "patches/@tanstack%[email protected]",
"@ai-sdk/[email protected]": "patches/@ai-sdk%[email protected]",
"@modelcontextprotocol/[email protected]": "patches/@modelcontextprotocol%[email protected]",
"[email protected]": "patches/[email protected]",
"@ai-sdk/[email protected]": "patches/@ai-sdk%[email protected]",
@@ -1071,6 +1070,7 @@
"@npmcli/[email protected]": "patches/@npmcli%[email protected]",
"@ai-sdk/[email protected]": "patches/@ai-sdk%[email protected]",
"@ai-sdk/[email protected]": "patches/@ai-sdk%[email protected]",
"@ai-sdk/[email protected]": "patches/@ai-sdk%[email protected]",
"@ai-sdk/[email protected]": "patches/@ai-sdk%[email protected]",
"[email protected]": "patches/[email protected]",
"@dnd-kit/[email protected]": "patches/@dnd-kit%[email protected]",
@@ -1214,7 +1214,7 @@
"@ai-sdk/vercel": ["@ai-sdk/[email protected]", "", { "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/[email protected]02", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.56", "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-NeQyOR7OCqDMgaLS4uNX/ep/HrwUzzFYLzXQSRoqLy2jsnqxAJhsgltRwAwf+ADjyPBIAKEOestWnIQA+LrLrQ=="],
"@ai-sdk/xai": ["@ai-sdk/[email protected]39", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.81", "@ai-sdk/provider": "3.0.18", "@ai-sdk/provider-utils": "4.0.57" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-epoKIfuCcmQh6uVn90OESeWmr7mqPJ3YtXNXStdpRvXLVqPmo47pnDUW/AH1vyvZ+6gsKg9TbSGVvyGeFQyHlg=="],
"@alloc/quick-lru": ["@alloc/[email protected]", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
@@ -5710,11 +5710,11 @@
"@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "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/[email protected].56", "", { "dependencies": { "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cQrN6OUn/jvsY3OdsU6Wn+ss7vp1iwIcakZKSlSRMnYqShBfyT7Qht+eqmgxs7w9ttrw6FAG6o11AiBs+iEsTA=="],
"@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/[email protected].81", "", { "dependencies": { "@ai-sdk/provider": "3.0.18", "@ai-sdk/provider-utils": "4.0.57" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-L14Jd0lAFKNM42l9TxlpwkNiyMlSvSvGySLc9GN4FB0qOo4/WmYOeLyn/CPhghIr0K4O6vH1y0nChxGljq2ccg=="],
"@ai-sdk/xai/@ai-sdk/provider": ["@ai-sdk/[email protected]3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-ZPtVYt5QIJzOta1kdUiDuCx4HhFkvNPv/rvmZ2b1iXwybYjJsCnNYR4PAw4kW7rgVfDARvHXcU64efWuqNp6bw=="],
"@ai-sdk/xai/@ai-sdk/provider": ["@ai-sdk/[email protected]8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-IpefF5ssZVOZCD4Wu7zxt6PwZoGT2HLVjxpJ10Bjx6EAmRfLYJPjyrfxb0RYDlwvXnDb9nkQ7Do/oJVDuRFzew=="],
"@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/[email protected].35", "", { "dependencies": { "@ai-sdk/provider": "3.0.13", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bjYld/2KGPLt78kpqbya+fD4LYS7BqVQJyUjE3qAHrYB0FR2Q90BaWEVIBZaguTWXf/A8L6uG1zO1v9TxVlGWg=="],
"@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]7", "", { "dependencies": { "@ai-sdk/provider": "3.0.18", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^6.28.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-89a7sPvZqXsP3PigcAQLWntu/duIVEcN/RyChAB+j6p3C7NwW767sVttvL8r0+BetTg63uAXQoYHCZZueQKBSw=="],
"@astrojs/check/yargs": ["[email protected]", "", { "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=="],
@@ -6176,6 +6176,8 @@
"ai-gateway-provider/@ai-sdk/perplexity": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bL3SWrPltTuxVNg/bZ5APbJLVe0+BIU+BYAbT8Yo0eSAZ5eMTImN2murQHZa08Wi8lUMm8MNolaYBQkb0JDbFw=="],
"ai-gateway-provider/@ai-sdk/xai": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.56", "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-NeQyOR7OCqDMgaLS4uNX/ep/HrwUzzFYLzXQSRoqLy2jsnqxAJhsgltRwAwf+ADjyPBIAKEOestWnIQA+LrLrQ=="],
"ai-gateway-provider/@openrouter/ai-sdk-provider": ["@openrouter/[email protected]", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-FMsAEjLUt5pWuRE2LDC/LCvVrFjLlrEzUITH5+5SZtfq7KZ2wrOHjQVxzz92sju8S9ltpzW87CLW8/b0oBXVCw=="],
"ajv-keywords/ajv": ["[email protected]", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
@@ -6618,6 +6620,8 @@
"@ai-sdk/xai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/[email protected]", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@ai-sdk/xai/@ai-sdk/provider-utils/undici": ["[email protected]", "", {}, "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA=="],
"@astrojs/check/yargs/cliui": ["[email protected]", "", { "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": ["[email protected]", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
@@ -7014,6 +7018,12 @@
"ai-gateway-provider/@ai-sdk/perplexity/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"ai-gateway-provider/@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cQrN6OUn/jvsY3OdsU6Wn+ss7vp1iwIcakZKSlSRMnYqShBfyT7Qht+eqmgxs7w9ttrw6FAG6o11AiBs+iEsTA=="],
"ai-gateway-provider/@ai-sdk/xai/@ai-sdk/provider": ["@ai-sdk/[email protected]", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-ZPtVYt5QIJzOta1kdUiDuCx4HhFkvNPv/rvmZ2b1iXwybYjJsCnNYR4PAw4kW7rgVfDARvHXcU64efWuqNp6bw=="],
"ai-gateway-provider/@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.13", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bjYld/2KGPLt78kpqbya+fD4LYS7BqVQJyUjE3qAHrYB0FR2Q90BaWEVIBZaguTWXf/A8L6uG1zO1v9TxVlGWg=="],
"ai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/[email protected]", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"ajv-keywords/ajv/json-schema-traverse": ["[email protected]", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
@@ -7462,6 +7472,8 @@
"ai-gateway-provider/@ai-sdk/perplexity/@ai-sdk/provider-utils/undici": ["[email protected]", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"ai-gateway-provider/@ai-sdk/xai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/[email protected]", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"ansi-align/string-width/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"app-builder-lib/@electron/get/fs-extra/universalify": ["[email protected]", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
+1 -1
View File
@@ -151,7 +151,7 @@
"@silvia-odwyer/[email protected]": "patches/@silvia-odwyer%[email protected]",
"@standard-community/[email protected]": "patches/@standard-community%[email protected]",
"[email protected]": "patches/[email protected]",
"@ai-sdk/[email protected]02": "patches/@ai-sdk%[email protected]02.patch",
"@ai-sdk/[email protected]39": "patches/@ai-sdk%[email protected]39.patch",
"@ai-sdk/[email protected]": "patches/@ai-sdk%[email protected]",
"[email protected]": "patches/[email protected]",
"[email protected]": "patches/[email protected]",
+1 -1
View File
@@ -80,7 +80,7 @@
"@ai-sdk/provider-utils": "4.0.51",
"@ai-sdk/togetherai": "2.0.68",
"@ai-sdk/vercel": "2.0.39",
"@ai-sdk/xai": "3.0.102",
"@ai-sdk/xai": "3.0.139",
"@aws-sdk/credential-providers": "3.1057.0",
"@effect/opentelemetry": "catalog:",
"@effect/platform-node": "catalog:",
+1 -1
View File
@@ -73,7 +73,7 @@
"@ai-sdk/provider": "3.0.16",
"@ai-sdk/togetherai": "2.0.68",
"@ai-sdk/vercel": "2.0.39",
"@ai-sdk/xai": "3.0.102",
"@ai-sdk/xai": "3.0.139",
"@aws-sdk/credential-providers": "3.1057.0",
"@clack/prompts": "1.0.0-alpha.1",
"@effect/opentelemetry": "catalog:",
+10 -1
View File
@@ -162,6 +162,12 @@ export const toModelMessagesEffect = Effect.fnUntraced(function* (
return false
}
// xAI rejects any other image format (e.g. GIF) with invalid_image, failing the whole request.
const rejectedByProvider = (attachment: { mime: string }) =>
model.api.npm === "@ai-sdk/xai" &&
attachment.mime.startsWith("image/") &&
!["image/png", "image/jpeg", "image/webp"].includes(attachment.mime)
const toModelOutput = (options: { toolCallId: string; input: unknown; output: unknown }) => {
const output = options.output
if (typeof output === "string") {
@@ -297,7 +303,10 @@ export const toModelMessagesEffect = Effect.fnUntraced(function* (
const outputText = part.state.time.compacted
? "[Old tool result content cleared]"
: truncateToolOutput(part.state.output, options?.toolOutputMaxChars)
const attachments = part.state.time.compacted || options?.stripMedia ? [] : (part.state.attachments ?? [])
const attachments =
part.state.time.compacted || options?.stripMedia
? []
: (part.state.attachments ?? []).filter((a) => !rejectedByProvider(a))
// For providers that don't support media in tool results, extract media files
// (images, PDFs) to be sent as a separate user message
@@ -1,6 +1,7 @@
import { describe, expect, test } from "bun:test"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { APICallError } from "ai"
import { createXai } from "@ai-sdk/xai"
import { APICallError, generateText } from "ai"
import { MessageV2 } from "../../src/session/message-v2"
import { ProviderTransform } from "@/provider/transform"
import type { Provider } from "@/provider/provider"
@@ -494,6 +495,195 @@ describe("session.message-v2.toModelMessage", () => {
})
})
test("sends tool-result images to xai responses as input_image", async () => {
const xaiModel: Provider.Model = {
...model,
id: ModelV2.ID.make("grok-4.7"),
providerID: ProviderV2.ID.make("xai"),
api: {
id: "grok-4.7",
url: "https://api.x.ai/v1",
npm: "@ai-sdk/xai",
},
capabilities: {
...model.capabilities,
attachment: true,
input: {
...model.capabilities.input,
image: true,
},
},
}
const png = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]).toString("base64")
const userID = "m-user-xai"
const assistantID = "m-assistant-xai"
const input: SessionV1.WithParts[] = [
{
info: userInfo(userID),
parts: [
{
...basePart(userID, "u1-xai"),
type: "text",
text: "describe the diagram",
},
] as SessionV1.Part[],
},
{
info: assistantInfo(assistantID, userID),
parts: [
{
...basePart(assistantID, "a1-xai"),
type: "tool",
callID: "call-xai-1",
tool: "read",
state: {
status: "completed",
input: { filePath: "/tmp/diagram.png" },
output: "Image read successfully",
title: "Read",
metadata: {},
time: { start: 0, end: 1 },
attachments: [
{
...basePart(assistantID, "file-xai-1"),
type: "file",
mime: "image/png",
filename: "diagram.png",
url: `data:image/png;base64,${png}`,
},
],
},
},
] as SessionV1.Part[],
},
]
const bodies: Record<string, unknown>[] = []
const xai = createXai({
apiKey: "test",
fetch: Object.assign(
async (url: RequestInfo | URL, init?: RequestInit) => {
bodies.push(await new Request(url, init).json())
return new Response("{}", { status: 400 })
},
{ preconnect: globalThis.fetch.preconnect },
),
})
await generateText({
model: xai.responses(xaiModel.api.id),
messages: ProviderTransform.message(await MessageV2.toModelMessages(input, xaiModel), xaiModel, {}),
providerOptions: { xai: { promptCacheKey: "session", reasoningEffort: "xhigh" } },
maxRetries: 0,
}).catch(() => undefined)
expect(bodies).toHaveLength(1)
expect(bodies[0].prompt_cache_key).toBe("session")
expect(bodies[0].reasoning).toEqual({ effort: "xhigh" })
expect(bodies[0].input).toContainEqual({
type: "function_call_output",
call_id: "call-xai-1",
output: [
{ type: "input_text", text: "Image read successfully" },
{ type: "input_image", image_url: `data:image/png;base64,${png}` },
],
})
})
test("drops tool-result images xai rejects but keeps png and webp", async () => {
const xaiModel: Provider.Model = {
...model,
id: ModelV2.ID.make("grok-4.7"),
providerID: ProviderV2.ID.make("xai"),
api: {
id: "grok-4.7",
url: "https://api.x.ai/v1",
npm: "@ai-sdk/xai",
},
capabilities: {
...model.capabilities,
attachment: true,
input: {
...model.capabilities.input,
image: true,
},
},
}
const images = [
{ mime: "image/png", data: Buffer.from([0x89, 0x50, 0x4e, 0x47]).toString("base64") },
{ mime: "image/gif", data: Buffer.from("GIF89a").toString("base64") },
{ mime: "image/webp", data: Buffer.from("RIFFWEBP").toString("base64") },
]
const userID = "m-user-xai-gif"
const assistantID = "m-assistant-xai-gif"
const input: SessionV1.WithParts[] = [
{
info: userInfo(userID),
parts: [
{
...basePart(userID, "u1-xai-gif"),
type: "text",
text: "describe the images",
},
] as SessionV1.Part[],
},
{
info: assistantInfo(assistantID, userID),
parts: [
{
...basePart(assistantID, "a1-xai-gif"),
type: "tool",
callID: "call-xai-gif",
tool: "read",
state: {
status: "completed",
input: { filePath: "/tmp/images" },
output: "Images read successfully",
title: "Read",
metadata: {},
time: { start: 0, end: 1 },
attachments: images.map((image, index) => ({
...basePart(assistantID, `file-xai-gif-${index}`),
type: "file" as const,
mime: image.mime,
filename: `image-${index}`,
url: `data:${image.mime};base64,${image.data}`,
})),
},
},
] as SessionV1.Part[],
},
]
const bodies: Record<string, unknown>[] = []
const xai = createXai({
apiKey: "test",
fetch: Object.assign(
async (url: RequestInfo | URL, init?: RequestInit) => {
bodies.push(await new Request(url, init).json())
return new Response("{}", { status: 400 })
},
{ preconnect: globalThis.fetch.preconnect },
),
})
await generateText({
model: xai.responses(xaiModel.api.id),
messages: ProviderTransform.message(await MessageV2.toModelMessages(input, xaiModel), xaiModel, {}),
maxRetries: 0,
}).catch(() => undefined)
expect(bodies).toHaveLength(1)
expect(bodies[0].input).toContainEqual({
type: "function_call_output",
call_id: "call-xai-gif",
output: [
{ type: "input_text", text: "Images read successfully" },
{ type: "input_image", image_url: `data:image/png;base64,${images[0].data}` },
{ type: "input_image", image_url: `data:image/webp;base64,${images[2].data}` },
],
})
expect(JSON.stringify(bodies[0])).not.toContain("image/gif")
})
test("moves bedrock pdf tool-result media into a separate user message", async () => {
const bedrockModel: Provider.Model = {
...model,
@@ -1,22 +1,23 @@
diff --git a/dist/index.d.mts b/dist/index.d.mts
index 266c5ffdd9ee74ff95908ce90858ee4369d4e4ae..6ac6e2873b7681ac632c903694aa38c7b09773fa 100644
index e9c9916..b2cefd4 100644
--- a/dist/index.d.mts
+++ b/dist/index.d.mts
@@ -5,12 +5,7 @@ import { FetchFunction } from '@ai-sdk/provider-utils';
@@ -5,13 +5,7 @@ import { FetchFunction } from '@ai-sdk/provider-utils';
type XaiChatModelId = 'grok-4.3' | 'grok-4.20-0309-reasoning' | 'grok-4.20-0309-non-reasoning' | 'grok-4.20-multi-agent-0309' | 'grok-build-0.1' | (string & {});
type XaiChatModelId = 'grok-4.7' | 'grok-4.6' | 'grok-4.5' | 'grok-4.3' | 'grok-4.20-0309-reasoning' | 'grok-4.20-0309-non-reasoning' | 'grok-4.20-multi-agent-0309' | 'grok-build-0.1' | (string & {});
declare const xaiLanguageModelChatOptions: z.ZodObject<{
- reasoningEffort: z.ZodOptional<z.ZodEnum<{
- none: "none";
- low: "low";
- medium: "medium";
- high: "high";
- xhigh: "xhigh";
- }>>;
+ reasoningEffort: z.ZodOptional<z.ZodString>;
logprobs: z.ZodOptional<z.ZodBoolean>;
topLogprobs: z.ZodOptional<z.ZodNumber>;
parallel_function_calling: z.ZodOptional<z.ZodBoolean>;
@@ -68,16 +63,12 @@ type XaiResponsesModelId = 'grok-4.3' | 'grok-4.20-0309-reasoning' | 'grok-4.20-
serviceTier: z.ZodOptional<z.ZodEnum<{
@@ -82,13 +76,7 @@ type XaiResponsesModelId = 'grok-4.7' | 'grok-4.6' | 'grok-4.5' | 'grok-4.3' | '
* @see https://docs.x.ai/docs/api-reference#create-new-response
*/
declare const xaiLanguageModelResponsesOptions: z.ZodObject<{
@@ -25,10 +26,14 @@ index 266c5ffdd9ee74ff95908ce90858ee4369d4e4ae..6ac6e2873b7681ac632c903694aa38c7
- low: "low";
- medium: "medium";
- high: "high";
- xhigh: "xhigh";
- }>>;
+ reasoningEffort: z.ZodOptional<z.ZodString>;
logprobs: z.ZodOptional<z.ZodBoolean>;
topLogprobs: z.ZodOptional<z.ZodNumber>;
serviceTier: z.ZodOptional<z.ZodEnum<{
@@ -97,6 +85,7 @@ declare const xaiLanguageModelResponsesOptions: z.ZodObject<{
}>>;
store: z.ZodOptional<z.ZodBoolean>;
previousResponseId: z.ZodOptional<z.ZodString>;
+ promptCacheKey: z.ZodOptional<z.ZodString>;
@@ -36,24 +41,25 @@ index 266c5ffdd9ee74ff95908ce90858ee4369d4e4ae..6ac6e2873b7681ac632c903694aa38c7
"file_search_call.results": "file_search_call.results";
}>>>>;
diff --git a/dist/index.d.ts b/dist/index.d.ts
index 266c5ffdd9ee74ff95908ce90858ee4369d4e4ae..6ac6e2873b7681ac632c903694aa38c7b09773fa 100644
index e9c9916..b2cefd4 100644
--- a/dist/index.d.ts
+++ b/dist/index.d.ts
@@ -5,12 +5,7 @@ import { FetchFunction } from '@ai-sdk/provider-utils';
@@ -5,13 +5,7 @@ import { FetchFunction } from '@ai-sdk/provider-utils';
type XaiChatModelId = 'grok-4.3' | 'grok-4.20-0309-reasoning' | 'grok-4.20-0309-non-reasoning' | 'grok-4.20-multi-agent-0309' | 'grok-build-0.1' | (string & {});
type XaiChatModelId = 'grok-4.7' | 'grok-4.6' | 'grok-4.5' | 'grok-4.3' | 'grok-4.20-0309-reasoning' | 'grok-4.20-0309-non-reasoning' | 'grok-4.20-multi-agent-0309' | 'grok-build-0.1' | (string & {});
declare const xaiLanguageModelChatOptions: z.ZodObject<{
- reasoningEffort: z.ZodOptional<z.ZodEnum<{
- none: "none";
- low: "low";
- medium: "medium";
- high: "high";
- xhigh: "xhigh";
- }>>;
+ reasoningEffort: z.ZodOptional<z.ZodString>;
logprobs: z.ZodOptional<z.ZodBoolean>;
topLogprobs: z.ZodOptional<z.ZodNumber>;
parallel_function_calling: z.ZodOptional<z.ZodBoolean>;
@@ -68,16 +63,12 @@ type XaiResponsesModelId = 'grok-4.3' | 'grok-4.20-0309-reasoning' | 'grok-4.20-
serviceTier: z.ZodOptional<z.ZodEnum<{
@@ -82,13 +76,7 @@ type XaiResponsesModelId = 'grok-4.7' | 'grok-4.6' | 'grok-4.5' | 'grok-4.3' | '
* @see https://docs.x.ai/docs/api-reference#create-new-response
*/
declare const xaiLanguageModelResponsesOptions: z.ZodObject<{
@@ -62,10 +68,14 @@ index 266c5ffdd9ee74ff95908ce90858ee4369d4e4ae..6ac6e2873b7681ac632c903694aa38c7
- low: "low";
- medium: "medium";
- high: "high";
- xhigh: "xhigh";
- }>>;
+ reasoningEffort: z.ZodOptional<z.ZodString>;
logprobs: z.ZodOptional<z.ZodBoolean>;
topLogprobs: z.ZodOptional<z.ZodNumber>;
serviceTier: z.ZodOptional<z.ZodEnum<{
@@ -97,6 +85,7 @@ declare const xaiLanguageModelResponsesOptions: z.ZodObject<{
}>>;
store: z.ZodOptional<z.ZodBoolean>;
previousResponseId: z.ZodOptional<z.ZodString>;
+ promptCacheKey: z.ZodOptional<z.ZodString>;
@@ -73,19 +83,19 @@ index 266c5ffdd9ee74ff95908ce90858ee4369d4e4ae..6ac6e2873b7681ac632c903694aa38c7
"file_search_call.results": "file_search_call.results";
}>>>>;
diff --git a/dist/index.js b/dist/index.js
index 717b74538f5c8f0d6ab1475ebb2a84a47ccd3950..0fd8f0d1cae951cd24401034a9c1dba762d9fd84 100644
index d5eb865..13ad9ca 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -246,7 +246,7 @@ var searchSourceSchema = import_v4.z.discriminatedUnion("type", [
@@ -274,7 +274,7 @@ var searchSourceSchema = import_v42.z.discriminatedUnion("type", [
rssSourceSchema
]);
var xaiLanguageModelChatOptions = import_v4.z.object({
- reasoningEffort: import_v4.z.enum(["none", "low", "medium", "high"]).optional(),
+ reasoningEffort: import_v4.z.string().optional(),
logprobs: import_v4.z.boolean().optional(),
topLogprobs: import_v4.z.number().int().min(0).max(8).optional(),
/**
@@ -1119,6 +1119,14 @@ async function convertToXaiResponsesInput({
var xaiLanguageModelChatOptions = import_v42.z.object({
- reasoningEffort: import_v42.z.enum(["none", "low", "medium", "high", "xhigh"]).optional(),
+ reasoningEffort: import_v42.z.string().optional(),
logprobs: import_v42.z.boolean().optional(),
topLogprobs: import_v42.z.number().int().min(0).max(8).optional(),
serviceTier: import_v42.z.enum(["default", "priority"]).optional(),
@@ -1162,6 +1162,14 @@ async function convertToXaiResponsesInput({
type: "input_file",
file_url: block.data.toString()
});
@@ -100,50 +110,50 @@ index 717b74538f5c8f0d6ab1475ebb2a84a47ccd3950..0fd8f0d1cae951cd24401034a9c1dba7
} 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)`
@@ -1746,7 +1754,7 @@ var xaiLanguageModelResponsesOptions = import_v47.z.object({
* tokens), `medium` and `high` (uses more reasoning tokens). Not all models
* support reasoning effort; see xAI's docs for the values each model accepts.
@@ -1846,7 +1854,7 @@ var xaiLanguageModelResponsesOptions = import_v48.z.object({
* models support reasoning effort; see xAI's docs for the values each model
* accepts. `xhigh` is currently only supported by `grok-4.6`.
*/
- reasoningEffort: import_v47.z.enum(["none", "low", "medium", "high"]).optional(),
+ reasoningEffort: import_v47.z.string().optional(),
logprobs: import_v47.z.boolean().optional(),
topLogprobs: import_v47.z.number().int().min(0).max(8).optional(),
/**
@@ -1760,6 +1768,10 @@ var xaiLanguageModelResponsesOptions = import_v47.z.object({
- reasoningEffort: import_v48.z.enum(["none", "low", "medium", "high", "xhigh"]).optional(),
+ reasoningEffort: import_v48.z.string().optional(),
logprobs: import_v48.z.boolean().optional(),
topLogprobs: import_v48.z.number().int().min(0).max(8).optional(),
serviceTier: import_v48.z.enum(["default", "priority"]).optional(),
@@ -1861,6 +1869,10 @@ var xaiLanguageModelResponsesOptions = import_v48.z.object({
* The ID of the previous response from the model.
*/
previousResponseId: import_v47.z.string().optional(),
previousResponseId: import_v48.z.string().optional(),
+ /**
+ * A stable key used by xAI to improve prompt cache routing.
+ */
+ promptCacheKey: import_v47.z.string().optional(),
+ promptCacheKey: import_v48.z.string().optional(),
/**
* Specify additional output data to include in the model response.
* Example values: 'file_search_call.results'.
@@ -2165,6 +2177,9 @@ var XaiResponsesLanguageModel = class {
},
@@ -2288,6 +2300,9 @@ var XaiResponsesLanguageModel = class {
...options.previousResponseId != null && {
previous_response_id: options.previousResponseId
+ },
},
+ ...options.promptCacheKey != null && {
+ prompt_cache_key: options.promptCacheKey
+ },
...options.serviceTier != null && {
service_tier: options.serviceTier
}
};
if (xaiTools2 && xaiTools2.length > 0) {
diff --git a/dist/index.mjs b/dist/index.mjs
index a26af109585fc2bd3053b320142aa869c06d36f4..5faca56477b4e55a87f6f57850731c7d3e1721a5 100644
index 1b24818..afb67cc 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -230,7 +230,7 @@ var searchSourceSchema = z.discriminatedUnion("type", [
@@ -258,7 +258,7 @@ var searchSourceSchema = z2.discriminatedUnion("type", [
rssSourceSchema
]);
var xaiLanguageModelChatOptions = z.object({
- reasoningEffort: z.enum(["none", "low", "medium", "high"]).optional(),
+ reasoningEffort: z.string().optional(),
logprobs: z.boolean().optional(),
topLogprobs: z.number().int().min(0).max(8).optional(),
/**
@@ -1122,6 +1122,14 @@ async function convertToXaiResponsesInput({
var xaiLanguageModelChatOptions = z2.object({
- reasoningEffort: z2.enum(["none", "low", "medium", "high", "xhigh"]).optional(),
+ reasoningEffort: z2.string().optional(),
logprobs: z2.boolean().optional(),
topLogprobs: z2.number().int().min(0).max(8).optional(),
serviceTier: z2.enum(["default", "priority"]).optional(),
@@ -1165,6 +1165,14 @@ async function convertToXaiResponsesInput({
type: "input_file",
file_url: block.data.toString()
});
@@ -158,41 +168,41 @@ index a26af109585fc2bd3053b320142aa869c06d36f4..5faca56477b4e55a87f6f57850731c7d
} 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)`
@@ -1749,7 +1757,7 @@ var xaiLanguageModelResponsesOptions = z7.object({
* tokens), `medium` and `high` (uses more reasoning tokens). Not all models
* support reasoning effort; see xAI's docs for the values each model accepts.
@@ -1849,7 +1857,7 @@ var xaiLanguageModelResponsesOptions = z8.object({
* models support reasoning effort; see xAI's docs for the values each model
* accepts. `xhigh` is currently only supported by `grok-4.6`.
*/
- reasoningEffort: z7.enum(["none", "low", "medium", "high"]).optional(),
+ reasoningEffort: z7.string().optional(),
logprobs: z7.boolean().optional(),
topLogprobs: z7.number().int().min(0).max(8).optional(),
/**
@@ -1763,6 +1771,10 @@ var xaiLanguageModelResponsesOptions = z7.object({
- reasoningEffort: z8.enum(["none", "low", "medium", "high", "xhigh"]).optional(),
+ reasoningEffort: z8.string().optional(),
logprobs: z8.boolean().optional(),
topLogprobs: z8.number().int().min(0).max(8).optional(),
serviceTier: z8.enum(["default", "priority"]).optional(),
@@ -1864,6 +1872,10 @@ var xaiLanguageModelResponsesOptions = z8.object({
* The ID of the previous response from the model.
*/
previousResponseId: z7.string().optional(),
previousResponseId: z8.string().optional(),
+ /**
+ * A stable key used by xAI to improve prompt cache routing.
+ */
+ promptCacheKey: z7.string().optional(),
+ promptCacheKey: z8.string().optional(),
/**
* Specify additional output data to include in the model response.
* Example values: 'file_search_call.results'.
@@ -2186,6 +2198,9 @@ var XaiResponsesLanguageModel = class {
},
@@ -2309,6 +2321,9 @@ var XaiResponsesLanguageModel = class {
...options.previousResponseId != null && {
previous_response_id: options.previousResponseId
+ },
},
+ ...options.promptCacheKey != null && {
+ prompt_cache_key: options.promptCacheKey
+ },
...options.serviceTier != null && {
service_tier: options.serviceTier
}
};
if (xaiTools2 && xaiTools2.length > 0) {
diff --git a/src/responses/convert-to-xai-responses-input.ts b/src/responses/convert-to-xai-responses-input.ts
index 9a3712990cf646a6f7ba757d3f2893a3c37c6ef9..b018a9b95eb21e6c12794e6bddb3d953f40441e5 100644
index 21af0cd..9b477ef 100644
--- 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({
@@ -78,6 +78,17 @@ export async function convertToXaiResponsesInput({
type: 'input_file',
file_url: block.data.toString(),
});
@@ -211,13 +221,13 @@ index 9a3712990cf646a6f7ba757d3f2893a3c37c6ef9..b018a9b95eb21e6c12794e6bddb3d953
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
index 70483ea4f7c340b84c23766ac1d1df21e052842b..4b4d052860caa019556151e9e46dbfda8dfb18a9 100644
index 0bc0f91..aa3857a 100644
--- a/src/responses/xai-responses-api.ts
+++ b/src/responses/xai-responses-api.ts
@@ -27,7 +27,13 @@ export type XaiResponsesSystemMessage = {
export type XaiResponsesUserMessageContentPart =
| { type: 'input_text'; text: string }
| { type: 'input_image'; image_url: string }
@@ -31,7 +31,13 @@ export type XaiResponsesUserMessageContentPart =
image_url: string;
detail?: 'low' | 'high' | 'auto';
}
- | { type: 'input_file'; file_url: string };
+ | {
+ type: 'input_file';
@@ -230,33 +240,35 @@ index 70483ea4f7c340b84c23766ac1d1df21e052842b..4b4d052860caa019556151e9e46dbfda
export type XaiResponsesUserMessage = {
role: 'user';
diff --git a/src/responses/xai-responses-language-model.ts b/src/responses/xai-responses-language-model.ts
index f90df62eb9a30154388b1390e9f3acc3ccc022bf..00e61cba6cf048ae0045be692f33cb7e1e6a1188 100644
index 06d6e5a..98a2d39 100644
--- a/src/responses/xai-responses-language-model.ts
+++ b/src/responses/xai-responses-language-model.ts
@@ -182,6 +182,9 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
@@ -201,6 +201,9 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
...(options.previousResponseId != null && {
previous_response_id: options.previousResponseId,
}),
+ ...(options.promptCacheKey != null && {
+ prompt_cache_key: options.promptCacheKey,
+ }),
};
if (xaiTools && xaiTools.length > 0) {
...(options.serviceTier != null && {
service_tier: options.serviceTier,
}),
diff --git a/src/responses/xai-responses-options.ts b/src/responses/xai-responses-options.ts
index f8e96c061bf8793a402ababb8cad65bb2ad6aead..2a39a36221ab23ea0000bff1d7854c5bce3f9d74 100644
index 6d52f46..9577200 100644
--- a/src/responses/xai-responses-options.ts
+++ b/src/responses/xai-responses-options.ts
@@ -18,7 +18,7 @@ export const xaiLanguageModelResponsesOptions = z.object({
* tokens), `medium` and `high` (uses more reasoning tokens). Not all models
* support reasoning effort; see xAI's docs for the values each model accepts.
@@ -22,9 +22,7 @@ export const xaiLanguageModelResponsesOptions = z.object({
* models support reasoning effort; see xAI's docs for the values each model
* accepts. `xhigh` is currently only supported by `grok-4.6`.
*/
- reasoningEffort: z.enum(['none', 'low', 'medium', 'high']).optional(),
- reasoningEffort: z
- .enum(['none', 'low', 'medium', 'high', 'xhigh'])
- .optional(),
+ reasoningEffort: z.string().optional(),
logprobs: z.boolean().optional(),
topLogprobs: z.number().int().min(0).max(8).optional(),
/**
@@ -32,6 +32,10 @@ export const xaiLanguageModelResponsesOptions = z.object({
serviceTier: z.enum(['default', 'priority']).optional(),
@@ -39,6 +37,10 @@ export const xaiLanguageModelResponsesOptions = z.object({
* The ID of the previous response from the model.
*/
previousResponseId: z.string().optional(),
@@ -268,14 +280,16 @@ index f8e96c061bf8793a402ababb8cad65bb2ad6aead..2a39a36221ab23ea0000bff1d7854c5b
* Specify additional output data to include in the model response.
* Example values: 'file_search_call.results'.
diff --git a/src/xai-chat-options.ts b/src/xai-chat-options.ts
index d70a72a9fa01da2c711c291da5ce949efbde60b5..fd6b1ae025388b614f08b620244be553199479ca 100644
index f3f1237..adb6ada 100644
--- a/src/xai-chat-options.ts
+++ b/src/xai-chat-options.ts
@@ -51,7 +51,7 @@ const searchSourceSchema = z.discriminatedUnion('type', [
@@ -54,9 +54,7 @@ const searchSourceSchema = z.discriminatedUnion('type', [
// xai-specific provider options
export const xaiLanguageModelChatOptions = z.object({
- reasoningEffort: z.enum(['none', 'low', 'medium', 'high']).optional(),
- reasoningEffort: z
- .enum(['none', 'low', 'medium', 'high', 'xhigh'])
- .optional(),
+ reasoningEffort: z.string().optional(),
logprobs: z.boolean().optional(),
topLogprobs: z.number().int().min(0).max(8).optional(),