feat(ai): add native Cerebras and Together AI providers (#45098)

This commit is contained in:
Aiden Cline
2026-08-25 19:20:47 -05:00
committed by GitHub
parent 6cd1ffac50
commit 6c97be6974
25 changed files with 507 additions and 115 deletions
-8
View File
@@ -346,7 +346,6 @@
"@ai-sdk/amazon-bedrock": "4.0.112",
"@ai-sdk/anthropic": "3.0.82",
"@ai-sdk/azure": "3.0.88",
"@ai-sdk/cerebras": "2.0.41",
"@ai-sdk/cohere": "3.0.27",
"@ai-sdk/deepinfra": "2.0.41",
"@ai-sdk/gateway": "3.0.104",
@@ -357,7 +356,6 @@
"@ai-sdk/perplexity": "3.0.26",
"@ai-sdk/provider": "3.0.8",
"@ai-sdk/provider-utils": "4.0.23",
"@ai-sdk/togetherai": "2.0.41",
"@ai-sdk/vercel": "2.0.39",
"@aws-sdk/credential-providers": "3.1057.0",
"@ff-labs/fff-bun": "0.10.5",
@@ -1207,8 +1205,6 @@
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.23", "", { "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-z8GlDaCmRSDlqkMF2f4/RFgWxdarvIbyuk+m6WXT1LYgsnGiXRJGTD2Z1+SDl3LqtFuRtGX1aghYvQLoHL/9pg=="],
"@ai-sdk/togetherai": ["@ai-sdk/togetherai@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-k3p9e3k0/gpDDyTtvafsK4HYR4D/aUQW/kzCwWo1+CzdBU84i4L14gWISC/mv6tgSicMXHcEUd521fPufQwNlg=="],
"@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.123", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.69", "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.46" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-WNASvd1C516oh2qYIj9EvAVPdU+Abads8DQWU6p9lQtvFFeGh8QW+3LDOARZd1GCINUFfw5yadEK845SMQKLsA=="],
@@ -5955,10 +5951,6 @@
"@ai-sdk/perplexity/@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/togetherai/@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/togetherai/@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/vercel/@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/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=="],
+5 -5
View File
@@ -157,9 +157,9 @@ const PROVIDERS: ReadonlyArray<Provider> = [
id: "togetherai",
label: "TogetherAI",
tier: "compatible",
note: "Existing OpenAI-compatible text/tool recorded tests",
vars: [{ name: "TOGETHER_AI_API_KEY" }],
validate: (env) => validateBearer("https://api.together.xyz/v1/models", Redacted.make(env.TOGETHER_AI_API_KEY)),
note: "Native Together AI text/tool recorded tests",
vars: [{ name: "TOGETHER_API_KEY" }],
validate: (env) => validateBearer("https://api.together.xyz/v1/models", Redacted.make(env.TOGETHER_API_KEY)),
},
{
id: "minimax",
@@ -200,8 +200,8 @@ const PROVIDERS: ReadonlyArray<Provider> = [
{
id: "cerebras",
label: "Cerebras",
tier: "optional",
note: "OpenAI-compatible bridge",
tier: "compatible",
note: "Native Cerebras text/tool/tool-loop recorded tests",
vars: [{ name: "CEREBRAS_API_KEY" }],
validate: (env) => validateBearer("https://api.cerebras.ai/v1/models", Redacted.make(env.CEREBRAS_API_KEY)),
},
+58
View File
@@ -0,0 +1,58 @@
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("cerebras")
export type LanguageModelOptions = Omit<RouteDefaultsInput, "providerOptions"> &
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: "cerebras-chat",
provider: id,
endpoint: { baseURL: profiles.cerebras.baseURL },
})
export const routes = [route]
export const configure = (input: LanguageModelOptions = {}) => {
const { apiKey: _apiKey, auth: _auth, baseURL, ...defaults } = input
const configured = route.with({
...defaults,
endpoint: { baseURL: baseURL ?? profiles.cerebras.baseURL },
auth: AuthOptions.bearer(input, "CEREBRAS_API_KEY"),
})
return {
id,
model: (modelID: string | ModelID) =>
configured.model<OpenAIProviderOptionsInput>({
id: modelID,
compatibility: { maxTokensField: "max_tokens", reasoningField: "reasoning", supportsStore: false },
}),
configure,
}
}
export const provider = configure()
export const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsInput>["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)
+2
View File
@@ -3,6 +3,7 @@ export * as AnthropicCompatible from "./anthropic-compatible.js"
export * as AmazonBedrock from "./amazon-bedrock.js"
export * as AmazonBedrockMantle from "./amazon-bedrock-mantle.js"
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 Google from "./google.js"
@@ -14,5 +15,6 @@ export * as OpenAI from "./openai.js"
export * as OpenAICompatible from "./openai-compatible.js"
export * as OpenAICompatibleResponses from "./openai-compatible-responses.js"
export * as OpenRouter from "./openrouter.js"
export * as TogetherAI from "./togetherai.js"
export * as XAI from "./xai.js"
export * as ZAI from "./zai.js"
+58
View File
@@ -0,0 +1,58 @@
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("togetherai")
export type LanguageModelOptions = Omit<RouteDefaultsInput, "providerOptions"> &
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: "togetherai-chat",
provider: id,
endpoint: { baseURL: profiles.togetherai.baseURL },
})
export const routes = [route]
export const configure = (input: LanguageModelOptions = {}) => {
const { apiKey: _apiKey, auth: _auth, baseURL, ...defaults } = input
const configured = route.with({
...defaults,
endpoint: { baseURL: baseURL ?? profiles.togetherai.baseURL },
auth: AuthOptions.bearer(input, ["TOGETHER_API_KEY", "TOGETHER_AI_API_KEY"]),
})
return {
id,
model: (modelID: string | ModelID) =>
configured.model<OpenAIProviderOptionsInput>({
id: modelID,
compatibility: { maxTokensField: "max_tokens", supportsStore: false, supportsStrictMode: false },
}),
configure,
}
}
export const provider = configure()
export const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsInput>["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)
@@ -0,0 +1,32 @@
{
"version": 1,
"metadata": {
"provider": "cerebras",
"route": "cerebras-chat",
"transport": "http",
"model": "gpt-oss-120b",
"tags": ["prefix:cerebras-chat", "provider:cerebras", "text", "golden"],
"name": "cerebras-chat/cerebras-gpt-oss-120b-text",
"recordedAt": "2026-08-25T23:55:27.619Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.cerebras.ai/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"gpt-oss-120b\",\"messages\":[{\"role\":\"system\",\"content\":\"You are concise.\"},{\"role\":\"user\",\"content\":\"Reply exactly with: Hello!\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_tokens\":256}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "data: {\"id\":\"chatcmpl-cd45cd6e-886a-433e-8ba9-caca78c1f13a\",\"choices\":[{\"delta\":{\"role\":\"assistant\"},\"index\":0}],\"created\":1787702127,\"model\":\"gpt-oss-120b\",\"system_fingerprint\":\"fp_e2cabf4999eb0aead3d1\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-cd45cd6e-886a-433e-8ba9-caca78c1f13a\",\"choices\":[{\"delta\":{\"reasoning\":\"The\"},\"index\":0}],\"created\":1787702127,\"model\":\"gpt-oss-120b\",\"system_fingerprint\":\"fp_e2cabf4999eb0aead3d1\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-cd45cd6e-886a-433e-8ba9-caca78c1f13a\",\"choices\":[{\"delta\":{\"reasoning\":\" user says: \\\"Reply exactly with: Hello!\\\" So we must output exactly\"},\"index\":0}],\"created\":1787702127,\"model\":\"gpt-oss-120b\",\"system_fingerprint\":\"fp_e2cabf4999eb0aead3d1\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-cd45cd6e-886a-433e-8ba9-caca78c1f13a\",\"choices\":[{\"delta\":{\"reasoning\":\" \\\"Hello!\\\" with no extra characters, no formatting\"},\"index\":0}],\"created\":1787702127,\"model\":\"gpt-oss-120b\",\"system_fingerprint\":\"fp_e2cabf4999eb0aead3d1\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-cd45cd6e-886a-433e-8ba9-caca78c1f13a\",\"choices\":[{\"delta\":{\"reasoning\":\". Ensure no extra spaces or new\"},\"index\":0}],\"created\":1787702127,\"model\":\"gpt-oss-120b\",\"system_fingerprint\":\"fp_e2cabf4999eb0aead3d1\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-cd45cd6e-886a-433e-8ba9-caca78c1f13a\",\"choices\":[{\"delta\":{\"reasoning\":\"lines? Probably just \\\"Hello!\\\".\"},\"index\":0}],\"created\":1787702127,\"model\":\"gpt-oss-120b\",\"system_fingerprint\":\"fp_e2cabf4999eb0aead3d1\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-cd45cd6e-886a-433e-8ba9-caca78c1f13a\",\"choices\":[{\"delta\":{\"reasoning\":\" Usually we output exactly that.\"},\"index\":0}],\"created\":1787702127,\"model\":\"gpt-oss-120b\",\"system_fingerprint\":\"fp_e2cabf4999eb0aead3d1\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-cd45cd6e-886a-433e-8ba9-caca78c1f13a\",\"choices\":[{\"delta\":{\"content\":\"Hello!\"},\"index\":0}],\"created\":1787702127,\"model\":\"gpt-oss-120b\",\"system_fingerprint\":\"fp_e2cabf4999eb0aead3d1\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-cd45cd6e-886a-433e-8ba9-caca78c1f13a\",\"choices\":[{\"delta\":{},\"finish_reason\":\"stop\",\"index\":0}],\"created\":1787702127,\"model\":\"gpt-oss-120b\",\"system_fingerprint\":\"fp_e2cabf4999eb0aead3d1\",\"object\":\"chat.completion.chunk\",\"usage\":{\"total_tokens\":142,\"completion_tokens\":58,\"completion_tokens_details\":{\"accepted_prediction_tokens\":0,\"rejected_prediction_tokens\":0,\"reasoning_tokens\":46},\"prompt_tokens\":84,\"prompt_tokens_details\":{\"cached_tokens\":0}},\"time_info\":{\"created\":1787702127.645281,\"queue_time\":0.003817115,\"prompt_time\":0.001587193,\"completion_time\":0.029805929,\"total_time\":0.036823272705078125}}\n\ndata: [DONE]\n\n"
}
}
]
}
@@ -0,0 +1,32 @@
{
"version": 1,
"metadata": {
"provider": "cerebras",
"route": "cerebras-chat",
"transport": "http",
"model": "gpt-oss-120b",
"tags": ["prefix:cerebras-chat", "provider:cerebras", "tool", "tool-call", "golden"],
"name": "cerebras-chat/cerebras-gpt-oss-120b-tool-call",
"recordedAt": "2026-08-25T23:55:28.454Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.cerebras.ai/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"gpt-oss-120b\",\"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\":512}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "data: {\"id\":\"chatcmpl-402a09cc-8668-446d-8f9c-3e4121f5fa51\",\"choices\":[{\"delta\":{\"role\":\"assistant\"},\"index\":0}],\"created\":1787702127,\"model\":\"gpt-oss-120b\",\"system_fingerprint\":\"fp_4cfabdd6620dc0120785\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-402a09cc-8668-446d-8f9c-3e4121f5fa51\",\"choices\":[{\"delta\":{\"reasoning\":\"We\"},\"index\":0}],\"created\":1787702127,\"model\":\"gpt-oss-120b\",\"system_fingerprint\":\"fp_4cfabdd6620dc0120785\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-402a09cc-8668-446d-8f9c-3e4121f5fa51\",\"choices\":[{\"delta\":{\"reasoning\":\" need to call the function get_weather with city \\\"\"},\"index\":0}],\"created\":1787702127,\"model\":\"gpt-oss-120b\",\"system_fingerprint\":\"fp_4cfabdd6620dc0120785\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-402a09cc-8668-446d-8f9c-3e4121f5fa51\",\"choices\":[{\"delta\":{\"reasoning\":\"Paris\\\".\"},\"index\":0}],\"created\":1787702127,\"model\":\"gpt-oss-120b\",\"system_fingerprint\":\"fp_4cfabdd6620dc0120785\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-402a09cc-8668-446d-8f9c-3e4121f5fa51\",\"choices\":[{\"delta\":{\"tool_calls\":[{\"function\":{\"name\":\"get_weather\",\"arguments\":\"\"},\"type\":\"function\",\"id\":\"3d860cefe\",\"index\":0}]},\"index\":0}],\"created\":1787702127,\"model\":\"gpt-oss-120b\",\"system_fingerprint\":\"fp_4cfabdd6620dc0120785\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-402a09cc-8668-446d-8f9c-3e4121f5fa51\",\"choices\":[{\"delta\":{\"tool_calls\":[{\"function\":{\"arguments\":\"{\\\"city\\\":\\\"Paris\\\"}\"},\"type\":\"function\",\"index\":0}]},\"index\":0}],\"created\":1787702127,\"model\":\"gpt-oss-120b\",\"system_fingerprint\":\"fp_4cfabdd6620dc0120785\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-402a09cc-8668-446d-8f9c-3e4121f5fa51\",\"choices\":[{\"delta\":{},\"finish_reason\":\"tool_calls\",\"index\":0}],\"created\":1787702127,\"model\":\"gpt-oss-120b\",\"system_fingerprint\":\"fp_4cfabdd6620dc0120785\",\"object\":\"chat.completion.chunk\",\"usage\":{\"total_tokens\":174,\"completion_tokens\":37,\"completion_tokens_details\":{\"accepted_prediction_tokens\":0,\"rejected_prediction_tokens\":0,\"reasoning_tokens\":13},\"prompt_tokens\":137,\"prompt_tokens_details\":{\"cached_tokens\":0}},\"time_info\":{\"created\":1787702127.8019717,\"queue_time\":0.31196235,\"prompt_time\":0.005234764,\"completion_time\":0.020198402,\"total_time\":0.702225923538208}}\n\ndata: [DONE]\n\n"
}
}
]
}
File diff suppressed because one or more lines are too long
@@ -26,6 +26,8 @@ describe("provider package entrypoints", () => {
import("@opencode-ai/ai/providers/amazon-bedrock/mantle"),
import("@opencode-ai/ai/providers/amazon-bedrock/mantle/chat"),
import("@opencode-ai/ai/providers/amazon-bedrock/mantle/responses"),
import("@opencode-ai/ai/providers/togetherai"),
import("@opencode-ai/ai/providers/cerebras"),
])
for (const module of modules) expect(module.model).toBeFunction()
@@ -1,5 +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 { CloudflareAIGateway, CloudflareWorkersAI } from "../../src/providers/cloudflare.js"
import * as Google from "../../src/providers/google.js"
import * as OpenAI from "../../src/providers/openai.js"
@@ -47,11 +48,10 @@ const cloudflareWorkersAITools = cloudflareWorkers.model("@cf/openai/gpt-oss-20b
const deepseek = OpenAICompatible.deepseek
.configure({ apiKey: process.env.DEEPSEEK_API_KEY ?? "fixture" })
.model("deepseek-chat")
const together = OpenAICompatible.togetherai
.configure({
apiKey: process.env.TOGETHER_AI_API_KEY ?? "fixture",
})
.model("meta-llama/Llama-3.3-70B-Instruct-Turbo")
const together = TogetherAI.configure({
apiKey: process.env.TOGETHER_API_KEY ?? process.env.TOGETHER_AI_API_KEY ?? "fixture",
}).model("meta-llama/Llama-3.3-70B-Instruct-Turbo")
const cerebras = Cerebras.configure({ apiKey: process.env.CEREBRAS_API_KEY ?? "fixture" }).model("gpt-oss-120b")
const groq = OpenAICompatible.groq
.configure({ apiKey: process.env.GROQ_API_KEY ?? "fixture" })
.model("llama-3.3-70b-versatile")
@@ -193,8 +193,27 @@ describeRecordedGoldenScenarios([
name: "TogetherAI Llama 3.3 70B",
prefix: "openai-compatible-chat",
model: together,
requires: ["TOGETHER_AI_API_KEY"],
scenarios: ["text", "tool-call"],
requires: ["TOGETHER_API_KEY"],
scenarios: [
{
id: "text",
cassette: "openai-compatible-chat/togetherai-streams-text",
prompt: "Reply with exactly: Hello!",
maxTokens: 20,
},
{ id: "tool-call", cassette: "openai-compatible-chat/togetherai-streams-tool-call" },
],
},
{
name: "Cerebras GPT OSS 120B",
prefix: "cerebras-chat",
model: cerebras,
requires: ["CEREBRAS_API_KEY"],
scenarios: [
{ id: "text", maxTokens: 256, temperature: false },
{ id: "tool-call", maxTokens: 512, temperature: false },
{ id: "tool-loop", maxTokens: 512, temperature: false, timeout: 30_000 },
],
},
{
name: "Groq Llama 3.3 70B",
@@ -0,0 +1,151 @@
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 { compileRequest } from "../../src/route/client.js"
import { it } from "../lib/effect.js"
import { dynamicResponse } from "../lib/http.js"
import { sseEvents } from "../lib/sse.js"
describe("native OpenAI-compatible providers", () => {
it.effect("preserves native Together AI and Cerebras provider and route identities", () =>
Effect.gen(function* () {
const together = TogetherAI.configure({ apiKey: "fixture" }).model("meta-llama/Llama-3.3-70B")
const cerebras = Cerebras.configure({ apiKey: "fixture" }).model("qwen-3-235b-a22b")
expect(together).toMatchObject({
provider: "togetherai",
compatibility: { maxTokensField: "max_tokens", supportsStore: false, supportsStrictMode: false },
route: { id: "togetherai-chat", protocol: "openai-chat" },
})
expect(together.route.endpoint.baseURL).toBe("https://api.together.xyz/v1")
expect(cerebras).toMatchObject({
provider: "cerebras",
compatibility: { maxTokensField: "max_tokens", reasoningField: "reasoning", supportsStore: false },
route: { id: "cerebras-chat", protocol: "openai-chat" },
})
expect(cerebras.route.endpoint.baseURL).toBe("https://api.cerebras.ai/v1")
}),
)
it.effect("applies native provider request defaults even with a custom gateway URL", () =>
Effect.gen(function* () {
const together = yield* compileRequest(
LLM.request({
model: TogetherAI.configure({ apiKey: "fixture", baseURL: "https://gateway.example/v1" }).model("llama"),
prompt: "Use a tool.",
generation: { maxTokens: 32 },
tools: [
ToolDefinition.make({ name: "lookup", description: "Look up data", inputSchema: { type: "object" } }),
],
providerOptions: { store: true },
}),
)
expect(together.body).toMatchObject({
max_tokens: 32,
stream_options: { include_usage: true },
tools: [{ function: { name: "lookup" } }],
})
expect(together.body).not.toHaveProperty("max_completion_tokens")
expect(together.body).not.toHaveProperty("store")
expect(together.body.tools?.[0]?.function).not.toHaveProperty("strict")
const cerebras = yield* compileRequest(
LLM.request({
model: Cerebras.configure({ apiKey: "fixture", baseURL: "https://gateway.example/v1" }).model("qwen"),
generation: { maxTokens: 48 },
messages: [
Message.user("Think first."),
Message.assistant([
{ type: "reasoning", text: "A deliberate thought." },
{ type: "text", text: "An answer." },
]),
Message.user("Continue."),
],
providerOptions: { store: true },
}),
)
expect(cerebras.body).toMatchObject({
max_tokens: 48,
messages: [
{ role: "user", content: "Think first." },
{ role: "assistant", content: "An answer.", reasoning: "A deliberate thought." },
{ role: "user", content: "Continue." },
],
})
expect(cerebras.body).not.toHaveProperty("max_completion_tokens")
expect(cerebras.body).not.toHaveProperty("store")
expect(cerebras.body.messages[1]).not.toHaveProperty("reasoning_content")
}),
)
it.effect("maps package settings onto native executable models", () =>
Effect.gen(function* () {
for (const native of [TogetherAI, Cerebras]) {
const selected = native.model("provider-model", {
apiKey: "fixture",
baseURL: "https://gateway.example/v1",
headers: { "x-application": "opencode" },
body: { service_tier: "priority" },
providerOptions: { reasoningEffort: "high" },
})
expect(selected.route.endpoint.baseURL).toBe("https://gateway.example/v1")
expect(selected.route.defaults.headers).toEqual({ "x-application": "opencode" })
expect(selected.route.defaults.http?.body).toEqual({ service_tier: "priority" })
expect(selected.route.defaults.providerOptions).toEqual({ reasoningEffort: "high" })
}
}),
)
it.effect("resolves provider environment credentials and preserves deprecated Together credentials", () =>
Effect.gen(function* () {
const scenarios = [
{
model: TogetherAI.configure().model("llama"),
env: { TOGETHER_API_KEY: "together-primary", TOGETHER_AI_API_KEY: "together-legacy" },
token: "together-primary",
url: "https://api.together.xyz/v1/chat/completions",
},
{
model: TogetherAI.configure().model("llama"),
env: { TOGETHER_AI_API_KEY: "together-legacy" },
token: "together-legacy",
url: "https://api.together.xyz/v1/chat/completions",
},
{
model: Cerebras.configure().model("qwen"),
env: { CEREBRAS_API_KEY: "cerebras-secret" },
token: "cerebras-secret",
url: "https://api.cerebras.ai/v1/chat/completions",
},
]
yield* Effect.forEach(scenarios, (scenario) =>
LLM.generate(LLM.request({ model: scenario.model, prompt: "Say hello." })).pipe(
Effect.provide(
dynamicResponse((input) =>
Effect.gen(function* () {
const request = yield* HttpClientRequest.toWeb(input.request).pipe(Effect.orDie)
expect(request.url).toBe(scenario.url)
expect(request.headers.get("authorization")).toBe(`Bearer ${scenario.token}`)
return input.respond(
sseEvents(
{ id: "chatcmpl_fixture", choices: [{ delta: { content: "Hello" }, finish_reason: null }] },
{ id: "chatcmpl_fixture", choices: [{ delta: {}, finish_reason: "stop" }] },
),
{ headers: { "content-type": "text/event-stream" } },
)
}),
),
),
Effect.provide(ConfigProvider.layer(ConfigProvider.fromEnv({ env: scenario.env }))),
Effect.tap((response) => Effect.sync(() => expect(response.text).toBe("Hello"))),
),
)
}),
)
})
+2
View File
@@ -20,6 +20,7 @@ type ScenarioInput =
readonly name?: string
readonly cassette?: string
readonly tags?: ReadonlyArray<string>
readonly prompt?: string
readonly maxTokens?: number
readonly temperature?: number | false
readonly timeout?: number
@@ -87,6 +88,7 @@ const runTarget = (target: TargetInput) => {
yield* runGoldenScenario(input.id, {
id: `recorded_${kebab(target.name).replaceAll("-", "_")}_${input.id.replaceAll("-", "_")}`,
model: target.model,
prompt: input.prompt,
maxTokens: input.maxTokens,
temperature: input.temperature,
})
+2 -1
View File
@@ -164,6 +164,7 @@ export const expectGoldenWeatherToolLoop = (events: ReadonlyArray<LLMEvent>) =>
export interface GoldenScenarioContext {
readonly id: string
readonly model: LanguageModel
readonly prompt?: string
readonly maxTokens?: number
readonly temperature?: number | false
}
@@ -298,7 +299,7 @@ const runGeneratedConversation = (context: GoldenScenarioContext, steps: Readonl
const runTextScenario = (context: GoldenScenarioContext) =>
runGeneratedConversation(context, [
user("Reply exactly with: Hello!"),
user(context.prompt ?? "Reply exactly with: Hello!"),
assistant.expectText(/^Hello!?$/, {
system: "You are concise.",
maxTokens: context.maxTokens ?? 40,
-2
View File
@@ -102,7 +102,6 @@
"@ai-sdk/amazon-bedrock": "4.0.112",
"@ai-sdk/anthropic": "3.0.82",
"@ai-sdk/azure": "3.0.88",
"@ai-sdk/cerebras": "2.0.41",
"@ai-sdk/cohere": "3.0.27",
"@ai-sdk/deepinfra": "2.0.41",
"@ai-sdk/gateway": "3.0.104",
@@ -113,7 +112,6 @@
"@ai-sdk/perplexity": "3.0.26",
"@ai-sdk/provider": "3.0.8",
"@ai-sdk/provider-utils": "4.0.23",
"@ai-sdk/togetherai": "2.0.41",
"@ai-sdk/vercel": "2.0.39",
"@aws-sdk/credential-providers": "3.1057.0",
"@lydell/node-pty": "catalog:",
+11
View File
@@ -53,6 +53,17 @@ export function map(input: MapInput): Mapping | undefined {
...mapOpenAIOptions(input.settings),
},
}
case "@ai-sdk/cerebras":
case "@ai-sdk/togetherai":
return {
package: `@opencode-ai/ai/providers/${input.packageName === "@ai-sdk/cerebras" ? "cerebras" : "togetherai"}`,
settings: {
...baseSettings,
...mapAPIKey(input.settings),
...mapProviderOptions(input.settings, ["apiKey", "baseURL", "fetch", "headers", "name"]),
},
...(isStringRecord(input.settings.headers) ? { headers: input.settings.headers } : {}),
}
case "@ai-sdk/google":
return {
package: "@opencode-ai/ai/providers/google",
+4
View File
@@ -333,8 +333,10 @@ function usesAPIKeyAuth(packageName: string | undefined) {
return (
name === "@ai-sdk/openai" ||
name === "@ai-sdk/anthropic" ||
name === "@ai-sdk/cerebras" ||
name === "@ai-sdk/openai-compatible" ||
name === "@ai-sdk/google" ||
name === "@ai-sdk/togetherai" ||
name === "@ai-sdk/xai" ||
name === "@openrouter/ai-sdk-provider" ||
name === "@ai-sdk/azure" ||
@@ -342,8 +344,10 @@ function usesAPIKeyAuth(packageName: string | undefined) {
name?.startsWith("@opencode-ai/ai/providers/openai/") === true ||
name === "@opencode-ai/ai/providers/anthropic" ||
name === "@opencode-ai/ai/providers/anthropic-compatible" ||
name === "@opencode-ai/ai/providers/cerebras" ||
name === "@opencode-ai/ai/providers/openai-compatible" ||
name === "@opencode-ai/ai/providers/google" ||
name === "@opencode-ai/ai/providers/togetherai" ||
name === "@opencode-ai/ai/providers/xai" ||
name === "@opencode-ai/ai/providers/openrouter" ||
name === "@opencode-ai/ai/providers/azure" ||
-2
View File
@@ -26,7 +26,6 @@ import { OpencodePlugin } from "./provider/opencode.js"
import { OpenRouterPlugin } from "./provider/openrouter.js"
import { PerplexityPlugin } from "./provider/perplexity.js"
import { SapAICorePlugin } from "./provider/sap-ai-core.js"
import { TogetherAIPlugin } from "./provider/togetherai.js"
import { VercelPlugin } from "./provider/vercel.js"
import { VenicePlugin } from "./provider/venice.js"
import { VLLMPlugin } from "./provider/vllm.js"
@@ -62,7 +61,6 @@ export const ProviderPlugins: PluginInternal.InternalPlugin[] = [
OpenRouterPlugin,
PerplexityPlugin,
SapAICorePlugin,
TogetherAIPlugin,
VercelPlugin,
VenicePlugin,
VLLMPlugin,
+2 -10
View File
@@ -7,20 +7,12 @@ export const CerebrasPlugin = define({
effect: Effect.fn(function* (ctx) {
yield* ctx.catalog.transform((evt) => {
for (const item of evt.provider.list()) {
if (!Provider.isAISDK(item.provider.package)) continue
if (Provider.packageName(item.provider.package) !== "@ai-sdk/cerebras") continue
const name = Provider.packageName(item.provider.package)
if (name !== "@ai-sdk/cerebras" && name !== "@opencode-ai/ai/providers/cerebras") continue
evt.provider.update(item.provider.id, (provider) => {
provider.headers = { ...provider.headers, "X-Cerebras-3rd-Party-Integration": "opencode" }
})
}
})
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/cerebras") return
const mod = yield* Effect.promise(() => import("@ai-sdk/cerebras"))
evt.sdk = mod.createCerebras(evt.options)
}),
)
}),
})
@@ -1,10 +0,0 @@
import { createProviderPlugin } from "./factory.js"
export const TogetherAIPlugin = createProviderPlugin({
id: "opencode.provider.togetherai",
package: "@ai-sdk/togetherai",
load: async (options) => {
const { createTogetherAI } = await import("@ai-sdk/togetherai")
return createTogetherAI(options)
},
})
+2
View File
@@ -48,6 +48,7 @@ const builtins = new Map<string, () => Promise<unknown>>([
["@opencode-ai/ai/providers/azure", () => import("@opencode-ai/ai/providers/azure")],
["@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/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")],
@@ -65,6 +66,7 @@ const builtins = new Map<string, () => Promise<unknown>>([
["@opencode-ai/ai/providers/openai/responses", () => import("@opencode-ai/ai/providers/openai/responses")],
["@opencode-ai/ai/providers/openai-compatible", () => import("@opencode-ai/ai/providers/openai-compatible")],
["@opencode-ai/ai/providers/openrouter", () => import("@opencode-ai/ai/providers/openrouter")],
["@opencode-ai/ai/providers/togetherai", () => import("@opencode-ai/ai/providers/togetherai")],
["@opencode-ai/ai/providers/xai", () => import("@opencode-ai/ai/providers/xai")],
])
+27
View File
@@ -61,6 +61,33 @@ describe("AISDKNative", () => {
})
})
test("maps Cerebras and Together AI settings, headers, and reasoning options to native providers", () => {
for (const name of ["cerebras", "togetherai"]) {
expect(
map(`@ai-sdk/${name}`, {
apiKey: "secret",
baseURL: `https://${name}.example/v1`,
headers: { "x-provider": name },
name: "custom-provider",
reasoningEffort: "high",
customOption: { enabled: true },
}),
).toEqual({
package: `@opencode-ai/ai/providers/${name}`,
settings: {
apiKey: "secret",
baseURL: `https://${name}.example/v1`,
providerOptions: { reasoningEffort: "high", customOption: { enabled: true } },
},
headers: { "x-provider": name },
})
expect(map(`@ai-sdk/${name}`, {})).toEqual({
package: `@opencode-ai/ai/providers/${name}`,
settings: {},
})
}
})
test("maps Google Vertex settings to the native provider", () => {
expect(
map("@ai-sdk/google-vertex", {
+26
View File
@@ -879,6 +879,12 @@ describe("ModelResolver", () => {
{ thinking: { type: "adaptive", display: "summarized" }, effort: "high" },
{ thinking: { type: "adaptive", display: "summarized" }, effort: "high" },
],
[
"@ai-sdk/cerebras",
"@opencode-ai/ai/providers/cerebras",
{ reasoningEffort: "high" },
{ reasoningEffort: "high" },
],
[
"@ai-sdk/openai-compatible",
"@opencode-ai/ai/providers/openai-compatible",
@@ -903,6 +909,12 @@ describe("ModelResolver", () => {
{ reasoning: { effort: "high" } },
{ reasoning: { effort: "high" } },
],
[
"@ai-sdk/togetherai",
"@opencode-ai/ai/providers/togetherai",
{ reasoningEffort: "high" },
{ reasoningEffort: "high" },
],
["@ai-sdk/xai", "@opencode-ai/ai/providers/xai", { reasoningEffort: "high" }, { reasoningEffort: "high" }],
] as const
@@ -950,12 +962,14 @@ describe("ModelResolver", () => {
"openai.gpt-oss-120b",
],
["@ai-sdk/azure", "@opencode-ai/ai/providers/azure/responses", "api-model"],
["@ai-sdk/cerebras", "@opencode-ai/ai/providers/cerebras", "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"],
["@ai-sdk/openai", "@opencode-ai/ai/providers/openai", "api-model"],
["@ai-sdk/openai-compatible", "@opencode-ai/ai/providers/openai-compatible", "api-model"],
["@openrouter/ai-sdk-provider", "@opencode-ai/ai/providers/openrouter", "api-model"],
["@ai-sdk/togetherai", "@opencode-ai/ai/providers/togetherai", "api-model"],
["@ai-sdk/xai", "@opencode-ai/ai/providers/xai", "api-model"],
] as const
@@ -1070,6 +1084,12 @@ describe("ModelResolver", () => {
settings: { reasoning: { effort: "high" } },
}),
)
const cerebras = yield* ModelResolver.fromCatalogModel(
model(Provider.aisdk("@ai-sdk/cerebras"), { settings: { reasoningEffort: "high" } }),
)
const togetherai = yield* ModelResolver.fromCatalogModel(
model(Provider.aisdk("@ai-sdk/togetherai"), { settings: { reasoningEffort: "high" } }),
)
const xai = yield* ModelResolver.fromCatalogModel(
model(Provider.aisdk("@ai-sdk/xai"), { settings: { reasoningEffort: "high" } }),
)
@@ -1090,6 +1110,12 @@ describe("ModelResolver", () => {
expect(google.route.defaults.providerOptions).toEqual({ thinkingConfig: { thinkingBudget: 1_024 } })
expect(openrouter.route.id).toBe("openrouter")
expect(openrouter.route.defaults.providerOptions).toEqual({ reasoning: { effort: "high" } })
expect(cerebras.route.id).toBe("cerebras-chat")
expect(cerebras.route.defaults.providerOptions).toEqual({ reasoningEffort: "high" })
expect(String(cerebras.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")
expect(xai.route.id).toBe("openai-responses")
expect(xai.route.defaults.providerOptions).toEqual({
reasoningEffort: "high",
@@ -1,8 +1,6 @@
import { AISDK } from "@opencode-ai/core/aisdk"
import { describe, expect, mock } from "bun:test"
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { CerebrasPlugin } from "@opencode-ai/core/plugin/provider/cerebras"
@@ -10,26 +8,14 @@ import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const cerebrasOptions: Record<string, unknown>[] = []
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* CerebrasPlugin.effect(host)
})
void mock.module("@ai-sdk/cerebras", () => ({
createCerebras: (options: Record<string, unknown>) => {
const snapshot = { ...options }
cerebrasOptions.push(snapshot)
return {
languageModel: (modelID: string) => ({ modelID, provider: snapshot.name, specificationVersion: "v3" }),
}
},
}))
describe("CerebrasPlugin", () => {
it.effect("applies the legacy integration header", () =>
Effect.gen(function* () {
@@ -57,62 +43,21 @@ describe("CerebrasPlugin", () => {
}),
)
it.effect("creates a bundled Cerebras SDK with the model provider ID as the SDK name", () =>
it.effect("applies the integration header to custom native Cerebras providers", () =>
Effect.gen(function* () {
cerebrasOptions.length = 0
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom-cerebras"), Model.ID.make("llama-4-scout-17b-16e-instruct")),
modelID: Model.ID.make("llama-4-scout-17b-16e-instruct"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/cerebras",
options: { name: "custom-cerebras", apiKey: "test" },
const catalog = yield* Catalog.Service
const providerID = Provider.ID.make("custom-cerebras")
yield* catalog.transform((catalog) => {
catalog.provider.update(providerID, (item) => {
item.package = "@opencode-ai/ai/providers/cerebras"
item.headers = { Existing: "1" }
})
})
expect(cerebrasOptions).toEqual([{ name: "custom-cerebras", apiKey: "test" }])
expect(result.sdk.languageModel("llama-4-scout-17b-16e-instruct").provider).toBe("custom-cerebras")
}),
)
it.effect("preserves an explicit bundled Cerebras SDK name option", () =>
Effect.gen(function* () {
cerebrasOptions.length = 0
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom-cerebras"), Model.ID.make("llama-4-scout-17b-16e-instruct")),
modelID: Model.ID.make("llama-4-scout-17b-16e-instruct"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/cerebras",
options: { name: "configured-cerebras", apiKey: "test" },
expect((yield* catalog.provider.get(providerID))?.headers).toEqual({
Existing: "1",
"X-Cerebras-3rd-Party-Integration": "opencode",
})
expect(cerebrasOptions).toEqual([{ name: "configured-cerebras", apiKey: "test" }])
}),
)
it.effect("ignores non-Cerebras SDK packages", () =>
Effect.gen(function* () {
cerebrasOptions.length = 0
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom-cerebras"), Model.ID.make("llama-4-scout-17b-16e-instruct")),
modelID: Model.ID.make("llama-4-scout-17b-16e-instruct"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/groq",
options: { name: "custom-cerebras", apiKey: "test" },
})
expect(cerebrasOptions).toEqual([])
expect(result.sdk).toBeUndefined()
}),
)
})
@@ -11,7 +11,6 @@ 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"
import { PerplexityPlugin } from "@opencode-ai/core/plugin/provider/perplexity"
import { TogetherAIPlugin } from "@opencode-ai/core/plugin/provider/togetherai"
import { VenicePlugin } from "@opencode-ai/core/plugin/provider/venice"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
@@ -27,7 +26,6 @@ const providers = [
{ id: "groq", plugin: GroqPlugin, package: "@ai-sdk/groq", provider: "groq.chat" },
{ id: "mistral", plugin: MistralPlugin, package: "@ai-sdk/mistral", provider: "mistral.chat" },
{ id: "perplexity", plugin: PerplexityPlugin, package: "@ai-sdk/perplexity", provider: "perplexity" },
{ id: "togetherai", plugin: TogetherAIPlugin, package: "@ai-sdk/togetherai", provider: "togetherai.chat" },
{ id: "venice", plugin: VenicePlugin, package: "venice-ai-sdk-provider", provider: "custom-provider.chat" },
] as const
+3 -1
View File
@@ -3,13 +3,15 @@ import { Effect } from "effect"
import { Provider } from "@opencode-ai/core/provider"
describe("Provider", () => {
test("loads Vertex native provider entrypoints", async () => {
test("loads bundled native provider entrypoints", async () => {
const packages = [
"@opencode-ai/ai/providers/cerebras",
"@opencode-ai/ai/providers/google-vertex",
"@opencode-ai/ai/providers/google-vertex/gemini",
"@opencode-ai/ai/providers/google-vertex/chat",
"@opencode-ai/ai/providers/google-vertex/responses",
"@opencode-ai/ai/providers/google-vertex/messages",
"@opencode-ai/ai/providers/togetherai",
]
for (const specifier of packages) {