From b3d0446d13504f63c6c26dfd040779a3ccd056cc Mon Sep 17 00:00:00 2001 From: Jaaneek <25470423+Jaaneek@users.noreply.github.com> Date: Fri, 20 Mar 2026 02:09:49 +0000 Subject: [PATCH] feat: switch xai provider to responses API (#18175) Co-authored-by: Jaaneek --- bun.lock | 1 + package.json | 3 +- packages/opencode/src/provider/provider.ts | 9 ++ patches/@ai-sdk%2Fxai@2.0.51.patch | 108 +++++++++++++++++++++ 4 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 patches/@ai-sdk%2Fxai@2.0.51.patch diff --git a/bun.lock b/bun.lock index 91c007da3..6d8237845 100644 --- a/bun.lock +++ b/bun.lock @@ -586,6 +586,7 @@ ], "patchedDependencies": { "@openrouter/ai-sdk-provider@1.5.4": "patches/@openrouter%2Fai-sdk-provider@1.5.4.patch", + "@ai-sdk/xai@2.0.51": "patches/@ai-sdk%2Fxai@2.0.51.patch", "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", }, "overrides": { diff --git a/package.json b/package.json index 4d89daffd..2875b9daa 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ }, "patchedDependencies": { "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", - "@openrouter/ai-sdk-provider@1.5.4": "patches/@openrouter%2Fai-sdk-provider@1.5.4.patch" + "@openrouter/ai-sdk-provider@1.5.4": "patches/@openrouter%2Fai-sdk-provider@1.5.4.patch", + "@ai-sdk/xai@2.0.51": "patches/@ai-sdk%2Fxai@2.0.51.patch" } } diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index f7667fc2c..9c9c8e834 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -184,6 +184,15 @@ export namespace Provider { options: {}, } }, + xai: async () => { + return { + autoload: false, + async getModel(sdk: any, modelID: string, _options?: Record) { + return sdk.responses(modelID) + }, + options: {}, + } + }, "github-copilot": async () => { return { autoload: false, diff --git a/patches/@ai-sdk%2Fxai@2.0.51.patch b/patches/@ai-sdk%2Fxai@2.0.51.patch new file mode 100644 index 000000000..8776cab48 --- /dev/null +++ b/patches/@ai-sdk%2Fxai@2.0.51.patch @@ -0,0 +1,108 @@ +diff --git a/dist/index.mjs b/dist/index.mjs +--- a/dist/index.mjs ++++ b/dist/index.mjs +@@ -959,7 +959,7 @@ + model: z4.string().nullish(), + object: z4.literal("response"), + output: z4.array(outputItemSchema), +- usage: xaiResponsesUsageSchema, ++ usage: xaiResponsesUsageSchema.nullish(), + status: z4.string() + }); + var xaiResponsesChunkSchema = z4.union([ +\ No newline at end of file +@@ -1143,6 +1143,18 @@ + z4.object({ + type: z4.literal("response.completed"), + response: xaiResponsesResponseSchema ++ }), ++ z4.object({ ++ type: z4.literal("response.function_call_arguments.delta"), ++ item_id: z4.string(), ++ output_index: z4.number(), ++ delta: z4.string() ++ }), ++ z4.object({ ++ type: z4.literal("response.function_call_arguments.done"), ++ item_id: z4.string(), ++ output_index: z4.number(), ++ arguments: z4.string() + }) + ]); + +\ No newline at end of file +@@ -1940,6 +1952,9 @@ + if (response2.status) { + finishReason = mapXaiResponsesFinishReason(response2.status); + } ++ if (seenToolCalls.size > 0 && finishReason !== "tool-calls") { ++ finishReason = "tool-calls"; ++ } + return; + } + if (event.type === "response.output_item.added" || event.type === "response.output_item.done") { +\ No newline at end of file +@@ -2024,7 +2039,7 @@ + } + } + } else if (part.type === "function_call") { +- if (!seenToolCalls.has(part.call_id)) { ++ if (event.type === "response.output_item.done" && !seenToolCalls.has(part.call_id)) { + seenToolCalls.add(part.call_id); + controller.enqueue({ + type: "tool-input-start", +\ No newline at end of file +diff --git a/dist/index.js b/dist/index.js +--- a/dist/index.js ++++ b/dist/index.js +@@ -964,7 +964,7 @@ + model: import_v44.z.string().nullish(), + object: import_v44.z.literal("response"), + output: import_v44.z.array(outputItemSchema), +- usage: xaiResponsesUsageSchema, ++ usage: xaiResponsesUsageSchema.nullish(), + status: import_v44.z.string() + }); + var xaiResponsesChunkSchema = import_v44.z.union([ +\ No newline at end of file +@@ -1148,6 +1148,18 @@ + import_v44.z.object({ + type: import_v44.z.literal("response.completed"), + response: xaiResponsesResponseSchema ++ }), ++ import_v44.z.object({ ++ type: import_v44.z.literal("response.function_call_arguments.delta"), ++ item_id: import_v44.z.string(), ++ output_index: import_v44.z.number(), ++ delta: import_v44.z.string() ++ }), ++ import_v44.z.object({ ++ type: import_v44.z.literal("response.function_call_arguments.done"), ++ item_id: import_v44.z.string(), ++ output_index: import_v44.z.number(), ++ arguments: import_v44.z.string() + }) + ]); + +\ No newline at end of file +@@ -1935,6 +1947,9 @@ + if (response2.status) { + finishReason = mapXaiResponsesFinishReason(response2.status); + } ++ if (seenToolCalls.size > 0 && finishReason !== "tool-calls") { ++ finishReason = "tool-calls"; ++ } + return; + } + if (event.type === "response.output_item.added" || event.type === "response.output_item.done") { +\ No newline at end of file +@@ -2019,7 +2034,7 @@ + } + } + } else if (part.type === "function_call") { +- if (!seenToolCalls.has(part.call_id)) { ++ if (event.type === "response.output_item.done" && !seenToolCalls.has(part.call_id)) { + seenToolCalls.add(part.call_id); + controller.enqueue({ + type: "tool-input-start", +\ No newline at end of file