feat(core): flatten provider config and load native packages (#35563)
Co-authored-by: Dax Raad <d@ironbay.co>
This commit is contained in:
parent
d603eed5a1
commit
e57d9ca390
107 changed files with 2593 additions and 1984 deletions
|
|
@ -72,7 +72,7 @@ function mergeCost(base: ModelV2Info["cost"], override: ModelsDev.Model["cost"]
|
|||
|
||||
const OPENAI_INCLUDE_ENCRYPTED_REASONING = ["reasoning.encrypted_content"]
|
||||
|
||||
function reasoningVariants(provider: ModelsDev.Provider, model: ModelsDev.Model): ModelV2Info["variants"] {
|
||||
function reasoningVariants(provider: ModelsDev.Provider, model: ModelsDev.Model): NonNullable<ModelV2Info["variants"]> {
|
||||
const npm = model.provider?.npm ?? provider.npm
|
||||
const options = model.reasoning_options ?? []
|
||||
const effort = options.find((option) => option.type === "effort")
|
||||
|
|
@ -82,7 +82,7 @@ function reasoningVariants(provider: ModelsDev.Provider, model: ModelsDev.Model)
|
|||
const id = raw === null ? "none" : typeof raw === "string" ? raw : undefined
|
||||
if (id === undefined) return []
|
||||
const settings = settingsForEffort(npm, id)
|
||||
return settings ? [{ id, settings, headers: {}, body: {} }] : []
|
||||
return settings ? [{ id: ModelV2.VariantID.make(id), settings }] : []
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -117,15 +117,18 @@ function settingsForEffort(npm: string | undefined, effort: string): ProviderV2.
|
|||
function budgetVariants(
|
||||
npm: string | undefined,
|
||||
option: Extract<NonNullable<ModelsDev.Model["reasoning_options"]>[number], { type: "budget_tokens" }>,
|
||||
): ModelV2Info["variants"] {
|
||||
): NonNullable<ModelV2Info["variants"]> {
|
||||
const max = option.max
|
||||
const high = option.max === undefined ? Math.max(option.min ?? 0, 16_000) : Math.min(Math.max(option.min ?? 0, 16_000), option.max)
|
||||
const high =
|
||||
option.max === undefined
|
||||
? Math.max(option.min ?? 0, 16_000)
|
||||
: Math.min(Math.max(option.min ?? 0, 16_000), option.max)
|
||||
return [
|
||||
{ id: "high", budget: high },
|
||||
...(max === undefined || max === high ? [] : [{ id: "max", budget: max }]),
|
||||
].flatMap((item) => {
|
||||
const settings = settingsForBudget(npm, item.budget)
|
||||
return settings ? [{ id: item.id, settings, headers: {}, body: {} }] : []
|
||||
return settings ? [{ id: ModelV2.VariantID.make(item.id), settings }] : []
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -143,12 +146,13 @@ function modeName(model: ModelsDev.Model, mode: string) {
|
|||
return `${model.name} ${mode.charAt(0).toUpperCase()}${mode.slice(1)}`
|
||||
}
|
||||
|
||||
function mergeVariants(model: ModelV2Info, next: ModelV2Info["variants"]) {
|
||||
const existing = new Map(model.variants.map((variant) => [variant.id, variant]))
|
||||
function mergeVariants(model: ModelV2Info, next: NonNullable<ModelV2Info["variants"]>) {
|
||||
const variants = model.variants ?? []
|
||||
const existing = new Map(variants.map((variant) => [variant.id, variant]))
|
||||
const nextIDs = new Set(next.map((variant) => variant.id))
|
||||
model.variants = [
|
||||
...next.map((variant) => existing.get(variant.id) ?? variant),
|
||||
...model.variants.filter((variant) => !nextIDs.has(variant.id)),
|
||||
...variants.filter((variant) => !nextIDs.has(variant.id)),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -159,24 +163,14 @@ function applyModel(
|
|||
readonly name?: string
|
||||
readonly cost?: ModelV2Info["cost"]
|
||||
readonly request?: NonNullable<NonNullable<ModelsDev.Model["experimental"]>["modes"]>[string]["provider"]
|
||||
readonly variants?: ModelV2Info["variants"]
|
||||
readonly variants?: NonNullable<ModelV2Info["variants"]>
|
||||
} = {},
|
||||
) {
|
||||
draft.name = input.name ?? model.name
|
||||
draft.modelID = model.id
|
||||
draft.family = model.family ? ModelV2.Family.make(model.family) : undefined
|
||||
draft.api = model.provider?.npm
|
||||
? {
|
||||
id: ModelV2.ID.make(model.id),
|
||||
type: "aisdk",
|
||||
package: model.provider.npm,
|
||||
url: model.provider.api,
|
||||
}
|
||||
: {
|
||||
id: ModelV2.ID.make(model.id),
|
||||
type: "native",
|
||||
url: model.provider?.api,
|
||||
settings: {},
|
||||
}
|
||||
draft.package = model.provider?.npm ? ProviderV2.aisdk(model.provider.npm) : undefined
|
||||
draft.settings = model.provider?.api ? { ...draft.settings, baseURL: model.provider.api } : draft.settings
|
||||
draft.capabilities = {
|
||||
tools: model.tool_call,
|
||||
input: [...(model.modalities?.input ?? [])],
|
||||
|
|
@ -184,7 +178,11 @@ function applyModel(
|
|||
}
|
||||
mergeVariants(draft, input.variants ?? [])
|
||||
draft.time.released = released(model.release_date)
|
||||
draft.cost = input.cost ?? cost(model.cost)
|
||||
draft.cost = (input.cost ?? cost(model.cost)).map((item) => ({
|
||||
...item,
|
||||
tier: item.tier && { ...item.tier },
|
||||
cache: { ...item.cache },
|
||||
}))
|
||||
draft.status = model.status ?? "active"
|
||||
draft.enabled = true
|
||||
draft.limit = {
|
||||
|
|
@ -192,8 +190,8 @@ function applyModel(
|
|||
input: model.limit.input,
|
||||
output: model.limit.output,
|
||||
}
|
||||
Object.assign(draft.request.headers, input.request?.headers ?? {})
|
||||
Object.assign(draft.request.body, input.request?.body ?? {})
|
||||
draft.headers = { ...draft.headers, ...input.request?.headers }
|
||||
draft.body = { ...draft.body, ...input.request?.body }
|
||||
}
|
||||
|
||||
export const ModelsDevPlugin = define({
|
||||
|
|
@ -222,25 +220,18 @@ export const ModelsDevPlugin = define({
|
|||
const providerID = ProviderV2.ID.make(item.id)
|
||||
catalog.provider.update(providerID, (provider) => {
|
||||
provider.name = item.name
|
||||
provider.api = item.npm
|
||||
? {
|
||||
type: "aisdk",
|
||||
package: item.npm,
|
||||
url: item.api,
|
||||
}
|
||||
: {
|
||||
type: "native",
|
||||
url: item.api,
|
||||
settings: {},
|
||||
}
|
||||
provider.package = item.npm ? ProviderV2.aisdk(item.npm) : ""
|
||||
provider.settings = item.api ? { ...provider.settings, baseURL: item.api } : provider.settings
|
||||
})
|
||||
|
||||
for (const model of Object.values(item.models)) {
|
||||
const baseCost = cost(model.cost)
|
||||
const variants = reasoningVariants(item, model)
|
||||
catalog.model.update(providerID, model.id, (draft) => applyModel(draft, model, { cost: baseCost, variants }))
|
||||
catalog.model.update(providerID, ModelV2.ID.make(model.id), (draft) =>
|
||||
applyModel(draft, model, { cost: baseCost, variants }),
|
||||
)
|
||||
for (const [mode, options] of Object.entries(model.experimental?.modes ?? {})) {
|
||||
catalog.model.update(providerID, `${model.id}-${mode}`, (draft) =>
|
||||
catalog.model.update(providerID, ModelV2.ID.make(`${model.id}-${mode}`), (draft) =>
|
||||
applyModel(draft, model, {
|
||||
name: modeName(model, mode),
|
||||
cost: mergeCost(baseCost, options.cost),
|
||||
|
|
|
|||
|
|
@ -64,15 +64,14 @@ export const AmazonBedrockPlugin = define({
|
|||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/amazon-bedrock") continue
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/amazon-bedrock") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
if (provider.api.type !== "aisdk") return
|
||||
if (typeof provider.request.body.endpoint !== "string") return
|
||||
if (typeof provider.settings?.endpoint !== "string") return
|
||||
// The AI SDK expects a base URL, but users configure Bedrock private/VPC
|
||||
// endpoints as `endpoint`; move it into the catalog endpoint URL once.
|
||||
provider.api.url = provider.request.body.endpoint
|
||||
delete provider.request.body.endpoint
|
||||
provider.settings.baseURL = provider.settings.endpoint
|
||||
delete provider.settings.endpoint
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
@ -112,12 +111,15 @@ export const AmazonBedrockPlugin = define({
|
|||
yield* ctx.aisdk.language(
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.amazonBedrock) return
|
||||
if (evt.model.api.type === "aisdk" && evt.model.api.package === "@ai-sdk/amazon-bedrock/mantle") {
|
||||
evt.language = selectMantleModel(evt.sdk, evt.model.api.id)
|
||||
if (
|
||||
ProviderV2.isAISDK(evt.model.package) &&
|
||||
ProviderV2.packageName(evt.model.package) === "@ai-sdk/amazon-bedrock/mantle"
|
||||
) {
|
||||
evt.language = selectMantleModel(evt.sdk, evt.model.modelID ?? evt.model.id)
|
||||
return
|
||||
}
|
||||
const region = typeof evt.options.region === "string" ? evt.options.region : process.env.AWS_REGION
|
||||
evt.language = evt.sdk.languageModel(resolveModelID(evt.model.api.id, region))
|
||||
evt.language = evt.sdk.languageModel(resolveModelID(evt.model.modelID ?? evt.model.id, region))
|
||||
}),
|
||||
)
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
|
||||
export const AnthropicPlugin = define({
|
||||
id: "opencode.provider.anthropic",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/anthropic") continue
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/anthropic") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.request.headers["anthropic-beta"] =
|
||||
"interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14"
|
||||
provider.headers = {
|
||||
...provider.headers,
|
||||
"anthropic-beta": "interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -15,14 +15,14 @@ export const AzurePlugin = define({
|
|||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/azure") continue
|
||||
const configured = item.provider.request.body.resourceName
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/azure") continue
|
||||
const configured = item.provider.settings?.resourceName
|
||||
const resourceName =
|
||||
typeof configured === "string" && configured.trim() !== "" ? configured : process.env.AZURE_RESOURCE_NAME
|
||||
if (!resourceName) continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.request.body.resourceName = resourceName
|
||||
provider.settings = { ...provider.settings, resourceName }
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
@ -33,7 +33,7 @@ export const AzurePlugin = define({
|
|||
if (
|
||||
!evt.options.resourceName &&
|
||||
!evt.options.baseURL &&
|
||||
(evt.model.api.type !== "aisdk" || !evt.model.api.url)
|
||||
(!ProviderV2.isAISDK(evt.model.package) || typeof evt.model.settings?.baseURL !== "string")
|
||||
) {
|
||||
throw new Error(
|
||||
"AZURE_RESOURCE_NAME is missing, set it using env var or reconnecting the azure provider and setting it",
|
||||
|
|
@ -47,7 +47,11 @@ export const AzurePlugin = define({
|
|||
yield* ctx.aisdk.language(
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.azure) return
|
||||
evt.language = selectLanguage(evt.sdk, evt.model.api.id, Boolean(evt.options.useCompletionUrls))
|
||||
evt.language = selectLanguage(
|
||||
evt.sdk,
|
||||
evt.model.modelID ?? evt.model.id,
|
||||
Boolean(evt.options.useCompletionUrls),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
|
|
@ -60,18 +64,25 @@ export const AzureCognitiveServicesPlugin = define({
|
|||
const resourceName = process.env.AZURE_COGNITIVE_SERVICES_RESOURCE_NAME
|
||||
if (!resourceName) return
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/openai-compatible") continue
|
||||
if (!item.provider.id.includes("azure-cognitive-services")) continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.request.body.baseURL = `https://${resourceName}.cognitiveservices.azure.com/openai`
|
||||
provider.settings = {
|
||||
...provider.settings,
|
||||
baseURL: `https://${resourceName}.cognitiveservices.azure.com/openai`,
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
yield* ctx.aisdk.language(
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.make("azure-cognitive-services")) return
|
||||
evt.language = selectLanguage(evt.sdk, evt.model.api.id, Boolean(evt.options.useCompletionUrls))
|
||||
evt.language = selectLanguage(
|
||||
evt.sdk,
|
||||
evt.model.modelID ?? evt.model.id,
|
||||
Boolean(evt.options.useCompletionUrls),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
|
||||
export const CerebrasPlugin = define({
|
||||
id: "opencode.provider.cerebras",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/cerebras") continue
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/cerebras") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.request.headers["X-Cerebras-3rd-Party-Integration"] = "opencode"
|
||||
provider.headers = { ...provider.headers, "X-Cerebras-3rd-Party-Integration": "opencode" }
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ export const CloudflareWorkersAIPlugin = define({
|
|||
const item = evt.provider.get(providerID)
|
||||
if (!item) return
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
if (provider.api.type !== "aisdk") return
|
||||
if (provider.api.url) return
|
||||
const accountId = resolveAccountId(provider.request.body)
|
||||
if (accountId) provider.api.url = workersEndpoint(accountId)
|
||||
if (!ProviderV2.isAISDK(provider.package)) return
|
||||
if (typeof provider.settings?.baseURL === "string") return
|
||||
const accountId = resolveAccountId(provider.settings ?? {})
|
||||
if (accountId) provider.settings = { ...provider.settings, baseURL: workersEndpoint(accountId) }
|
||||
})
|
||||
})
|
||||
yield* ctx.aisdk.sdk(
|
||||
|
|
@ -25,7 +25,7 @@ export const CloudflareWorkersAIPlugin = define({
|
|||
if (evt.package !== "@ai-sdk/openai-compatible") return
|
||||
|
||||
const accountId = resolveAccountId(evt.options)
|
||||
if (!hasWorkersEndpoint(evt.model.api) && !accountId) return
|
||||
if (!hasWorkersEndpoint(evt.model) && !accountId) return
|
||||
const mod = yield* Effect.promise(() => import("@ai-sdk/openai-compatible"))
|
||||
evt.sdk = mod.createOpenAICompatible(
|
||||
sdkOptions({
|
||||
|
|
@ -38,7 +38,7 @@ export const CloudflareWorkersAIPlugin = define({
|
|||
yield* ctx.aisdk.language(
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== providerID) return
|
||||
evt.language = evt.sdk.languageModel(evt.model.api.id)
|
||||
evt.language = evt.sdk.languageModel(evt.model.modelID ?? evt.model.id)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
|
|
@ -52,8 +52,11 @@ function workersEndpoint(accountId: string) {
|
|||
return `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/v1`
|
||||
}
|
||||
|
||||
function hasWorkersEndpoint(api: ProviderV2.Api) {
|
||||
return api.type === "aisdk" && Boolean(api.url)
|
||||
function hasWorkersEndpoint(model: {
|
||||
readonly package?: string
|
||||
readonly settings?: Readonly<Record<string, unknown>>
|
||||
}) {
|
||||
return ProviderV2.isAISDK(model.package) && typeof model.settings?.baseURL === "string"
|
||||
}
|
||||
|
||||
function sdkOptions(options: Record<string, any>) {
|
||||
|
|
|
|||
|
|
@ -34,12 +34,11 @@ export const GithubCopilotPlugin = define({
|
|||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.githubCopilot) return
|
||||
if (evt.sdk.responses === undefined && evt.sdk.chat === undefined) {
|
||||
evt.language = evt.sdk.languageModel(evt.model.api.id)
|
||||
evt.language = evt.sdk.languageModel(evt.model.modelID ?? evt.model.id)
|
||||
return
|
||||
}
|
||||
evt.language = shouldUseResponses(evt.model.api.id)
|
||||
? evt.sdk.responses(evt.model.api.id)
|
||||
: evt.sdk.chat(evt.model.api.id)
|
||||
const id = evt.model.modelID ?? evt.model.id
|
||||
evt.language = shouldUseResponses(id) ? evt.sdk.responses(id) : evt.sdk.chat(id)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -36,26 +36,24 @@ export const GitLabPlugin = define({
|
|||
if (evt.model.providerID !== ProviderV2.ID.gitlab) return
|
||||
const featureFlags =
|
||||
typeof evt.options.featureFlags === "object" && evt.options.featureFlags ? evt.options.featureFlags : {}
|
||||
if (evt.model.api.id.startsWith("duo-workflow-")) {
|
||||
const id = evt.model.modelID ?? evt.model.id
|
||||
if (id.startsWith("duo-workflow-")) {
|
||||
const gitlab = yield* Effect.promise(() => import("gitlab-ai-provider")).pipe(Effect.orDie)
|
||||
const workflowRef =
|
||||
typeof evt.model.request.body.workflowRef === "string" ? evt.model.request.body.workflowRef : undefined
|
||||
typeof evt.model.settings?.workflowRef === "string" ? evt.model.settings.workflowRef : undefined
|
||||
const workflowDefinition =
|
||||
typeof evt.model.request.body.workflowDefinition === "string"
|
||||
? evt.model.request.body.workflowDefinition
|
||||
typeof evt.model.settings?.workflowDefinition === "string"
|
||||
? evt.model.settings.workflowDefinition
|
||||
: undefined
|
||||
const language = evt.sdk.workflowChat(
|
||||
gitlab.isWorkflowModel(evt.model.api.id) ? evt.model.api.id : "duo-workflow",
|
||||
{
|
||||
featureFlags,
|
||||
workflowDefinition,
|
||||
},
|
||||
)
|
||||
const language = evt.sdk.workflowChat(gitlab.isWorkflowModel(id) ? id : "duo-workflow", {
|
||||
featureFlags,
|
||||
workflowDefinition,
|
||||
})
|
||||
if (workflowRef) language.selectedModelRef = workflowRef
|
||||
evt.language = language
|
||||
return
|
||||
}
|
||||
evt.language = evt.sdk.agenticChat(evt.model.api.id, {
|
||||
evt.language = evt.sdk.agenticChat(id, {
|
||||
aiGatewayHeaders: evt.options.aiGatewayHeaders,
|
||||
featureFlags,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -59,25 +59,28 @@ export const GoogleVertexPlugin = define({
|
|||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (
|
||||
item.provider.api.package !== "@ai-sdk/google-vertex" &&
|
||||
ProviderV2.packageName(item.provider.package) !== "@ai-sdk/google-vertex" &&
|
||||
!(
|
||||
item.provider.id === ProviderV2.ID.googleVertex &&
|
||||
item.provider.api.package.includes("@ai-sdk/openai-compatible")
|
||||
ProviderV2.packageName(item.provider.package)?.includes("@ai-sdk/openai-compatible")
|
||||
)
|
||||
)
|
||||
continue
|
||||
const project = resolveProject(item.provider.request.body)
|
||||
const location = String(resolveLocation(item.provider.request.body))
|
||||
const project = resolveProject(item.provider.settings ?? {})
|
||||
const location = String(resolveLocation(item.provider.settings ?? {}))
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
if (project) provider.request.body.project = project
|
||||
provider.request.body.location = location
|
||||
if (provider.api.type === "aisdk" && provider.api.url) {
|
||||
provider.api.url = replaceVertexVars(provider.api.url, project, location)
|
||||
}
|
||||
if (provider.api.type === "aisdk" && provider.api.package.includes("@ai-sdk/openai-compatible")) {
|
||||
provider.request.body.fetch = authFetch(provider.request.body.fetch)
|
||||
provider.settings = {
|
||||
...provider.settings,
|
||||
...(project ? { project } : {}),
|
||||
location,
|
||||
...(typeof provider.settings?.baseURL === "string"
|
||||
? { baseURL: replaceVertexVars(provider.settings.baseURL, project, location) }
|
||||
: {}),
|
||||
...(ProviderV2.packageName(provider.package)?.includes("@ai-sdk/openai-compatible")
|
||||
? { fetch: authFetch(provider.settings?.fetch) }
|
||||
: {}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -104,7 +107,7 @@ export const GoogleVertexPlugin = define({
|
|||
yield* ctx.aisdk.language(
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.googleVertex) return
|
||||
evt.language = evt.sdk.languageModel(String(evt.model.api.id).trim())
|
||||
evt.language = evt.sdk.languageModel(String(evt.model.modelID ?? evt.model.id).trim())
|
||||
}),
|
||||
)
|
||||
}),
|
||||
|
|
@ -115,21 +118,20 @@ export const GoogleVertexAnthropicPlugin = define({
|
|||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/google-vertex/anthropic") continue
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/google-vertex/anthropic") continue
|
||||
const project =
|
||||
item.provider.request.body.project ??
|
||||
item.provider.settings?.project ??
|
||||
process.env.GOOGLE_CLOUD_PROJECT ??
|
||||
process.env.GCP_PROJECT ??
|
||||
process.env.GCLOUD_PROJECT
|
||||
const location =
|
||||
item.provider.request.body.location ??
|
||||
item.provider.settings?.location ??
|
||||
process.env.GOOGLE_CLOUD_LOCATION ??
|
||||
process.env.VERTEX_LOCATION ??
|
||||
"global"
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
if (project) provider.request.body.project = project
|
||||
provider.request.body.location = location
|
||||
provider.settings = { ...provider.settings, ...(project ? { project } : {}), location }
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
@ -162,7 +164,7 @@ export const GoogleVertexAnthropicPlugin = define({
|
|||
yield* ctx.aisdk.language(
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.make("google-vertex-anthropic")) return
|
||||
evt.language = evt.sdk.languageModel(String(evt.model.api.id).trim())
|
||||
evt.language = evt.sdk.languageModel(String(evt.model.modelID ?? evt.model.id).trim())
|
||||
}),
|
||||
)
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
|
||||
export const KiloPlugin = define({
|
||||
id: "opencode.provider.kilo",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.api.url !== "https://api.kilo.ai/api/gateway") continue
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.settings?.baseURL !== "https://api.kilo.ai/api/gateway") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
|
||||
provider.request.headers["X-Title"] = "opencode"
|
||||
provider.headers = { ...provider.headers, "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode" }
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { Integration } from "../../integration"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
|
||||
export const LLMGatewayPlugin = define({
|
||||
id: "opencode.provider.llmgateway",
|
||||
|
|
@ -10,14 +11,17 @@ export const LLMGatewayPlugin = define({
|
|||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.disabled) continue
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.api.url !== "https://api.llmgateway.io/v1") continue
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.settings?.baseURL !== "https://api.llmgateway.io/v1") continue
|
||||
if (!configured.has(Integration.ID.make(item.provider.id))) continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
|
||||
provider.request.headers["X-Title"] = "opencode"
|
||||
provider.request.headers["X-Source"] = "opencode"
|
||||
provider.headers = {
|
||||
...provider.headers,
|
||||
"HTTP-Referer": "https://opencode.ai/",
|
||||
"X-Title": "opencode",
|
||||
"X-Source": "opencode",
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,18 +1,22 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
|
||||
export const NvidiaPlugin = define({
|
||||
id: "opencode.provider.nvidia",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.api.url !== "https://integrate.api.nvidia.com/v1") continue
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.settings?.baseURL !== "https://integrate.api.nvidia.com/v1") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
|
||||
provider.request.headers["X-Title"] = "opencode"
|
||||
provider.request.headers["X-BILLING-INVOKE-ORIGIN"] ??= "OpenCode"
|
||||
provider.headers = {
|
||||
...provider.headers,
|
||||
"HTTP-Referer": "https://opencode.ai/",
|
||||
"X-Title": "opencode",
|
||||
"X-BILLING-INVOKE-ORIGIN": provider.headers?.["X-BILLING-INVOKE-ORIGIN"] ?? "OpenCode",
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -178,8 +178,8 @@ export const OpenAIPlugin = define({
|
|||
})
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/openai") continue
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/openai") continue
|
||||
if (!item.models.has(ModelV2.ID.make("gpt-5-chat-latest"))) continue
|
||||
evt.model.update(item.provider.id, ModelV2.ID.make("gpt-5-chat-latest"), (model) => {
|
||||
// OpenAIPlugin sends OpenAI models through Responses; this alias is a
|
||||
|
|
@ -194,7 +194,7 @@ export const OpenAIPlugin = define({
|
|||
// ChatGPT-plan tokens only authorize codex-eligible models, and the
|
||||
// subscription covers usage, so hide the rest and zero the cost.
|
||||
evt.model.update(item.provider.id, model.id, (draft) => {
|
||||
if (!OpenAICodex.eligible(draft.api.id)) {
|
||||
if (!OpenAICodex.eligible(draft.modelID ?? draft.id)) {
|
||||
draft.enabled = false
|
||||
return
|
||||
}
|
||||
|
|
@ -220,7 +220,7 @@ export const OpenAIPlugin = define({
|
|||
yield* ctx.aisdk.language(
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.openai) return
|
||||
evt.language = evt.sdk.responses(evt.model.api.id)
|
||||
evt.language = evt.sdk.responses(evt.model.modelID ?? evt.model.id)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -112,45 +112,43 @@ export const OpencodePlugin = define<HttpClient.HttpClient | EventV2.Service | S
|
|||
catalog.provider.update(providerID, (provider) => {
|
||||
provider.integrationID = Integration.ID.make("opencode")
|
||||
if (item.name !== undefined) provider.name = item.name
|
||||
provider.api = item.npm
|
||||
? { type: "aisdk", package: item.npm, url: item.api }
|
||||
: { type: "native", url: item.api, settings: {} }
|
||||
Object.assign(provider.request.headers, item.options?.headers)
|
||||
Object.assign(provider.request.body, withoutCredentials(item.options))
|
||||
provider.package = item.npm ? ProviderV2.aisdk(item.npm) : ""
|
||||
provider.settings = {
|
||||
...provider.settings,
|
||||
...withoutCredentials(item.options),
|
||||
...(item.api ? { baseURL: item.api } : {}),
|
||||
}
|
||||
provider.headers = { ...provider.headers, ...item.options?.headers }
|
||||
})
|
||||
|
||||
for (const [modelID, config] of Object.entries(item.models ?? {})) {
|
||||
catalog.model.update(providerID, modelID, (model) => {
|
||||
if (config.family !== undefined) model.family = config.family
|
||||
if (config.name !== undefined) model.name = config.name
|
||||
if (config.id !== undefined) model.api.id = config.id
|
||||
if (config.id !== undefined) model.modelID = config.id
|
||||
if (config.provider !== undefined) {
|
||||
model.api = config.provider.npm
|
||||
? {
|
||||
id: model.api.id,
|
||||
type: "aisdk",
|
||||
package: config.provider.npm,
|
||||
url: config.provider.api,
|
||||
}
|
||||
: { id: model.api.id, type: "native", url: config.provider.api, settings: {} }
|
||||
model.package = config.provider.npm ? ProviderV2.aisdk(config.provider.npm) : undefined
|
||||
if (config.provider.api) model.settings = { ...model.settings, baseURL: config.provider.api }
|
||||
}
|
||||
if (config.tool_call !== undefined) model.capabilities.tools = config.tool_call
|
||||
if (config.modalities?.input !== undefined) model.capabilities.input = [...config.modalities.input]
|
||||
if (config.modalities?.output !== undefined) model.capabilities.output = [...config.modalities.output]
|
||||
const packageName = config.provider?.npm ?? item.npm
|
||||
const lowerer = ConfigProviderOptionsV1.get(packageName)
|
||||
Object.assign(model.request.headers, config.headers)
|
||||
Object.assign(model.request.body, lowerer.request(withoutCredentials(config.options)))
|
||||
model.headers = { ...model.headers, ...config.headers }
|
||||
model.settings = { ...model.settings, ...ConfigProviderOptionsV1.model(withoutCredentials(config.options)) }
|
||||
if (config.variants !== undefined) {
|
||||
model.variants ??= []
|
||||
for (const [id, options] of Object.entries(config.variants)) {
|
||||
const variantID = ModelV2.VariantID.make(id)
|
||||
let existing = model.variants.find((item) => item.id === variantID)
|
||||
if (!existing) {
|
||||
existing = { id: variantID, settings: {}, headers: {}, body: {} }
|
||||
existing = { id: variantID }
|
||||
model.variants.push(existing)
|
||||
}
|
||||
Object.assign(existing.headers, options.headers)
|
||||
Object.assign(existing.body, lowerer.request(withoutCredentials(options)))
|
||||
existing.headers = { ...existing.headers, ...options.headers }
|
||||
existing.settings = {
|
||||
...existing.settings,
|
||||
...ConfigProviderOptionsV1.model(withoutCredentials(options)),
|
||||
}
|
||||
}
|
||||
}
|
||||
if (config.release_date !== undefined) {
|
||||
|
|
@ -169,9 +167,9 @@ export const OpencodePlugin = define<HttpClient.HttpClient | EventV2.Service | S
|
|||
|
||||
const item = catalog.provider.get(ProviderV2.ID.opencode)
|
||||
if (!item) return
|
||||
const hasKey = Boolean(process.env.OPENCODE_API_KEY || connected || item.provider.request.body.apiKey)
|
||||
const hasKey = Boolean(process.env.OPENCODE_API_KEY || connected || item.provider.settings?.apiKey)
|
||||
catalog.provider.update(item.provider.id, (provider) => {
|
||||
if (!hasKey) provider.request.body.apiKey = "public"
|
||||
if (!hasKey) provider.settings = { ...provider.settings, apiKey: "public" }
|
||||
})
|
||||
if (hasKey) return
|
||||
for (const model of item.models.values()) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Effect } from "effect"
|
||||
import { ModelV2 } from "../../model"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
|
||||
export const OpenRouterPlugin = define({
|
||||
|
|
@ -7,11 +8,10 @@ export const OpenRouterPlugin = define({
|
|||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@openrouter/ai-sdk-provider") continue
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@openrouter/ai-sdk-provider") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
|
||||
provider.request.headers["X-Title"] = "opencode"
|
||||
provider.headers = { ...provider.headers, "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode" }
|
||||
})
|
||||
for (const modelID of [ModelV2.ID.make("gpt-5-chat-latest"), ModelV2.ID.make("openai/gpt-5-chat")]) {
|
||||
if (!item.models.has(modelID)) continue
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export const SapAICorePlugin = define({
|
|||
yield* ctx.aisdk.language(
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.make("sap-ai-core")) return
|
||||
evt.language = evt.sdk(evt.model.api.id)
|
||||
evt.language = evt.sdk(evt.model.modelID ?? evt.model.id)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
|
||||
export const VercelPlugin = define({
|
||||
id: "opencode.provider.vercel",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/vercel") continue
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/vercel") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.request.headers["http-referer"] = "https://opencode.ai/"
|
||||
provider.request.headers["x-title"] = "opencode"
|
||||
provider.headers = { ...provider.headers, "http-referer": "https://opencode.ai/", "x-title": "opencode" }
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export const XAIPlugin = define({
|
|||
yield* ctx.aisdk.language(
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.make("xai")) return
|
||||
evt.language = evt.sdk.responses(evt.model.api.id)
|
||||
evt.language = evt.sdk.responses(evt.model.modelID ?? evt.model.id)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,17 +1,21 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
|
||||
export const ZenmuxPlugin = define({
|
||||
id: "opencode.provider.zenmux",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.api.url !== "https://zenmux.ai/api/v1") continue
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.settings?.baseURL !== "https://zenmux.ai/api/v1") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.request.headers["HTTP-Referer"] ??= "https://opencode.ai/"
|
||||
provider.request.headers["X-Title"] ??= "opencode"
|
||||
provider.headers = {
|
||||
"HTTP-Referer": "https://opencode.ai/",
|
||||
"X-Title": "opencode",
|
||||
...provider.headers,
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
export * as VariantPlugin from "./variant"
|
||||
|
||||
import type { ModelV2Info } from "@opencode-ai/sdk/v2/types"
|
||||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ModelV2 } from "../model"
|
||||
import { ProviderV2 } from "../provider"
|
||||
|
||||
export const Plugin = define({
|
||||
id: "opencode.variant",
|
||||
|
|
@ -11,14 +12,15 @@ export const Plugin = define({
|
|||
for (const record of catalog.provider.list()) {
|
||||
for (const model of record.models.values()) {
|
||||
catalog.model.update(model.providerID, model.id, (draft) => {
|
||||
const generated = generate(draft)
|
||||
const generated = generate(draft, record.provider)
|
||||
if (generated.length === 0) return
|
||||
|
||||
const explicit = new Map(draft.variants.map((variant) => [variant.id, variant]))
|
||||
const generatedIDs = new Set(generated.map((variant) => variant.id))
|
||||
const variants = draft.variants ?? []
|
||||
const explicit = new Map(variants.map((variant) => [variant.id, variant]))
|
||||
const generatedIDs = new Set<string>(generated.map((variant) => variant.id))
|
||||
draft.variants = [
|
||||
...generated.map((variant) => explicit.get(variant.id) ?? variant),
|
||||
...draft.variants.filter((variant) => !generatedIDs.has(variant.id)),
|
||||
...variants.filter((variant) => !generatedIDs.has(variant.id)),
|
||||
]
|
||||
})
|
||||
}
|
||||
|
|
@ -27,14 +29,16 @@ export const Plugin = define({
|
|||
}),
|
||||
})
|
||||
|
||||
export function generate(model: ModelV2Info): ModelV2Info["variants"] {
|
||||
if (model.api.type !== "aisdk" || model.api.package !== "@ai-sdk/openai-compatible") return []
|
||||
const ids = `${model.id} ${model.api.id}`.toLowerCase()
|
||||
export function generate(
|
||||
model: { readonly id: string; readonly modelID?: string; readonly package?: string },
|
||||
provider?: { readonly package: string },
|
||||
): NonNullable<ModelV2.Info["variants"]> {
|
||||
const packageName = model.package ?? provider?.package
|
||||
if (!ProviderV2.isAISDK(packageName) || ProviderV2.packageName(packageName) !== "@ai-sdk/openai-compatible") return []
|
||||
const ids = `${model.id} ${model.modelID ?? ""}`.toLowerCase()
|
||||
if (!["glm-5.2", "glm-5-2", "glm-5p2"].some((name) => ids.includes(name))) return []
|
||||
return ["high", "max"].map((id) => ({
|
||||
id,
|
||||
id: ModelV2.VariantID.make(id),
|
||||
settings: { reasoningEffort: id },
|
||||
headers: {},
|
||||
body: {},
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue