refactor(core): remove ai sdk option fields (#30581)
This commit is contained in:
parent
7f8412ec3e
commit
1520b0de20
57 changed files with 825 additions and 686 deletions
|
|
@ -19,7 +19,7 @@ export const Color = Schema.Union([
|
|||
export class Info extends Schema.Class<Info>("AgentV2.Info")({
|
||||
id: ID,
|
||||
model: ModelV2.Ref.pipe(Schema.optional),
|
||||
options: ProviderV2.Options,
|
||||
request: ProviderV2.Request,
|
||||
system: Schema.String.pipe(Schema.optional),
|
||||
description: Schema.String.pipe(Schema.optional),
|
||||
mode: Schema.Literals(["subagent", "primary", "all"]),
|
||||
|
|
@ -31,13 +31,9 @@ export class Info extends Schema.Class<Info>("AgentV2.Info")({
|
|||
static empty(id: ID) {
|
||||
return new Info({
|
||||
id,
|
||||
options: {
|
||||
request: {
|
||||
headers: {},
|
||||
body: {},
|
||||
aisdk: {
|
||||
provider: {},
|
||||
request: {},
|
||||
},
|
||||
},
|
||||
mode: "all",
|
||||
hidden: false,
|
||||
|
|
|
|||
|
|
@ -58,8 +58,12 @@ function wrapSSE(res: Response, ms: number, ctl: AbortController) {
|
|||
}
|
||||
|
||||
function prepareOptions(model: ModelV2.Info, pkg: string) {
|
||||
const options: Record<string, any> = { name: model.providerID, ...model.options.aisdk.provider }
|
||||
if (model.endpoint.type === "aisdk" && model.endpoint.url) options.baseURL = model.endpoint.url
|
||||
const options: Record<string, any> = {
|
||||
name: model.providerID,
|
||||
...(model.api.type === "aisdk" ? (model.api.settings ?? {}) : {}),
|
||||
...model.request.body,
|
||||
}
|
||||
if (model.api.type === "aisdk" && model.api.url) options.baseURL = model.api.url
|
||||
|
||||
const customFetch = options.fetch
|
||||
const chunkTimeout = options.chunkTimeout
|
||||
|
|
@ -123,25 +127,25 @@ export const layer = Layer.effect(
|
|||
|
||||
return Service.of({
|
||||
language: Effect.fn("AISDK.language")(function* (model) {
|
||||
const key = `${model.providerID}/${model.id}/${model.options.variant ?? "default"}`
|
||||
const key = `${model.providerID}/${model.id}/${model.request.variant ?? "default"}`
|
||||
const existing = languages.get(key)
|
||||
if (existing) return existing
|
||||
if (model.endpoint.type !== "aisdk")
|
||||
if (model.api.type !== "aisdk")
|
||||
return yield* new InitError({
|
||||
providerID: model.providerID,
|
||||
cause: new Error(`Unsupported endpoint ${model.endpoint.type}`),
|
||||
cause: new Error(`Unsupported api ${model.api.type}`),
|
||||
})
|
||||
|
||||
const options = prepareOptions(model, model.endpoint.package)
|
||||
const options = prepareOptions(model, model.api.package)
|
||||
const sdkKey = JSON.stringify({
|
||||
providerID: model.providerID,
|
||||
endpoint: model.endpoint,
|
||||
api: model.api,
|
||||
options,
|
||||
})
|
||||
const sdk =
|
||||
sdks.get(sdkKey) ??
|
||||
(yield* plugin
|
||||
.trigger("aisdk.sdk", { model, package: model.endpoint.package, options }, {})
|
||||
.trigger("aisdk.sdk", { model, package: model.api.package, options }, {})
|
||||
.pipe(initError(model.providerID))).sdk
|
||||
if (!sdk)
|
||||
return yield* new InitError({
|
||||
|
|
|
|||
|
|
@ -97,34 +97,29 @@ export const layer = Layer.effect(
|
|||
|
||||
const resolve = (model: ModelV2.Info) => {
|
||||
const provider = state.get().providers.get(model.providerID)!.provider
|
||||
const endpoint =
|
||||
model.endpoint.type === "unknown"
|
||||
? provider.endpoint
|
||||
: model.endpoint.type === "aisdk" && provider.endpoint.type === "aisdk" && !model.endpoint.url
|
||||
? { ...model.endpoint, url: provider.endpoint.url }
|
||||
: model.endpoint
|
||||
const options = {
|
||||
const api =
|
||||
model.api.type === "native" && !model.api.url && Object.keys(model.api.settings).length === 0
|
||||
? provider.api
|
||||
: model.api.type === "aisdk" && provider.api.type === "aisdk" && !model.api.url
|
||||
? { ...model.api, url: provider.api.url, settings: { ...provider.api.settings, ...model.api.settings } }
|
||||
: model.api.type === "aisdk" && provider.api.type === "aisdk"
|
||||
? { ...model.api, settings: { ...provider.api.settings, ...model.api.settings } }
|
||||
: model.api
|
||||
const request = {
|
||||
headers: {
|
||||
...provider.options.headers,
|
||||
...model.options.headers,
|
||||
...provider.request.headers,
|
||||
...model.request.headers,
|
||||
},
|
||||
body: {
|
||||
...provider.options.body,
|
||||
...model.options.body,
|
||||
...provider.request.body,
|
||||
...model.request.body,
|
||||
},
|
||||
aisdk: {
|
||||
provider: {
|
||||
...provider.options.aisdk.provider,
|
||||
...model.options.aisdk.provider,
|
||||
},
|
||||
request: model.options.aisdk.request,
|
||||
},
|
||||
variant: model.options.variant,
|
||||
variant: model.request.variant,
|
||||
}
|
||||
return new ModelV2.Info({
|
||||
...model,
|
||||
endpoint,
|
||||
options,
|
||||
api,
|
||||
request,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -134,10 +129,10 @@ export const layer = Layer.effect(
|
|||
return match
|
||||
}
|
||||
|
||||
const normalizeEndpoint = (item: Draft<ProviderV2.Info> | Draft<ModelV2.Info>) => {
|
||||
if (item.endpoint.type !== "aisdk" || typeof item.options.aisdk.provider.baseURL !== "string") return
|
||||
item.endpoint.url = item.options.aisdk.provider.baseURL
|
||||
delete item.options.aisdk.provider.baseURL
|
||||
const normalizeApi = (item: Draft<ProviderV2.Info> | Draft<ModelV2.Info>) => {
|
||||
if (typeof item.request.body.baseURL !== "string") return
|
||||
item.api.url = item.request.body.baseURL
|
||||
delete item.request.body.baseURL
|
||||
}
|
||||
|
||||
const state = State.create<Data, Editor>({
|
||||
|
|
@ -157,7 +152,7 @@ export const layer = Layer.effect(
|
|||
draft.providers.set(providerID, current)
|
||||
}
|
||||
fn(current.provider)
|
||||
normalizeEndpoint(current.provider)
|
||||
normalizeApi(current.provider)
|
||||
},
|
||||
remove: (providerID) => {
|
||||
draft.providers.delete(providerID)
|
||||
|
|
@ -179,7 +174,7 @@ export const layer = Layer.effect(
|
|||
fn(model)
|
||||
model.id = modelID
|
||||
model.providerID = providerID
|
||||
normalizeEndpoint(model)
|
||||
normalizeApi(model)
|
||||
},
|
||||
remove: (providerID, modelID) => {
|
||||
draft.providers.get(providerID)?.models.delete(modelID)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export const Color = Schema.Union([
|
|||
export class Info extends Schema.Class<Info>("ConfigV2.Agent")({
|
||||
model: Schema.String.pipe(Schema.optional),
|
||||
variant: Schema.String.pipe(Schema.optional),
|
||||
options: ConfigProvider.Options.pipe(Schema.optional),
|
||||
request: ConfigProvider.Request.pipe(Schema.optional),
|
||||
system: Schema.String.pipe(Schema.optional),
|
||||
description: Schema.String.pipe(Schema.optional),
|
||||
mode: Schema.Literals(["subagent", "primary", "all"]).pipe(Schema.optional),
|
||||
|
|
|
|||
|
|
@ -34,11 +34,9 @@ export const Plugin = PluginV2.define({
|
|||
if (item.variant !== undefined && agent.model !== undefined) {
|
||||
agent.model.variant = ModelV2.VariantID.make(item.variant)
|
||||
}
|
||||
if (item.options !== undefined) {
|
||||
Object.assign(agent.options.headers, item.options.headers ?? {})
|
||||
Object.assign(agent.options.body, item.options.body ?? {})
|
||||
Object.assign(agent.options.aisdk.provider, item.options.aisdk?.provider ?? {})
|
||||
Object.assign(agent.options.aisdk.request, item.options.aisdk?.request ?? {})
|
||||
if (item.request !== undefined) {
|
||||
Object.assign(agent.request.headers, item.request.headers ?? {})
|
||||
Object.assign(agent.request.body, item.request.body ?? {})
|
||||
}
|
||||
if (item.system !== undefined) agent.system = item.system
|
||||
if (item.description !== undefined) agent.description = item.description
|
||||
|
|
|
|||
|
|
@ -23,12 +23,10 @@ export const Plugin = PluginV2.define({
|
|||
if (item.name !== undefined) provider.name = item.name
|
||||
if (item.env !== undefined) provider.env = [...item.env]
|
||||
provider.enabled = { via: "custom", data: {} }
|
||||
if (item.endpoint !== undefined) provider.endpoint = { ...item.endpoint }
|
||||
if (item.options !== undefined) {
|
||||
Object.assign(provider.options.headers, item.options.headers ?? {})
|
||||
Object.assign(provider.options.body, item.options.body ?? {})
|
||||
Object.assign(provider.options.aisdk.provider, item.options.aisdk?.provider ?? {})
|
||||
Object.assign(provider.options.aisdk.request, item.options.aisdk?.request ?? {})
|
||||
if (item.api !== undefined) provider.api = { ...item.api }
|
||||
if (item.request !== undefined) {
|
||||
Object.assign(provider.request.headers, item.request.headers ?? {})
|
||||
Object.assign(provider.request.body, item.request.body ?? {})
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -37,7 +35,7 @@ export const Plugin = PluginV2.define({
|
|||
if (config.api_id !== undefined) model.apiID = config.api_id
|
||||
if (config.family !== undefined) model.family = config.family
|
||||
if (config.name !== undefined) model.name = config.name
|
||||
if (config.endpoint !== undefined) model.endpoint = { ...config.endpoint }
|
||||
if (config.api !== undefined) model.api = { ...config.api }
|
||||
if (config.capabilities !== undefined) {
|
||||
model.capabilities = {
|
||||
tools: config.capabilities.tools,
|
||||
|
|
@ -45,12 +43,10 @@ export const Plugin = PluginV2.define({
|
|||
output: [...config.capabilities.output],
|
||||
}
|
||||
}
|
||||
if (config.options !== undefined) {
|
||||
Object.assign(model.options.headers, config.options.headers ?? {})
|
||||
Object.assign(model.options.body, config.options.body ?? {})
|
||||
Object.assign(model.options.aisdk.provider, config.options.aisdk?.provider ?? {})
|
||||
Object.assign(model.options.aisdk.request, config.options.aisdk?.request ?? {})
|
||||
if (config.options.variant !== undefined) model.options.variant = config.options.variant
|
||||
if (config.request !== undefined) {
|
||||
Object.assign(model.request.headers, config.request.headers ?? {})
|
||||
Object.assign(model.request.body, config.request.body ?? {})
|
||||
if (config.request.variant !== undefined) model.request.variant = config.request.variant
|
||||
}
|
||||
if (config.variants !== undefined) {
|
||||
for (const variant of config.variants) {
|
||||
|
|
@ -60,17 +56,11 @@ export const Plugin = PluginV2.define({
|
|||
id: variant.id,
|
||||
headers: {},
|
||||
body: {},
|
||||
aisdk: {
|
||||
provider: {},
|
||||
request: {},
|
||||
},
|
||||
}
|
||||
model.variants.push(existing)
|
||||
}
|
||||
Object.assign(existing.headers, variant.headers ?? {})
|
||||
Object.assign(existing.body, variant.body ?? {})
|
||||
Object.assign(existing.aisdk.provider, variant.aisdk?.provider ?? {})
|
||||
Object.assign(existing.aisdk.request, variant.aisdk?.request ?? {})
|
||||
}
|
||||
}
|
||||
if (config.cost !== undefined) {
|
||||
|
|
|
|||
|
|
@ -4,13 +4,9 @@ import { Schema } from "effect"
|
|||
import { ProviderV2 } from "../provider"
|
||||
import { ModelV2 } from "../model"
|
||||
|
||||
export class Options extends Schema.Class<Options>("ConfigV2.Provider.Options")({
|
||||
export class Request extends Schema.Class<Request>("ConfigV2.Provider.Request")({
|
||||
headers: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
|
||||
body: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||
aisdk: Schema.Struct({
|
||||
provider: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||
request: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||
}).pipe(Schema.optional),
|
||||
}) {}
|
||||
|
||||
class Cache extends Schema.Class<Cache>("ConfigV2.Model.Cost.Cache")({
|
||||
|
|
@ -38,15 +34,15 @@ class Model extends Schema.Class<Model>("ConfigV2.Model")({
|
|||
api_id: ModelV2.ID.pipe(Schema.optional),
|
||||
family: ModelV2.Family.pipe(Schema.optional),
|
||||
name: Schema.String.pipe(Schema.optional),
|
||||
endpoint: ProviderV2.Endpoint.pipe(Schema.optional),
|
||||
api: ProviderV2.Api.pipe(Schema.optional),
|
||||
capabilities: ModelV2.Capabilities.pipe(Schema.optional),
|
||||
options: Schema.Struct({
|
||||
...Options.fields,
|
||||
request: Schema.Struct({
|
||||
...Request.fields,
|
||||
variant: Schema.String.pipe(Schema.optional),
|
||||
}).pipe(Schema.optional),
|
||||
variants: Schema.Struct({
|
||||
id: ModelV2.VariantID,
|
||||
...Options.fields,
|
||||
...Request.fields,
|
||||
}).pipe(Schema.Array, Schema.optional),
|
||||
cost: Schema.Union([Cost, Cost.pipe(Schema.Array)]).pipe(Schema.optional),
|
||||
disabled: Schema.Boolean.pipe(Schema.optional),
|
||||
|
|
@ -56,7 +52,7 @@ class Model extends Schema.Class<Model>("ConfigV2.Model")({
|
|||
export class Info extends Schema.Class<Info>("ConfigV2.Provider")({
|
||||
name: Schema.String.pipe(Schema.optional),
|
||||
env: Schema.String.pipe(Schema.Array, Schema.optional),
|
||||
endpoint: ProviderV2.Endpoint.pipe(Schema.optional),
|
||||
options: Options.pipe(Schema.optional),
|
||||
api: ProviderV2.Api.pipe(Schema.optional),
|
||||
request: Request.pipe(Schema.optional),
|
||||
models: Schema.Record(Schema.String, Model).pipe(Schema.optional),
|
||||
}) {}
|
||||
|
|
|
|||
|
|
@ -46,15 +46,15 @@ export class Info extends Schema.Class<Info>("ModelV2.Info")({
|
|||
providerID: ProviderV2.ID,
|
||||
family: Family.pipe(Schema.optional),
|
||||
name: Schema.String,
|
||||
endpoint: ProviderV2.Endpoint,
|
||||
api: ProviderV2.Api,
|
||||
capabilities: Capabilities,
|
||||
options: Schema.Struct({
|
||||
...ProviderV2.Options.fields,
|
||||
request: Schema.Struct({
|
||||
...ProviderV2.Request.fields,
|
||||
variant: Schema.String.pipe(Schema.optional),
|
||||
}),
|
||||
variants: Schema.Struct({
|
||||
id: VariantID,
|
||||
...ProviderV2.Options.fields,
|
||||
...ProviderV2.Request.fields,
|
||||
}).pipe(Schema.Array),
|
||||
time: Schema.Struct({
|
||||
released: DateTimeUtcFromMillis,
|
||||
|
|
@ -69,26 +69,23 @@ export class Info extends Schema.Class<Info>("ModelV2.Info")({
|
|||
}),
|
||||
}) {
|
||||
static empty(providerID: ProviderV2.ID, modelID: ID): Info {
|
||||
return {
|
||||
return new Info({
|
||||
id: modelID,
|
||||
apiID: modelID,
|
||||
providerID,
|
||||
name: modelID,
|
||||
endpoint: {
|
||||
type: "unknown",
|
||||
api: {
|
||||
type: "native",
|
||||
settings: {},
|
||||
},
|
||||
capabilities: {
|
||||
tools: false,
|
||||
input: [],
|
||||
output: [],
|
||||
},
|
||||
options: {
|
||||
request: {
|
||||
headers: {},
|
||||
body: {},
|
||||
aisdk: {
|
||||
provider: {},
|
||||
request: {},
|
||||
},
|
||||
},
|
||||
variants: [],
|
||||
time: {
|
||||
|
|
@ -101,7 +98,7 @@ export class Info extends Schema.Class<Info>("ModelV2.Info")({
|
|||
context: 0,
|
||||
output: 0,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,10 +32,10 @@ export const AccountPlugin = PluginV2.define({
|
|||
service: account.serviceID,
|
||||
}
|
||||
if (account.credential.type === "api") {
|
||||
provider.options.aisdk.provider.apiKey = account.credential.key
|
||||
Object.assign(provider.options.aisdk.provider, account.credential.metadata ?? {})
|
||||
provider.request.body.apiKey = account.credential.key
|
||||
Object.assign(provider.request.body, account.credential.metadata ?? {})
|
||||
}
|
||||
if (account.credential.type === "oauth") provider.options.aisdk.provider.apiKey = account.credential.access
|
||||
if (account.credential.type === "oauth") provider.request.body.apiKey = account.credential.access
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -43,10 +43,6 @@ function variants(model: ModelsDev.Model) {
|
|||
id: ModelV2.VariantID.make(id),
|
||||
headers: { ...(item.provider?.headers ?? {}) },
|
||||
body: { ...(item.provider?.body ?? {}) },
|
||||
aisdk: {
|
||||
provider: {},
|
||||
request: {},
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -66,14 +62,16 @@ export const ModelsDevPlugin = PluginV2.define({
|
|||
catalog.provider.update(providerID, (provider) => {
|
||||
provider.name = item.name
|
||||
provider.env = [...item.env]
|
||||
provider.endpoint = item.npm
|
||||
provider.api = item.npm
|
||||
? {
|
||||
type: "aisdk",
|
||||
package: item.npm,
|
||||
url: item.api,
|
||||
}
|
||||
: {
|
||||
type: "unknown",
|
||||
type: "native",
|
||||
url: item.api,
|
||||
settings: {},
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -82,14 +80,16 @@ export const ModelsDevPlugin = PluginV2.define({
|
|||
catalog.model.update(providerID, modelID, (draft) => {
|
||||
draft.name = model.name
|
||||
draft.family = model.family ? ModelV2.Family.make(model.family) : undefined
|
||||
draft.endpoint = model.provider?.npm
|
||||
draft.api = model.provider?.npm
|
||||
? {
|
||||
type: "aisdk",
|
||||
package: model.provider?.npm,
|
||||
url: model.provider.api,
|
||||
}
|
||||
: {
|
||||
type: "unknown",
|
||||
type: "native",
|
||||
url: model.provider?.api,
|
||||
settings: {},
|
||||
}
|
||||
draft.capabilities = {
|
||||
tools: model.tool_call,
|
||||
|
|
|
|||
|
|
@ -52,15 +52,15 @@ export const AmazonBedrockPlugin = PluginV2.define({
|
|||
return {
|
||||
"catalog.transform": Effect.fn(function* (evt) {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.endpoint.type !== "aisdk") continue
|
||||
if (item.provider.endpoint.package !== "@ai-sdk/amazon-bedrock") continue
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/amazon-bedrock") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
if (provider.endpoint.type !== "aisdk") return
|
||||
if (typeof provider.options.aisdk.provider.endpoint !== "string") return
|
||||
if (provider.api.type !== "aisdk") return
|
||||
if (typeof provider.request.body.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.endpoint.url = provider.options.aisdk.provider.endpoint
|
||||
delete provider.options.aisdk.provider.endpoint
|
||||
provider.api.url = provider.request.body.endpoint
|
||||
delete provider.request.body.endpoint
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ export const AnthropicPlugin = PluginV2.define({
|
|||
return {
|
||||
"catalog.transform": Effect.fn(function* (evt) {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.endpoint.type !== "aisdk") continue
|
||||
if (item.provider.endpoint.package !== "@ai-sdk/anthropic") continue
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/anthropic") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.options.headers["anthropic-beta"] =
|
||||
provider.request.headers["anthropic-beta"] =
|
||||
"interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14"
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,14 +16,14 @@ export const AzurePlugin = PluginV2.define({
|
|||
return {
|
||||
"catalog.transform": Effect.fn(function* (evt) {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.endpoint.type !== "aisdk") continue
|
||||
if (item.provider.endpoint.package !== "@ai-sdk/azure") continue
|
||||
const configured = item.provider.options.aisdk.provider.resourceName
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/azure") continue
|
||||
const configured = item.provider.request.body.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.options.aisdk.provider.resourceName = resourceName
|
||||
provider.request.body.resourceName = resourceName
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
|
@ -33,7 +33,7 @@ export const AzurePlugin = PluginV2.define({
|
|||
if (
|
||||
!evt.options.resourceName &&
|
||||
!evt.options.baseURL &&
|
||||
(evt.model.endpoint.type !== "aisdk" || !evt.model.endpoint.url)
|
||||
(evt.model.api.type !== "aisdk" || !evt.model.api.url)
|
||||
) {
|
||||
throw new Error(
|
||||
"AZURE_RESOURCE_NAME is missing, set it using env var or reconnecting the azure provider and setting it",
|
||||
|
|
@ -59,11 +59,11 @@ export const AzureCognitiveServicesPlugin = PluginV2.define({
|
|||
const resourceName = process.env.AZURE_COGNITIVE_SERVICES_RESOURCE_NAME
|
||||
if (!resourceName) return
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.endpoint.type !== "aisdk") continue
|
||||
if (item.provider.endpoint.package !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue
|
||||
if (!item.provider.id.includes("azure-cognitive-services")) continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.options.aisdk.provider.baseURL = `https://${resourceName}.cognitiveservices.azure.com/openai`
|
||||
provider.request.body.baseURL = `https://${resourceName}.cognitiveservices.azure.com/openai`
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ export const CerebrasPlugin = PluginV2.define({
|
|||
return {
|
||||
"catalog.transform": Effect.fn(function* (ctx) {
|
||||
for (const item of ctx.provider.list()) {
|
||||
if (item.provider.endpoint.type !== "aisdk") continue
|
||||
if (item.provider.endpoint.package !== "@ai-sdk/cerebras") continue
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/cerebras") continue
|
||||
ctx.provider.update(item.provider.id, (provider) => {
|
||||
provider.options.headers["X-Cerebras-3rd-Party-Integration"] = "opencode"
|
||||
provider.request.headers["X-Cerebras-3rd-Party-Integration"] = "opencode"
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -14,17 +14,17 @@ export const CloudflareWorkersAIPlugin = PluginV2.define({
|
|||
const item = evt.provider.get(providerID)
|
||||
if (!item) return
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
if (provider.endpoint.type !== "aisdk") return
|
||||
if (provider.endpoint.url) return
|
||||
const accountId = resolveAccountId(provider.options.aisdk.provider)
|
||||
if (accountId) provider.endpoint.url = workersEndpoint(accountId)
|
||||
if (provider.api.type !== "aisdk") return
|
||||
if (provider.api.url) return
|
||||
const accountId = resolveAccountId(provider.request.body)
|
||||
if (accountId) provider.api.url = workersEndpoint(accountId)
|
||||
})
|
||||
}),
|
||||
"aisdk.sdk": Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== providerID) return
|
||||
if (evt.package !== "@ai-sdk/openai-compatible") return
|
||||
|
||||
if (!hasWorkersEndpoint(evt.model.endpoint)) return
|
||||
if (!hasWorkersEndpoint(evt.model.api)) return
|
||||
const mod = yield* Effect.promise(() => import("@ai-sdk/openai-compatible"))
|
||||
evt.sdk = mod.createOpenAICompatible(sdkOptions(evt.options) as any)
|
||||
}),
|
||||
|
|
@ -44,8 +44,8 @@ function workersEndpoint(accountId: string) {
|
|||
return `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/v1`
|
||||
}
|
||||
|
||||
function hasWorkersEndpoint(endpoint: ProviderV2.Endpoint) {
|
||||
return endpoint.type === "aisdk" && Boolean(endpoint.url)
|
||||
function hasWorkersEndpoint(api: ProviderV2.Api) {
|
||||
return api.type === "aisdk" && Boolean(api.url)
|
||||
}
|
||||
|
||||
function sdkOptions(options: Record<string, any>) {
|
||||
|
|
|
|||
|
|
@ -37,12 +37,12 @@ export const GitLabPlugin = PluginV2.define({
|
|||
if (evt.model.apiID.startsWith("duo-workflow-")) {
|
||||
const gitlab = yield* Effect.promise(() => import("gitlab-ai-provider")).pipe(Effect.orDie)
|
||||
const workflowRef =
|
||||
typeof evt.model.options.aisdk.request.workflowRef === "string"
|
||||
? evt.model.options.aisdk.request.workflowRef
|
||||
typeof evt.model.request.body.workflowRef === "string"
|
||||
? evt.model.request.body.workflowRef
|
||||
: undefined
|
||||
const workflowDefinition =
|
||||
typeof evt.model.options.aisdk.request.workflowDefinition === "string"
|
||||
? evt.model.options.aisdk.request.workflowDefinition
|
||||
typeof evt.model.request.body.workflowDefinition === "string"
|
||||
? evt.model.request.body.workflowDefinition
|
||||
: undefined
|
||||
const language = evt.sdk.workflowChat(
|
||||
gitlab.isWorkflowModel(evt.model.apiID) ? evt.model.apiID : "duo-workflow",
|
||||
|
|
|
|||
|
|
@ -60,22 +60,22 @@ export const GoogleVertexPlugin = PluginV2.define({
|
|||
return {
|
||||
"catalog.transform": Effect.fn(function* (evt) {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.endpoint.type !== "aisdk") continue
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (
|
||||
item.provider.endpoint.package !== "@ai-sdk/google-vertex" &&
|
||||
!item.provider.endpoint.package.includes("@ai-sdk/openai-compatible")
|
||||
item.provider.api.package !== "@ai-sdk/google-vertex" &&
|
||||
!item.provider.api.package.includes("@ai-sdk/openai-compatible")
|
||||
)
|
||||
continue
|
||||
const project = resolveProject(item.provider.options.aisdk.provider)
|
||||
const location = String(resolveLocation(item.provider.options.aisdk.provider))
|
||||
const project = resolveProject(item.provider.request.body)
|
||||
const location = String(resolveLocation(item.provider.request.body))
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
if (project) provider.options.aisdk.provider.project = project
|
||||
provider.options.aisdk.provider.location = location
|
||||
if (provider.endpoint.type === "aisdk" && provider.endpoint.url) {
|
||||
provider.endpoint.url = replaceVertexVars(provider.endpoint.url, project, location)
|
||||
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.endpoint.type === "aisdk" && provider.endpoint.package.includes("@ai-sdk/openai-compatible")) {
|
||||
provider.options.aisdk.provider.fetch = authFetch(provider.options.aisdk.provider.fetch)
|
||||
if (provider.api.type === "aisdk" && provider.api.package.includes("@ai-sdk/openai-compatible")) {
|
||||
provider.request.body.fetch = authFetch(provider.request.body.fetch)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -111,21 +111,21 @@ export const GoogleVertexAnthropicPlugin = PluginV2.define({
|
|||
return {
|
||||
"catalog.transform": Effect.fn(function* (evt) {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.endpoint.type !== "aisdk") continue
|
||||
if (item.provider.endpoint.package !== "@ai-sdk/google-vertex/anthropic") continue
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/google-vertex/anthropic") continue
|
||||
const project =
|
||||
item.provider.options.aisdk.provider.project ??
|
||||
item.provider.request.body.project ??
|
||||
process.env.GOOGLE_CLOUD_PROJECT ??
|
||||
process.env.GCP_PROJECT ??
|
||||
process.env.GCLOUD_PROJECT
|
||||
const location =
|
||||
item.provider.options.aisdk.provider.location ??
|
||||
item.provider.request.body.location ??
|
||||
process.env.GOOGLE_CLOUD_LOCATION ??
|
||||
process.env.VERTEX_LOCATION ??
|
||||
"global"
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
if (project) provider.options.aisdk.provider.project = project
|
||||
provider.options.aisdk.provider.location = location
|
||||
if (project) provider.request.body.project = project
|
||||
provider.request.body.location = location
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ export const KiloPlugin = PluginV2.define({
|
|||
return {
|
||||
"catalog.transform": Effect.fn(function* (evt) {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.endpoint.type !== "aisdk") continue
|
||||
if (item.provider.endpoint.package !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.endpoint.url !== "https://api.kilo.ai/api/gateway") 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.kilo.ai/api/gateway") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.options.headers["HTTP-Referer"] = "https://opencode.ai/"
|
||||
provider.options.headers["X-Title"] = "opencode"
|
||||
provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
|
||||
provider.request.headers["X-Title"] = "opencode"
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ export const LLMGatewayPlugin = PluginV2.define({
|
|||
"catalog.transform": Effect.fn(function* (evt) {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.enabled === false) continue
|
||||
if (item.provider.endpoint.type !== "aisdk") continue
|
||||
if (item.provider.endpoint.package !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.endpoint.url !== "https://api.llmgateway.io/v1") 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
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.options.headers["HTTP-Referer"] = "https://opencode.ai/"
|
||||
provider.options.headers["X-Title"] = "opencode"
|
||||
provider.options.headers["X-Source"] = "opencode"
|
||||
provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
|
||||
provider.request.headers["X-Title"] = "opencode"
|
||||
provider.request.headers["X-Source"] = "opencode"
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ export const NvidiaPlugin = PluginV2.define({
|
|||
return {
|
||||
"catalog.transform": Effect.fn(function* (evt) {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.endpoint.type !== "aisdk") continue
|
||||
if (item.provider.endpoint.package !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.endpoint.url !== "https://integrate.api.nvidia.com/v1") continue
|
||||
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
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.options.headers["HTTP-Referer"] = "https://opencode.ai/"
|
||||
provider.options.headers["X-Title"] = "opencode"
|
||||
provider.options.headers["X-BILLING-INVOKE-ORIGIN"] ??= "OpenCode"
|
||||
provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
|
||||
provider.request.headers["X-Title"] = "opencode"
|
||||
provider.request.headers["X-BILLING-INVOKE-ORIGIN"] ??= "OpenCode"
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ export const OpenAIPlugin = PluginV2.define({
|
|||
}),
|
||||
"catalog.transform": Effect.fn(function* (evt) {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.endpoint.type !== "aisdk") continue
|
||||
if (item.provider.endpoint.package !== "@ai-sdk/openai") continue
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.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
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ export const OpencodePlugin = PluginV2.define({
|
|||
hasKey = Boolean(
|
||||
process.env.OPENCODE_API_KEY ||
|
||||
item.provider.env.some((env) => process.env[env]) ||
|
||||
item.provider.options.aisdk.provider.apiKey ||
|
||||
item.provider.request.body.apiKey ||
|
||||
(item.provider.enabled && item.provider.enabled.via === "account"),
|
||||
)
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
if (!hasKey) provider.options.aisdk.provider.apiKey = "public"
|
||||
if (!hasKey) provider.request.body.apiKey = "public"
|
||||
})
|
||||
if (hasKey) return
|
||||
for (const model of item.models.values()) {
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ export const OpenRouterPlugin = PluginV2.define({
|
|||
return {
|
||||
"catalog.transform": Effect.fn(function* (evt) {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.endpoint.type !== "aisdk") continue
|
||||
if (item.provider.endpoint.package !== "@openrouter/ai-sdk-provider") continue
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@openrouter/ai-sdk-provider") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.options.headers["HTTP-Referer"] = "https://opencode.ai/"
|
||||
provider.options.headers["X-Title"] = "opencode"
|
||||
provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
|
||||
provider.request.headers["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
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ export const VercelPlugin = PluginV2.define({
|
|||
return {
|
||||
"catalog.transform": Effect.fn(function* (evt) {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.endpoint.type !== "aisdk") continue
|
||||
if (item.provider.endpoint.package !== "@ai-sdk/vercel") continue
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/vercel") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.options.headers["http-referer"] = "https://opencode.ai/"
|
||||
provider.options.headers["x-title"] = "opencode"
|
||||
provider.request.headers["http-referer"] = "https://opencode.ai/"
|
||||
provider.request.headers["x-title"] = "opencode"
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ export const ZenmuxPlugin = PluginV2.define({
|
|||
return {
|
||||
"catalog.transform": Effect.fn(function* (evt) {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.endpoint.type !== "aisdk") continue
|
||||
if (item.provider.endpoint.package !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.endpoint.url !== "https://zenmux.ai/api/v1") continue
|
||||
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
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.options.headers["HTTP-Referer"] ??= "https://opencode.ai/"
|
||||
provider.options.headers["X-Title"] ??= "opencode"
|
||||
provider.request.headers["HTTP-Referer"] ??= "https://opencode.ai/"
|
||||
provider.request.headers["X-Title"] ??= "opencode"
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -25,59 +25,27 @@ export type ID = typeof ID.Type
|
|||
export const ModelID = Schema.String.pipe(Schema.brand("ModelID"))
|
||||
export type ModelID = typeof ModelID.Type
|
||||
|
||||
const OpenAIResponses = Schema.Struct({
|
||||
type: Schema.Literal("openai/responses"),
|
||||
url: Schema.String,
|
||||
websocket: Schema.optional(Schema.Boolean),
|
||||
})
|
||||
|
||||
const OpenAICompletions = Schema.Struct({
|
||||
type: Schema.Literal("openai/completions"),
|
||||
url: Schema.String,
|
||||
reasoning: Schema.Union([
|
||||
Schema.Struct({
|
||||
type: Schema.Literal("reasoning_content"),
|
||||
}),
|
||||
Schema.Struct({
|
||||
type: Schema.Literal("reasoning_details"),
|
||||
}),
|
||||
]).pipe(Schema.optional),
|
||||
})
|
||||
export type OpenAICompletions = typeof OpenAICompletions.Type
|
||||
|
||||
const AISDK = Schema.Struct({
|
||||
type: Schema.Literal("aisdk"),
|
||||
package: Schema.String,
|
||||
url: Schema.String.pipe(Schema.optional),
|
||||
settings: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||
})
|
||||
|
||||
const AnthropicMessages = Schema.Struct({
|
||||
type: Schema.Literal("anthropic/messages"),
|
||||
url: Schema.String,
|
||||
const Native = Schema.Struct({
|
||||
type: Schema.Literal("native"),
|
||||
url: Schema.String.pipe(Schema.optional),
|
||||
settings: Schema.Record(Schema.String, Schema.Unknown),
|
||||
})
|
||||
|
||||
const UnknownEndpoint = Schema.Struct({
|
||||
type: Schema.Literal("unknown"),
|
||||
})
|
||||
export const Api = Schema.Union([AISDK, Native]).pipe(Schema.toTaggedUnion("type"))
|
||||
export type Api = typeof Api.Type
|
||||
|
||||
export const Endpoint = Schema.Union([
|
||||
UnknownEndpoint,
|
||||
OpenAIResponses,
|
||||
OpenAICompletions,
|
||||
AnthropicMessages,
|
||||
AISDK,
|
||||
]).pipe(Schema.toTaggedUnion("type"))
|
||||
export type Endpoint = typeof Endpoint.Type
|
||||
|
||||
export const Options = Schema.Struct({
|
||||
export const Request = Schema.Struct({
|
||||
headers: Schema.Record(Schema.String, Schema.String),
|
||||
body: Schema.Record(Schema.String, Schema.Any),
|
||||
aisdk: Schema.Struct({
|
||||
provider: Schema.Record(Schema.String, Schema.Any),
|
||||
request: Schema.Record(Schema.String, Schema.Any),
|
||||
}),
|
||||
})
|
||||
export type Options = typeof Options.Type
|
||||
export type Request = typeof Request.Type
|
||||
|
||||
export class Info extends Schema.Class<Info>("ProviderV2.Info")({
|
||||
id: ID,
|
||||
|
|
@ -98,26 +66,23 @@ export class Info extends Schema.Class<Info>("ProviderV2.Info")({
|
|||
}),
|
||||
]),
|
||||
env: Schema.String.pipe(Schema.Array),
|
||||
endpoint: Endpoint,
|
||||
options: Options,
|
||||
api: Api,
|
||||
request: Request,
|
||||
}) {
|
||||
static empty(providerID: ID): Info {
|
||||
return {
|
||||
return new Info({
|
||||
id: providerID,
|
||||
name: providerID,
|
||||
enabled: false,
|
||||
env: [],
|
||||
endpoint: {
|
||||
type: "unknown",
|
||||
api: {
|
||||
type: "native",
|
||||
settings: {},
|
||||
},
|
||||
options: {
|
||||
request: {
|
||||
headers: {},
|
||||
body: {},
|
||||
aisdk: {
|
||||
provider: {},
|
||||
request: {},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { ConfigAgentV1 } from "./agent"
|
|||
import { ConfigMCPV1 } from "./mcp"
|
||||
import { ConfigPermissionV1 } from "./permission"
|
||||
import { ConfigProviderV1 } from "./provider"
|
||||
import { ConfigProviderOptionsV1 } from "./provider-options"
|
||||
|
||||
const keys = new Set([
|
||||
"logLevel",
|
||||
|
|
@ -99,7 +100,7 @@ function agents(info: typeof ConfigV1.Info.Type) {
|
|||
...Object.entries(info.mode ?? {}).map(([name, agent]) => [name, { ...agent, mode: "primary" as const }] as const),
|
||||
]
|
||||
if (!entries.length) return undefined
|
||||
return Object.fromEntries(entries.map(([name, agent]) => [name, migrateAgent(agent)]))
|
||||
return Object.fromEntries(entries.flatMap(([name, agent]) => (agent ? [[name, migrateAgent(agent)]] : [])))
|
||||
}
|
||||
|
||||
function migrateAgent(info: ConfigAgentV1.Info) {
|
||||
|
|
@ -111,7 +112,7 @@ function migrateAgent(info: ConfigAgentV1.Info) {
|
|||
return {
|
||||
model: info.model,
|
||||
variant: info.variant,
|
||||
options: Object.keys(body).length ? { body } : undefined,
|
||||
request: Object.keys(body).length ? { body } : undefined,
|
||||
system: info.prompt,
|
||||
description: info.description,
|
||||
mode: info.mode,
|
||||
|
|
@ -160,22 +161,27 @@ function providers(info?: Readonly<Record<string, ConfigProviderV1.Info>>) {
|
|||
}
|
||||
|
||||
function migrateProvider(info: ConfigProviderV1.Info) {
|
||||
const lowerer = ConfigProviderOptionsV1.get(info.npm)
|
||||
const options = lowerer.provider(info.options ?? {})
|
||||
return {
|
||||
name: info.name,
|
||||
env: info.env,
|
||||
endpoint: info.npm && {
|
||||
type: "aisdk" as const,
|
||||
package: info.npm,
|
||||
url: info.api ?? (typeof info.options?.baseURL === "string" ? info.options.baseURL : undefined),
|
||||
},
|
||||
options: info.options && { body: info.options },
|
||||
api: info.npm
|
||||
? {
|
||||
type: "aisdk" as const,
|
||||
package: info.npm,
|
||||
url: info.api ?? options.url,
|
||||
settings: options.settings ?? {},
|
||||
}
|
||||
: undefined,
|
||||
request: info.options && { headers: options.headers, body: options.body },
|
||||
models:
|
||||
info.models &&
|
||||
Object.fromEntries(Object.entries(info.models).map(([name, model]) => [name, migrateModel(model)])),
|
||||
Object.fromEntries(Object.entries(info.models).map(([name, model]) => [name, migrateModel(model, info.npm)])),
|
||||
}
|
||||
}
|
||||
|
||||
function migrateModel(info: typeof ConfigProviderV1.Model.Type) {
|
||||
function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: string) {
|
||||
const costs = info.cost && [
|
||||
{
|
||||
input: info.cost.input,
|
||||
|
|
@ -197,16 +203,33 @@ function migrateModel(info: typeof ConfigProviderV1.Model.Type) {
|
|||
info.tool_call !== undefined || info.modalities?.input !== undefined || info.modalities?.output !== undefined
|
||||
? { tools: info.tool_call ?? false, input: info.modalities?.input ?? [], output: info.modalities?.output ?? [] }
|
||||
: undefined
|
||||
const lowerer = ConfigProviderOptionsV1.get(info.provider?.npm ?? packageName)
|
||||
return {
|
||||
api_id: info.id,
|
||||
family: info.family,
|
||||
name: info.name,
|
||||
endpoint: info.provider?.npm && { type: "aisdk" as const, package: info.provider.npm, url: info.provider.api },
|
||||
api: info.provider?.npm
|
||||
? { type: "aisdk" as const, package: info.provider.npm, url: info.provider.api, settings: {} }
|
||||
: undefined,
|
||||
capabilities,
|
||||
options: (info.headers || info.options) && { headers: info.headers, body: info.options },
|
||||
variants: info.variants && Object.entries(info.variants).map(([id, options]) => ({ id, body: options })),
|
||||
request: (info.headers || info.options) && {
|
||||
headers: info.headers,
|
||||
body: info.options && lowerer.request(info.options),
|
||||
},
|
||||
variants:
|
||||
info.variants &&
|
||||
Object.entries(info.variants).map(([id, options]) => ({ id, body: lowerer.request(options) })),
|
||||
cost: costs,
|
||||
disabled: info.status === "deprecated" ? true : undefined,
|
||||
limit: info.limit,
|
||||
limit:
|
||||
info.limit && {
|
||||
context: int(info.limit.context),
|
||||
input: info.limit.input === undefined ? undefined : int(info.limit.input),
|
||||
output: int(info.limit.output),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function int(value: number) {
|
||||
return Math.max(Number.MIN_SAFE_INTEGER, Math.min(Number.MAX_SAFE_INTEGER, Math.trunc(value)))
|
||||
}
|
||||
|
|
|
|||
210
packages/core/src/v1/config/provider-options.ts
Normal file
210
packages/core/src/v1/config/provider-options.ts
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
export * as ConfigProviderOptionsV1 from "./provider-options"
|
||||
|
||||
type Options = Readonly<Record<string, unknown>>
|
||||
|
||||
export interface ProviderResult {
|
||||
readonly headers?: Record<string, string>
|
||||
readonly body?: Record<string, unknown>
|
||||
readonly url?: string
|
||||
readonly settings?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface Lowerer {
|
||||
readonly provider: (options: Options) => ProviderResult
|
||||
readonly request: (options: Options) => Record<string, unknown>
|
||||
}
|
||||
|
||||
export function get(packageName?: string): Lowerer {
|
||||
const key = packageName ?? ""
|
||||
return Object.hasOwn(lowerers, key) ? lowerers[key]! : raw
|
||||
}
|
||||
|
||||
const raw: Lowerer = {
|
||||
provider(options) {
|
||||
return { body: clone(options) }
|
||||
},
|
||||
request: clone,
|
||||
}
|
||||
|
||||
const openai: Lowerer = {
|
||||
provider(options) {
|
||||
return {
|
||||
url: string(options.baseURL),
|
||||
headers: compact({
|
||||
Authorization: bearer(options.apiKey),
|
||||
"OpenAI-Organization": string(options.organization),
|
||||
"OpenAI-Project": string(options.project),
|
||||
...headers(options.headers),
|
||||
}),
|
||||
body: body(options.body),
|
||||
settings: omit(options, ["apiKey", "baseURL", "organization", "project", "headers", "body"]),
|
||||
}
|
||||
},
|
||||
request: snake,
|
||||
}
|
||||
|
||||
const anthropic: Lowerer = {
|
||||
provider(options) {
|
||||
return {
|
||||
url: string(options.baseURL),
|
||||
headers: compact({
|
||||
"x-api-key": string(options.apiKey),
|
||||
Authorization: options.authToken ? bearer(options.authToken) : undefined,
|
||||
...headers(options.headers),
|
||||
}),
|
||||
body: body(options.body),
|
||||
settings: omit(options, ["apiKey", "authToken", "baseURL", "headers", "body"]),
|
||||
}
|
||||
},
|
||||
request(options) {
|
||||
const result = snake(options)
|
||||
if (options.effort !== undefined || options.taskBudget !== undefined) {
|
||||
result.output_config = compactUnknown({ effort: options.effort, task_budget: options.taskBudget })
|
||||
delete result.effort
|
||||
delete result.task_budget
|
||||
}
|
||||
if (isRecord(options.metadata) && options.metadata.userId !== undefined) {
|
||||
result.metadata = { ...options.metadata, user_id: options.metadata.userId }
|
||||
delete (result.metadata as Record<string, unknown>).userId
|
||||
}
|
||||
return result
|
||||
},
|
||||
}
|
||||
|
||||
const google: Lowerer = {
|
||||
provider(options) {
|
||||
return {
|
||||
url: string(options.baseURL),
|
||||
headers: compact({ "x-goog-api-key": string(options.apiKey), ...headers(options.headers) }),
|
||||
body: body(options.body),
|
||||
settings: omit(options, ["apiKey", "baseURL", "headers", "body"]),
|
||||
}
|
||||
},
|
||||
request(options) {
|
||||
const generationConfig = pick(options, ["thinkingConfig", "responseModalities", "mediaResolution", "imageConfig"])
|
||||
return {
|
||||
...omit(options, ["thinkingConfig", "responseModalities", "mediaResolution", "imageConfig"]),
|
||||
...(Object.keys(generationConfig).length ? { generationConfig } : {}),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const azure: Lowerer = {
|
||||
provider(options) {
|
||||
return {
|
||||
url: string(options.baseURL),
|
||||
headers: compact({ "api-key": string(options.apiKey), ...headers(options.headers) }),
|
||||
body: body(options.body),
|
||||
settings: omit(options, ["apiKey", "baseURL", "headers", "body"]),
|
||||
}
|
||||
},
|
||||
request: openai.request,
|
||||
}
|
||||
|
||||
const bedrock: Lowerer = {
|
||||
provider(options) {
|
||||
return direct(options)
|
||||
},
|
||||
request(options) {
|
||||
return { additionalModelRequestFields: clone(options) }
|
||||
},
|
||||
}
|
||||
|
||||
const openaiCompatible: Lowerer = {
|
||||
provider(options) {
|
||||
return { ...direct(options, ["baseURL"]), url: string(options.baseURL) }
|
||||
},
|
||||
request(options) {
|
||||
const result = clone(options)
|
||||
if (options.reasoningEffort !== undefined) {
|
||||
result.reasoning_effort = options.reasoningEffort
|
||||
delete result.reasoningEffort
|
||||
}
|
||||
return result
|
||||
},
|
||||
}
|
||||
|
||||
const lowerers: Readonly<Record<string, Lowerer>> = {
|
||||
"@ai-sdk/openai": openai,
|
||||
"@ai-sdk/anthropic": anthropic,
|
||||
"@ai-sdk/google-vertex/anthropic": anthropic,
|
||||
"@ai-sdk/google": google,
|
||||
"@ai-sdk/google-vertex": google,
|
||||
"@ai-sdk/azure": azure,
|
||||
"@ai-sdk/amazon-bedrock": bedrock,
|
||||
"@ai-sdk/openai-compatible": openaiCompatible,
|
||||
"@ai-sdk/cerebras": openaiCompatible,
|
||||
"@ai-sdk/deepinfra": openaiCompatible,
|
||||
"@ai-sdk/groq": openaiCompatible,
|
||||
"@ai-sdk/mistral": openaiCompatible,
|
||||
"@ai-sdk/togetherai": openaiCompatible,
|
||||
"@ai-sdk/xai": openaiCompatible,
|
||||
"@openrouter/ai-sdk-provider": openaiCompatible,
|
||||
"ai-gateway-provider": openaiCompatible,
|
||||
"venice-ai-sdk-provider": openaiCompatible,
|
||||
}
|
||||
|
||||
function direct(options: Options, extraKeys: ReadonlyArray<string> = []): ProviderResult {
|
||||
return {
|
||||
headers: headers(options.headers),
|
||||
body: body(options.body),
|
||||
settings: omit(options, ["headers", "body", ...extraKeys]),
|
||||
}
|
||||
}
|
||||
|
||||
function body(input: unknown) {
|
||||
if (!isRecord(input)) return undefined
|
||||
return { ...input }
|
||||
}
|
||||
|
||||
function snake(options: Options) {
|
||||
return Object.fromEntries(Object.entries(options).map(([key, value]) => [snakeKey(key), snakeValue(value)]))
|
||||
}
|
||||
|
||||
function snakeValue(value: unknown): unknown {
|
||||
if (Array.isArray(value)) return value.map(snakeValue)
|
||||
if (!isRecord(value)) return value
|
||||
return Object.fromEntries(Object.entries(value).map(([key, value]) => [snakeKey(key), snakeValue(value)]))
|
||||
}
|
||||
|
||||
function snakeKey(key: string) {
|
||||
return key.replace(/[A-Z]/g, (match) => "_" + match.toLowerCase())
|
||||
}
|
||||
|
||||
function clone(options: Options) {
|
||||
return { ...options }
|
||||
}
|
||||
|
||||
function omit(options: Options, keys: ReadonlyArray<string>) {
|
||||
return Object.fromEntries(Object.entries(options).filter(([key]) => !keys.includes(key)))
|
||||
}
|
||||
|
||||
function pick(options: Options, keys: ReadonlyArray<string>) {
|
||||
return Object.fromEntries(Object.entries(options).filter(([key]) => keys.includes(key)))
|
||||
}
|
||||
|
||||
function headers(input: unknown) {
|
||||
if (!isRecord(input)) return undefined
|
||||
return Object.fromEntries(Object.entries(input).filter((entry): entry is [string, string] => typeof entry[1] === "string"))
|
||||
}
|
||||
|
||||
function compact(input: Record<string, string | undefined>) {
|
||||
const entries = Object.entries(input).filter((entry): entry is [string, string] => entry[1] !== undefined)
|
||||
return entries.length ? Object.fromEntries(entries) : undefined
|
||||
}
|
||||
|
||||
function compactUnknown(input: Record<string, unknown>) {
|
||||
return Object.fromEntries(Object.entries(input).filter((entry) => entry[1] !== undefined))
|
||||
}
|
||||
|
||||
function string(input: unknown) {
|
||||
return typeof input === "string" && input ? input : undefined
|
||||
}
|
||||
|
||||
function bearer(input: unknown) {
|
||||
return typeof input === "string" && input ? `Bearer ${input}` : undefined
|
||||
}
|
||||
|
||||
function isRecord(input: unknown): input is Record<string, unknown> {
|
||||
return typeof input === "object" && input !== null && !Array.isArray(input)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue