diff --git a/packages/opencode/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts b/packages/opencode/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts index e71658c2fa0..fa7d64ed6e2 100644 --- a/packages/opencode/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +++ b/packages/opencode/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts @@ -29,6 +29,11 @@ export interface OpenaiCompatibleProviderSettings { */ headers?: Record + /** + * Custom query parameter to append to the requests + */ + queryParams?: Record + /** * Custom fetch implementation. */ @@ -65,11 +70,21 @@ export function createOpenaiCompatible(options: OpenaiCompatibleProviderSettings const getHeaders = () => withUserAgentSuffix(headers, `ai-sdk/openai-compatible/${VERSION}`) + const getUrl = ({ path }: { path: string }) => { + const url = new URL(`${baseURL}${path}`) + if (options.queryParams) { + for (const [key, value] of Object.entries(options.queryParams)) { + url.searchParams.set(key, value) + } + } + return url.toString() + } + const createChatModel = (modelId: OpenaiCompatibleModelId) => { return new OpenAICompatibleChatLanguageModel(modelId, { provider: `${options.name ?? "openai-compatible"}.chat`, headers: getHeaders, - url: ({ path }) => `${baseURL}${path}`, + url: getUrl, fetch: options.fetch, }) } @@ -78,7 +93,7 @@ export function createOpenaiCompatible(options: OpenaiCompatibleProviderSettings return new OpenAIResponsesLanguageModel(modelId, { provider: `${options.name ?? "openai-compatible"}.responses`, headers: getHeaders, - url: ({ path }) => `${baseURL}${path}`, + url: getUrl, fetch: options.fetch, }) } diff --git a/packages/opencode/test/provider/openai-compatible.test.ts b/packages/opencode/test/provider/openai-compatible.test.ts new file mode 100644 index 00000000000..5bcea364aaa --- /dev/null +++ b/packages/opencode/test/provider/openai-compatible.test.ts @@ -0,0 +1,132 @@ +import { test, expect } from "bun:test" +import path from "path" +import { tmpdir } from "../fixture/fixture" +import { Instance } from "../../src/project/instance" +import { Provider } from "../../src/provider/provider" + +test("provider with queryParams in config", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + provider: { + "custom-openai": { + name: "Custom OpenAI with Query Params", + npm: "@ai-sdk/openai-compatible", + env: [], + models: { + "gpt-4": { + name: "GPT-4", + tool_call: true, + limit: { context: 128000, output: 4096 }, + }, + }, + options: { + apiKey: "test-key", + baseURL: "https://api.example.com/v1", + queryParams: { + "api-version": "2024-01-01", + tenant: "my-org", + }, + }, + }, + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const providers = await Provider.list() + expect(providers["custom-openai"]).toBeDefined() + expect(providers["custom-openai"].name).toBe("Custom OpenAI with Query Params") + expect(providers["custom-openai"].options.queryParams).toEqual({ + "api-version": "2024-01-01", + tenant: "my-org", + }) + }, + }) +}) + +test("provider without queryParams still works", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + provider: { + "custom-openai": { + name: "Custom OpenAI", + npm: "@ai-sdk/openai-compatible", + env: [], + models: { + "gpt-4": { + name: "GPT-4", + tool_call: true, + limit: { context: 128000, output: 4096 }, + }, + }, + options: { + apiKey: "test-key", + baseURL: "https://api.example.com/v1", + }, + }, + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const providers = await Provider.list() + expect(providers["custom-openai"]).toBeDefined() + expect(providers["custom-openai"].name).toBe("Custom OpenAI") + expect(providers["custom-openai"].options.queryParams).toBeUndefined() + }, + }) +}) + +test("provider with empty queryParams", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + provider: { + "custom-openai": { + name: "Custom OpenAI Empty Params", + npm: "@ai-sdk/openai-compatible", + env: [], + models: { + "gpt-4": { + name: "GPT-4", + tool_call: true, + limit: { context: 128000, output: 4096 }, + }, + }, + options: { + apiKey: "test-key", + baseURL: "https://api.example.com/v1", + queryParams: {}, + }, + }, + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const providers = await Provider.list() + expect(providers["custom-openai"]).toBeDefined() + expect(providers["custom-openai"].options.queryParams).toEqual({}) + }, + }) +})