diff --git a/bun.lock b/bun.lock index 292cb1cfdf5..6e3a92f5e6f 100644 --- a/bun.lock +++ b/bun.lock @@ -347,7 +347,6 @@ "@ai-sdk/anthropic": "3.0.82", "@ai-sdk/azure": "3.0.88", "@ai-sdk/cohere": "3.0.27", - "@ai-sdk/deepinfra": "2.0.41", "@ai-sdk/gateway": "3.0.104", "@ai-sdk/google-vertex": "4.0.128", "@ai-sdk/groq": "3.0.31", @@ -1178,8 +1177,6 @@ "@ai-sdk/deepgram": ["@ai-sdk/deepgram@2.0.52", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.46" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8pcrQvEQCbvrrQKnD6hclBbI0hUgSrgyADykRbabxv/g9vPurfMC6n23J7dD+KZ3EcCoW+qz3IUIfySJ58gBOg=="], - "@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@2.0.41", "", { "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-y6RoOP7DGWmDSiSxrUSt5p18sbz+Ixe5lMVPmdE7x+Tr5rlrzvftyHhjWHfqlAtoYERZTGFbP6tPW1OfQcrb4A=="], - "@ai-sdk/deepseek": ["@ai-sdk/deepseek@2.0.47", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MzcQ321JO8OY+TVLFI81A7cIIuoeLLxrLCDD+8C1E3Ro6UFyfMtRXo9bw9OhTMRSDMo6hgSDOo4Fekz8aJtQYQ=="], "@ai-sdk/elevenlabs": ["@ai-sdk/elevenlabs@2.0.52", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.46" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZgkausouWvO9U4ZtowNJ093bSNYOvH8zqls3uLC3+oxzWvbbTZO8SOdmFk0+gGafsXFJvq2yUX9+rEeJPwOJLw=="], @@ -5910,10 +5907,6 @@ "@ai-sdk/deepgram/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.46", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^6.28.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tEtld97plCFiYevsJuOkGkeuhQndeMWFBVrJS4AjnbD5AqrNSXRCe0p+BZ3Cju/sxDeeZ9ym3q9YUV8fASA7aQ=="], - "@ai-sdk/deepinfra/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.37", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-+POSFVcgiu47BK64dhsI6OpcDC0/VAE2ZSaXdXGNNhpC/ava++uSRJYks0k2bpfY0wwCTgpAWZsXn/dG2Yppiw=="], - - "@ai-sdk/deepinfra/@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/deepseek/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="], "@ai-sdk/deepseek/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/HHGmtKllqjg1OLc023v9w9kK3laW7Z6TzfZukYQWCsGBbzB9p60zTvvpXFVcs44NZBVXL3viOa1HRKUbeee8g=="], diff --git a/packages/ai/src/providers/deepinfra.ts b/packages/ai/src/providers/deepinfra.ts new file mode 100644 index 00000000000..a7b749ab881 --- /dev/null +++ b/packages/ai/src/providers/deepinfra.ts @@ -0,0 +1,61 @@ +import type { ProviderPackage } from "../provider-package.js" +import { OpenAICompatibleChat } from "../protocols/openai-compatible-chat.js" +import { AuthOptions, type ProviderAuthOption } from "../route/auth-options.js" +import type { RouteDefaultsInput } from "../route/client.js" +import { ProviderID, type ModelID } from "../schema/index.js" +import { profiles } from "./openai-compatible-profile.js" +import type { OpenAIProviderOptionsInput } from "./openai-options.js" + +export const id = ProviderID.make("deepinfra") + +export type LanguageModelOptions = Omit & + ProviderAuthOption<"optional"> & { + readonly baseURL?: string + readonly providerOptions?: OpenAIProviderOptionsInput + } + +export interface Settings extends ProviderPackage.Settings { + readonly apiKey?: string + readonly baseURL?: string + readonly providerOptions?: OpenAIProviderOptionsInput +} + +export const route = OpenAICompatibleChat.route.with({ + id: "deepinfra-chat", + provider: id, + endpoint: { baseURL: profiles.deepinfra.baseURL }, +}) + +export const routes = [route] + +export const configure = (input: LanguageModelOptions = {}) => { + const { apiKey: _apiKey, auth: _auth, baseURL, ...defaults } = input + const root = baseURL?.replace(/\/+$/, "") + const configured = route.with({ + ...defaults, + endpoint: { + baseURL: root === undefined ? profiles.deepinfra.baseURL : root.endsWith("/openai") ? root : `${root}/openai`, + }, + auth: AuthOptions.bearer(input, "DEEPINFRA_API_KEY"), + }) + return { + id, + model: (modelID: string | ModelID) => + configured.model({ + id: modelID, + compatibility: { maxTokensField: "max_tokens", reasoningField: "reasoning_content", supportsStore: false }, + }), + configure, + } +} + +export const provider = configure() + +export const model: ProviderPackage.Definition["model"] = (modelID, settings) => + configure({ + apiKey: settings.apiKey, + baseURL: settings.baseURL, + headers: settings.headers === undefined ? undefined : { ...settings.headers }, + http: settings.body === undefined ? undefined : { body: { ...settings.body } }, + providerOptions: settings.providerOptions, + }).model(modelID) diff --git a/packages/ai/src/providers/index.ts b/packages/ai/src/providers/index.ts index 4bc1cc2712f..0d6c557a998 100644 --- a/packages/ai/src/providers/index.ts +++ b/packages/ai/src/providers/index.ts @@ -6,6 +6,7 @@ export * as Azure from "./azure.js" export * as Cerebras from "./cerebras.js" export * as Cloudflare from "./cloudflare.js" export { CloudflareAIGateway, CloudflareWorkersAI } from "./cloudflare.js" +export * as DeepInfra from "./deepinfra.js" export * as Google from "./google.js" export * as GoogleVertex from "./google-vertex.js" export * as GoogleVertexChat from "./google-vertex-chat.js" diff --git a/packages/ai/test/fixtures/recordings/deepinfra-chat/deepinfra-llama-3-3-70b-text.json b/packages/ai/test/fixtures/recordings/deepinfra-chat/deepinfra-llama-3-3-70b-text.json new file mode 100644 index 00000000000..1e4fd714e51 --- /dev/null +++ b/packages/ai/test/fixtures/recordings/deepinfra-chat/deepinfra-llama-3-3-70b-text.json @@ -0,0 +1,32 @@ +{ + "version": 1, + "metadata": { + "provider": "deepinfra", + "route": "deepinfra-chat", + "transport": "http", + "model": "meta-llama/Llama-3.3-70B-Instruct-Turbo", + "tags": ["prefix:deepinfra-chat", "provider:deepinfra", "text", "golden"], + "name": "deepinfra-chat/deepinfra-llama-3-3-70b-text", + "recordedAt": "2026-08-26T00:34:03.019Z" + }, + "interactions": [ + { + "transport": "http", + "request": { + "method": "POST", + "url": "https://api.deepinfra.com/v1/openai/chat/completions", + "headers": { + "content-type": "application/json" + }, + "body": "{\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"messages\":[{\"role\":\"system\",\"content\":\"You are concise.\"},{\"role\":\"user\",\"content\":\"Reply exactly with: Hello!\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_tokens\":40,\"temperature\":0}" + }, + "response": { + "status": 200, + "headers": { + "content-type": "text/event-stream; charset=utf-8" + }, + "body": "data: {\"service_tier\":null,\"id\":\"chatcmpl-RbZ8MyY5pos2MShihSmoXoRe\",\"object\":\"chat.completion.chunk\",\"created\":1787704442,\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RbZ8MyY5pos2MShihSmoXoRe\",\"object\":\"chat.completion.chunk\",\"created\":1787704442,\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"Hello\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RbZ8MyY5pos2MShihSmoXoRe\",\"object\":\"chat.completion.chunk\",\"created\":1787704442,\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"!\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RbZ8MyY5pos2MShihSmoXoRe\",\"object\":\"chat.completion.chunk\",\"created\":1787704442,\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":25,\"total_tokens\":28,\"completion_tokens\":3,\"estimated_cost\":null,\"prompt_tokens_details\":null}}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RbZ8MyY5pos2MShihSmoXoRe\",\"object\":\"chat.completion.chunk\",\"created\":1787704442,\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"choices\":[],\"usage\":{\"prompt_tokens\":25,\"total_tokens\":28,\"completion_tokens\":3,\"estimated_cost\":3.46e-6,\"prompt_tokens_details\":null}}\n\ndata: [DONE]\n\n" + } + } + ] +} diff --git a/packages/ai/test/fixtures/recordings/deepinfra-chat/deepinfra-llama-3-3-70b-tool-call.json b/packages/ai/test/fixtures/recordings/deepinfra-chat/deepinfra-llama-3-3-70b-tool-call.json new file mode 100644 index 00000000000..af846107242 --- /dev/null +++ b/packages/ai/test/fixtures/recordings/deepinfra-chat/deepinfra-llama-3-3-70b-tool-call.json @@ -0,0 +1,32 @@ +{ + "version": 1, + "metadata": { + "provider": "deepinfra", + "route": "deepinfra-chat", + "transport": "http", + "model": "meta-llama/Llama-3.3-70B-Instruct-Turbo", + "tags": ["prefix:deepinfra-chat", "provider:deepinfra", "tool", "tool-call", "golden"], + "name": "deepinfra-chat/deepinfra-llama-3-3-70b-tool-call", + "recordedAt": "2026-08-26T00:34:04.173Z" + }, + "interactions": [ + { + "transport": "http", + "request": { + "method": "POST", + "url": "https://api.deepinfra.com/v1/openai/chat/completions", + "headers": { + "content-type": "application/json" + }, + "body": "{\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"messages\":[{\"role\":\"system\",\"content\":\"Call tools exactly as requested.\"},{\"role\":\"user\",\"content\":\"Call get_weather with city exactly Paris.\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"description\":\"Get current weather for a city.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false},\"strict\":false}}],\"tool_choice\":{\"type\":\"function\",\"function\":{\"name\":\"get_weather\"}},\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_tokens\":80,\"temperature\":0}" + }, + "response": { + "status": 200, + "headers": { + "content-type": "text/event-stream; charset=utf-8" + }, + "body": "data: {\"service_tier\":null,\"id\":\"chatcmpl-RFOwhlB2PhZrgMLviGrx5BQf\",\"object\":\"chat.completion.chunk\",\"created\":1787704443,\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RFOwhlB2PhZrgMLviGrx5BQf\",\"object\":\"chat.completion.chunk\",\"created\":1787704443,\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"reasoning_content\":null,\"tool_calls\":[{\"index\":0,\"id\":\"call_SMfBjXa8eCmHLyjfeyARxe3a\",\"function\":{\"arguments\":\"{\\\"city\\\": \\\"Paris\\\"}\",\"name\":\"get_weather\"},\"type\":\"function\"}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RFOwhlB2PhZrgMLviGrx5BQf\",\"object\":\"chat.completion.chunk\",\"created\":1787704443,\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":230,\"total_tokens\":244,\"completion_tokens\":14,\"estimated_cost\":null,\"prompt_tokens_details\":null}}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RFOwhlB2PhZrgMLviGrx5BQf\",\"object\":\"chat.completion.chunk\",\"created\":1787704443,\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"choices\":[],\"usage\":{\"prompt_tokens\":230,\"total_tokens\":244,\"completion_tokens\":14,\"estimated_cost\":0.000027480000000000005,\"prompt_tokens_details\":null}}\n\ndata: [DONE]\n\n" + } + } + ] +} diff --git a/packages/ai/test/fixtures/recordings/deepinfra-chat/deepinfra-llama-3-3-70b-tool-loop.json b/packages/ai/test/fixtures/recordings/deepinfra-chat/deepinfra-llama-3-3-70b-tool-loop.json new file mode 100644 index 00000000000..d6acdc5016d --- /dev/null +++ b/packages/ai/test/fixtures/recordings/deepinfra-chat/deepinfra-llama-3-3-70b-tool-loop.json @@ -0,0 +1,50 @@ +{ + "version": 1, + "metadata": { + "provider": "deepinfra", + "route": "deepinfra-chat", + "transport": "http", + "model": "meta-llama/Llama-3.3-70B-Instruct-Turbo", + "tags": ["prefix:deepinfra-chat", "provider:deepinfra", "tool", "tool-loop", "golden"], + "name": "deepinfra-chat/deepinfra-llama-3-3-70b-tool-loop", + "recordedAt": "2026-08-26T00:34:05.656Z" + }, + "interactions": [ + { + "transport": "http", + "request": { + "method": "POST", + "url": "https://api.deepinfra.com/v1/openai/chat/completions", + "headers": { + "content-type": "application/json" + }, + "body": "{\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"messages\":[{\"role\":\"system\",\"content\":\"Use the get_weather tool exactly once. After the tool result, reply exactly: Paris is sunny.\"},{\"role\":\"user\",\"content\":\"What is the weather in Paris?\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"description\":\"Get current weather for a city.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false},\"strict\":false}}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_tokens\":80,\"temperature\":0}" + }, + "response": { + "status": 200, + "headers": { + "content-type": "text/event-stream; charset=utf-8" + }, + "body": "data: {\"service_tier\":null,\"id\":\"chatcmpl-RxlHFSnlLbUUz6XSxqBQj7TC\",\"object\":\"chat.completion.chunk\",\"created\":1787704444,\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RxlHFSnlLbUUz6XSxqBQj7TC\",\"object\":\"chat.completion.chunk\",\"created\":1787704444,\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"reasoning_content\":null,\"tool_calls\":[{\"index\":0,\"id\":\"call_W3stxe7FNHozlB4tDxlTVZou\",\"function\":{\"arguments\":\"{\\\"city\\\": \\\"Paris\\\"}\",\"name\":\"get_weather\"},\"type\":\"function\"}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RxlHFSnlLbUUz6XSxqBQj7TC\",\"object\":\"chat.completion.chunk\",\"created\":1787704444,\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":243,\"total_tokens\":257,\"completion_tokens\":14,\"estimated_cost\":null,\"prompt_tokens_details\":null}}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RxlHFSnlLbUUz6XSxqBQj7TC\",\"object\":\"chat.completion.chunk\",\"created\":1787704444,\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"choices\":[],\"usage\":{\"prompt_tokens\":243,\"total_tokens\":257,\"completion_tokens\":14,\"estimated_cost\":0.000028780000000000002,\"prompt_tokens_details\":null}}\n\ndata: [DONE]\n\n" + } + }, + { + "transport": "http", + "request": { + "method": "POST", + "url": "https://api.deepinfra.com/v1/openai/chat/completions", + "headers": { + "content-type": "application/json" + }, + "body": "{\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"messages\":[{\"role\":\"system\",\"content\":\"Use the get_weather tool exactly once. After the tool result, reply exactly: Paris is sunny.\"},{\"role\":\"user\",\"content\":\"What is the weather in Paris?\"},{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{\"id\":\"call_W3stxe7FNHozlB4tDxlTVZou\",\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"arguments\":\"{\\\"city\\\":\\\"Paris\\\"}\"}}],\"reasoning_content\":\"\"},{\"role\":\"tool\",\"tool_call_id\":\"call_W3stxe7FNHozlB4tDxlTVZou\",\"content\":\"{\\\"temperature\\\":22,\\\"condition\\\":\\\"sunny\\\"}\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"description\":\"Get current weather for a city.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false},\"strict\":false}}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_tokens\":80,\"temperature\":0}" + }, + "response": { + "status": 200, + "headers": { + "content-type": "text/event-stream; charset=utf-8" + }, + "body": "data: {\"service_tier\":null,\"id\":\"chatcmpl-RHESVChSFPybgK1eUj2cLLY0\",\"object\":\"chat.completion.chunk\",\"created\":1787704445,\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RHESVChSFPybgK1eUj2cLLY0\",\"object\":\"chat.completion.chunk\",\"created\":1787704445,\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"Paris\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RHESVChSFPybgK1eUj2cLLY0\",\"object\":\"chat.completion.chunk\",\"created\":1787704445,\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\" is\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RHESVChSFPybgK1eUj2cLLY0\",\"object\":\"chat.completion.chunk\",\"created\":1787704445,\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\" sunny\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RHESVChSFPybgK1eUj2cLLY0\",\"object\":\"chat.completion.chunk\",\"created\":1787704445,\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\".\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RHESVChSFPybgK1eUj2cLLY0\",\"object\":\"chat.completion.chunk\",\"created\":1787704445,\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":276,\"total_tokens\":281,\"completion_tokens\":5,\"estimated_cost\":null,\"prompt_tokens_details\":null}}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RHESVChSFPybgK1eUj2cLLY0\",\"object\":\"chat.completion.chunk\",\"created\":1787704445,\"model\":\"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\"choices\":[],\"usage\":{\"prompt_tokens\":276,\"total_tokens\":281,\"completion_tokens\":5,\"estimated_cost\":0.0000292,\"prompt_tokens_details\":null}}\n\ndata: [DONE]\n\n" + } + } + ] +} diff --git a/packages/ai/test/provider-package.test.ts b/packages/ai/test/provider-package.test.ts index fafdac48f4f..c5d6219ee77 100644 --- a/packages/ai/test/provider-package.test.ts +++ b/packages/ai/test/provider-package.test.ts @@ -28,6 +28,7 @@ describe("provider package entrypoints", () => { import("@opencode-ai/ai/providers/amazon-bedrock/mantle/responses"), import("@opencode-ai/ai/providers/togetherai"), import("@opencode-ai/ai/providers/cerebras"), + import("@opencode-ai/ai/providers/deepinfra"), ]) for (const module of modules) expect(module.model).toBeFunction() @@ -37,6 +38,24 @@ describe("provider package entrypoints", () => { expect(modules[19].model).toBe(modules[20].model) }) + test("maps DeepInfra package settings onto its native executable model", async () => { + const DeepInfra = await import("@opencode-ai/ai/providers/deepinfra") + const settings = { + apiKey: "fixture", + baseURL: "https://provider.example.test/v1/", + headers: { "x-application": "opencode" }, + body: { service_tier: "priority" }, + providerOptions: { reasoningEffort: "high" as const }, + } + const deepinfra = DeepInfra.model("google/gemma-3-27b-it", settings) + + expect(deepinfra.route.id).toBe("deepinfra-chat") + expect(deepinfra.route.endpoint.baseURL).toBe("https://provider.example.test/v1/openai") + expect(deepinfra.route.defaults.providerOptions).toEqual(settings.providerOptions) + expect(deepinfra.route.defaults.headers).toEqual(settings.headers) + expect(deepinfra.route.defaults.http?.body).toEqual(settings.body) + }) + test("maps OpenRouter and xAI package settings onto executable models", async () => { const OpenRouter = await import("@opencode-ai/ai/providers/openrouter") const XAI = await import("@opencode-ai/ai/providers/xai") diff --git a/packages/ai/test/provider/golden.recorded.test.ts b/packages/ai/test/provider/golden.recorded.test.ts index 581c354325e..5e49fe4cff3 100644 --- a/packages/ai/test/provider/golden.recorded.test.ts +++ b/packages/ai/test/provider/golden.recorded.test.ts @@ -1,6 +1,6 @@ import * as Anthropic from "../../src/providers/anthropic.js" import * as AnthropicCompatible from "../../src/providers/anthropic-compatible.js" -import { Cerebras, TogetherAI } from "../../src/providers/index.js" +import { Cerebras, DeepInfra, TogetherAI } from "../../src/providers/index.js" import { CloudflareAIGateway, CloudflareWorkersAI } from "../../src/providers/cloudflare.js" import * as Google from "../../src/providers/google.js" import * as OpenAI from "../../src/providers/openai.js" @@ -55,6 +55,9 @@ const cerebras = Cerebras.configure({ apiKey: process.env.CEREBRAS_API_KEY ?? "f const groq = OpenAICompatible.groq .configure({ apiKey: process.env.GROQ_API_KEY ?? "fixture" }) .model("llama-3.3-70b-versatile") +const deepInfra = DeepInfra.configure({ apiKey: process.env.DEEPINFRA_API_KEY ?? "fixture" }).model( + "meta-llama/Llama-3.3-70B-Instruct-Turbo", +) const openRouter = OpenRouter.configure({ apiKey: process.env.OPENROUTER_API_KEY ?? "fixture" }) const openrouter = openRouter.model("openai/gpt-4o-mini") const openrouterGpt55 = openRouter.model("openai/gpt-5.5") @@ -222,6 +225,13 @@ describeRecordedGoldenScenarios([ requires: ["GROQ_API_KEY"], scenarios: ["text", "tool-call", { id: "tool-loop", timeout: 30_000 }], }, + { + name: "DeepInfra Llama 3.3 70B", + prefix: "deepinfra-chat", + model: deepInfra, + requires: ["DEEPINFRA_API_KEY"], + scenarios: ["text", "tool-call", { id: "tool-loop", timeout: 30_000 }], + }, { name: "OpenRouter gpt-4o-mini", prefix: "openai-compatible-chat", diff --git a/packages/ai/test/provider/native-providers.test.ts b/packages/ai/test/provider/native-providers.test.ts index 4fdbd3a0081..de658ca1445 100644 --- a/packages/ai/test/provider/native-providers.test.ts +++ b/packages/ai/test/provider/native-providers.test.ts @@ -2,7 +2,7 @@ import { describe, expect } from "bun:test" import { ConfigProvider, Effect } from "effect" import { HttpClientRequest } from "effect/unstable/http" import { LLM, Message, ToolDefinition } from "../../src/index.js" -import { Cerebras, TogetherAI } from "../../src/providers/index.js" +import { Cerebras, DeepInfra, TogetherAI } from "../../src/providers/index.js" import { compileRequest } from "../../src/route/client.js" import { it } from "../lib/effect.js" import { dynamicResponse } from "../lib/http.js" @@ -29,6 +29,18 @@ describe("native OpenAI-compatible providers", () => { }), ) + it.effect("preserves native DeepInfra provider and route identity", () => + Effect.gen(function* () { + const deepinfra = DeepInfra.configure({ apiKey: "fixture" }).model("google/gemma-3-27b-it") + expect(deepinfra).toMatchObject({ + provider: "deepinfra", + compatibility: { maxTokensField: "max_tokens", reasoningField: "reasoning_content", supportsStore: false }, + route: { id: "deepinfra-chat", protocol: "openai-chat" }, + }) + expect(deepinfra.route.endpoint.baseURL).toBe("https://api.deepinfra.com/v1/openai") + }), + ) + it.effect("applies native provider request defaults even with a custom gateway URL", () => Effect.gen(function* () { const together = yield* compileRequest( @@ -82,6 +94,21 @@ describe("native OpenAI-compatible providers", () => { }), ) + it.effect("normalizes DeepInfra API roots without duplicating the OpenAI path", () => + Effect.gen(function* () { + for (const baseURL of [ + "https://gateway.example/v1", + "https://gateway.example/v1/", + "https://gateway.example/v1/openai", + "https://gateway.example/v1/openai/", + ]) { + expect(DeepInfra.configure({ apiKey: "fixture", baseURL }).model("gemma").route.endpoint.baseURL).toBe( + "https://gateway.example/v1/openai", + ) + } + }), + ) + it.effect("maps package settings onto native executable models", () => Effect.gen(function* () { for (const native of [TogetherAI, Cerebras]) { @@ -122,6 +149,12 @@ describe("native OpenAI-compatible providers", () => { token: "cerebras-secret", url: "https://api.cerebras.ai/v1/chat/completions", }, + { + model: DeepInfra.configure().model("gemma"), + env: { DEEPINFRA_API_KEY: "deepinfra-secret" }, + token: "deepinfra-secret", + url: "https://api.deepinfra.com/v1/openai/chat/completions", + }, ] yield* Effect.forEach(scenarios, (scenario) => diff --git a/packages/core/package.json b/packages/core/package.json index 1125c16eeba..c35bac60f06 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -103,7 +103,6 @@ "@ai-sdk/anthropic": "3.0.82", "@ai-sdk/azure": "3.0.88", "@ai-sdk/cohere": "3.0.27", - "@ai-sdk/deepinfra": "2.0.41", "@ai-sdk/gateway": "3.0.104", "@ai-sdk/google-vertex": "4.0.128", "@ai-sdk/groq": "3.0.31", diff --git a/packages/core/src/aisdk-native.ts b/packages/core/src/aisdk-native.ts index 9fdabe9cee3..f2d08386f8d 100644 --- a/packages/core/src/aisdk-native.ts +++ b/packages/core/src/aisdk-native.ts @@ -54,9 +54,10 @@ export function map(input: MapInput): Mapping | undefined { }, } case "@ai-sdk/cerebras": + case "@ai-sdk/deepinfra": case "@ai-sdk/togetherai": return { - package: `@opencode-ai/ai/providers/${input.packageName === "@ai-sdk/cerebras" ? "cerebras" : "togetherai"}`, + package: `@opencode-ai/ai/providers/${input.packageName.slice("@ai-sdk/".length)}`, settings: { ...baseSettings, ...mapAPIKey(input.settings), diff --git a/packages/core/src/model-resolver.ts b/packages/core/src/model-resolver.ts index 56eeb644de8..f38a60d1ff0 100644 --- a/packages/core/src/model-resolver.ts +++ b/packages/core/src/model-resolver.ts @@ -334,6 +334,7 @@ function usesAPIKeyAuth(packageName: string | undefined) { name === "@ai-sdk/openai" || name === "@ai-sdk/anthropic" || name === "@ai-sdk/cerebras" || + name === "@ai-sdk/deepinfra" || name === "@ai-sdk/openai-compatible" || name === "@ai-sdk/google" || name === "@ai-sdk/togetherai" || @@ -345,6 +346,7 @@ function usesAPIKeyAuth(packageName: string | undefined) { name === "@opencode-ai/ai/providers/anthropic" || name === "@opencode-ai/ai/providers/anthropic-compatible" || name === "@opencode-ai/ai/providers/cerebras" || + name === "@opencode-ai/ai/providers/deepinfra" || name === "@opencode-ai/ai/providers/openai-compatible" || name === "@opencode-ai/ai/providers/google" || name === "@opencode-ai/ai/providers/togetherai" || diff --git a/packages/core/src/plugin/provider.ts b/packages/core/src/plugin/provider.ts index ac952b1684c..6ddbefcd8e4 100644 --- a/packages/core/src/plugin/provider.ts +++ b/packages/core/src/plugin/provider.ts @@ -6,7 +6,6 @@ import { CerebrasPlugin } from "./provider/cerebras.js" import { CloudflareAIGatewayPlugin } from "./provider/cloudflare-ai-gateway.js" import { CloudflareWorkersAIPlugin } from "./provider/cloudflare-workers-ai.js" import { CoherePlugin } from "./provider/cohere.js" -import { DeepInfraPlugin } from "./provider/deepinfra.js" import { DynamicProviderPlugin } from "./provider/dynamic.js" import { GatewayPlugin } from "./provider/gateway.js" import { GithubCopilotPlugin } from "./provider/github-copilot.js" @@ -42,7 +41,6 @@ export const ProviderPlugins: PluginInternal.InternalPlugin[] = [ CloudflareAIGatewayPlugin, CloudflareWorkersAIPlugin, CoherePlugin, - DeepInfraPlugin, GatewayPlugin, GithubCopilotPlugin, GitLabPlugin, diff --git a/packages/core/src/plugin/provider/deepinfra.ts b/packages/core/src/plugin/provider/deepinfra.ts deleted file mode 100644 index 30f84abfe82..00000000000 --- a/packages/core/src/plugin/provider/deepinfra.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { createProviderPlugin } from "./factory.js" - -export const DeepInfraPlugin = createProviderPlugin({ - id: "opencode.provider.deepinfra", - package: "@ai-sdk/deepinfra", - load: async (options) => { - const { createDeepInfra } = await import("@ai-sdk/deepinfra") - return createDeepInfra(options) - }, -}) diff --git a/packages/core/src/provider.ts b/packages/core/src/provider.ts index 11a42bdc661..68e54cfd869 100644 --- a/packages/core/src/provider.ts +++ b/packages/core/src/provider.ts @@ -49,6 +49,7 @@ const builtins = new Map Promise>([ ["@opencode-ai/ai/providers/azure/chat", () => import("@opencode-ai/ai/providers/azure/chat")], ["@opencode-ai/ai/providers/azure/responses", () => import("@opencode-ai/ai/providers/azure/responses")], ["@opencode-ai/ai/providers/cerebras", () => import("@opencode-ai/ai/providers/cerebras")], + ["@opencode-ai/ai/providers/deepinfra", () => import("@opencode-ai/ai/providers/deepinfra")], ["@opencode-ai/ai/providers/google", () => import("@opencode-ai/ai/providers/google")], ["@opencode-ai/ai/providers/google-vertex", () => import("@opencode-ai/ai/providers/google-vertex")], ["@opencode-ai/ai/providers/google-vertex/gemini", () => import("@opencode-ai/ai/providers/google-vertex/gemini")], diff --git a/packages/core/test/aisdk-native.test.ts b/packages/core/test/aisdk-native.test.ts index 34a37668156..c9a0a0e9734 100644 --- a/packages/core/test/aisdk-native.test.ts +++ b/packages/core/test/aisdk-native.test.ts @@ -61,8 +61,8 @@ describe("AISDKNative", () => { }) }) - test("maps Cerebras and Together AI settings, headers, and reasoning options to native providers", () => { - for (const name of ["cerebras", "togetherai"]) { + test("maps Cerebras, DeepInfra, and Together AI settings, headers, and reasoning options to native providers", () => { + for (const name of ["cerebras", "deepinfra", "togetherai"]) { expect( map(`@ai-sdk/${name}`, { apiKey: "secret", diff --git a/packages/core/test/model-resolver.test.ts b/packages/core/test/model-resolver.test.ts index 60cff54d82e..af807b8b855 100644 --- a/packages/core/test/model-resolver.test.ts +++ b/packages/core/test/model-resolver.test.ts @@ -885,6 +885,12 @@ describe("ModelResolver", () => { { reasoningEffort: "high" }, { reasoningEffort: "high" }, ], + [ + "@ai-sdk/deepinfra", + "@opencode-ai/ai/providers/deepinfra", + { reasoningEffort: "none" }, + { reasoningEffort: "none" }, + ], [ "@ai-sdk/openai-compatible", "@opencode-ai/ai/providers/openai-compatible", @@ -963,6 +969,7 @@ describe("ModelResolver", () => { ], ["@ai-sdk/azure", "@opencode-ai/ai/providers/azure/responses", "api-model"], ["@ai-sdk/cerebras", "@opencode-ai/ai/providers/cerebras", "api-model"], + ["@ai-sdk/deepinfra", "@opencode-ai/ai/providers/deepinfra", "api-model"], ["@ai-sdk/google", "@opencode-ai/ai/providers/google", "api-model"], ["@ai-sdk/google-vertex", "@opencode-ai/ai/providers/google-vertex", "api-model"], ["@ai-sdk/google-vertex/anthropic", "@opencode-ai/ai/providers/google-vertex/messages", "claude-sonnet-4-6"], @@ -1087,6 +1094,11 @@ describe("ModelResolver", () => { const cerebras = yield* ModelResolver.fromCatalogModel( model(Provider.aisdk("@ai-sdk/cerebras"), { settings: { reasoningEffort: "high" } }), ) + const deepinfra = yield* ModelResolver.fromCatalogModel( + model(Provider.aisdk("@ai-sdk/deepinfra"), { + settings: { baseURL: "https://deepinfra.example/provider-root", reasoningEffort: "none" }, + }), + ) const togetherai = yield* ModelResolver.fromCatalogModel( model(Provider.aisdk("@ai-sdk/togetherai"), { settings: { reasoningEffort: "high" } }), ) @@ -1113,6 +1125,10 @@ describe("ModelResolver", () => { expect(cerebras.route.id).toBe("cerebras-chat") expect(cerebras.route.defaults.providerOptions).toEqual({ reasoningEffort: "high" }) expect(String(cerebras.provider)).toBe("test-provider") + expect(deepinfra.route.id).toBe("deepinfra-chat") + expect(deepinfra.route.endpoint.baseURL).toBe("https://deepinfra.example/provider-root/openai") + expect(deepinfra.route.defaults.providerOptions).toEqual({ reasoningEffort: "none" }) + expect(String(deepinfra.provider)).toBe("test-provider") expect(togetherai.route.id).toBe("togetherai-chat") expect(togetherai.route.defaults.providerOptions).toEqual({ reasoningEffort: "high" }) expect(String(togetherai.provider)).toBe("test-provider") diff --git a/packages/core/test/plugin/provider-factory.test.ts b/packages/core/test/plugin/provider-factory.test.ts index 0ffc422011b..9e5b7bac966 100644 --- a/packages/core/test/plugin/provider-factory.test.ts +++ b/packages/core/test/plugin/provider-factory.test.ts @@ -6,7 +6,6 @@ import { Plugin } from "@opencode-ai/core/plugin" import { PluginHost } from "@opencode-ai/core/plugin/host" import { AlibabaPlugin } from "@opencode-ai/core/plugin/provider/alibaba" import { CoherePlugin } from "@opencode-ai/core/plugin/provider/cohere" -import { DeepInfraPlugin } from "@opencode-ai/core/plugin/provider/deepinfra" import { GatewayPlugin } from "@opencode-ai/core/plugin/provider/gateway" import { GroqPlugin } from "@opencode-ai/core/plugin/provider/groq" import { MistralPlugin } from "@opencode-ai/core/plugin/provider/mistral" @@ -21,7 +20,6 @@ const options = { name: "custom-provider", apiKey: "test", baseURL: "https://exa const providers = [ { id: "alibaba", plugin: AlibabaPlugin, package: "@ai-sdk/alibaba", provider: "alibaba.chat" }, { id: "cohere", plugin: CoherePlugin, package: "@ai-sdk/cohere", provider: "cohere.chat" }, - { id: "deepinfra", plugin: DeepInfraPlugin, package: "@ai-sdk/deepinfra", provider: "deepinfra.chat" }, { id: "gateway", plugin: GatewayPlugin, package: "@ai-sdk/gateway", provider: "gateway" }, { id: "groq", plugin: GroqPlugin, package: "@ai-sdk/groq", provider: "groq.chat" }, { id: "mistral", plugin: MistralPlugin, package: "@ai-sdk/mistral", provider: "mistral.chat" }, diff --git a/packages/core/test/provider.test.ts b/packages/core/test/provider.test.ts index 16008d09b07..56b3418ba09 100644 --- a/packages/core/test/provider.test.ts +++ b/packages/core/test/provider.test.ts @@ -6,6 +6,7 @@ describe("Provider", () => { test("loads bundled native provider entrypoints", async () => { const packages = [ "@opencode-ai/ai/providers/cerebras", + "@opencode-ai/ai/providers/deepinfra", "@opencode-ai/ai/providers/google-vertex", "@opencode-ai/ai/providers/google-vertex/gemini", "@opencode-ai/ai/providers/google-vertex/chat",