Compare commits
4 commits
dev
...
refactor/r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ababb9fbf | ||
|
|
28402a483b | ||
|
|
cb9cb098f2 | ||
|
|
06337878e4 |
58 changed files with 1032 additions and 686 deletions
|
|
@ -19,7 +19,7 @@ export const Color = Schema.Union([
|
||||||
export class Info extends Schema.Class<Info>("AgentV2.Info")({
|
export class Info extends Schema.Class<Info>("AgentV2.Info")({
|
||||||
id: ID,
|
id: ID,
|
||||||
model: ModelV2.Ref.pipe(Schema.optional),
|
model: ModelV2.Ref.pipe(Schema.optional),
|
||||||
options: ProviderV2.Options,
|
request: ProviderV2.Request,
|
||||||
system: Schema.String.pipe(Schema.optional),
|
system: Schema.String.pipe(Schema.optional),
|
||||||
description: Schema.String.pipe(Schema.optional),
|
description: Schema.String.pipe(Schema.optional),
|
||||||
mode: Schema.Literals(["subagent", "primary", "all"]),
|
mode: Schema.Literals(["subagent", "primary", "all"]),
|
||||||
|
|
@ -31,13 +31,9 @@ export class Info extends Schema.Class<Info>("AgentV2.Info")({
|
||||||
static empty(id: ID) {
|
static empty(id: ID) {
|
||||||
return new Info({
|
return new Info({
|
||||||
id,
|
id,
|
||||||
options: {
|
request: {
|
||||||
headers: {},
|
headers: {},
|
||||||
body: {},
|
body: {},
|
||||||
aisdk: {
|
|
||||||
provider: {},
|
|
||||||
request: {},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
mode: "all",
|
mode: "all",
|
||||||
hidden: false,
|
hidden: false,
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,12 @@ function wrapSSE(res: Response, ms: number, ctl: AbortController) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareOptions(model: ModelV2.Info, pkg: string) {
|
function prepareOptions(model: ModelV2.Info, pkg: string) {
|
||||||
const options: Record<string, any> = { name: model.providerID, ...model.options.aisdk.provider }
|
const options: Record<string, any> = {
|
||||||
if (model.endpoint.type === "aisdk" && model.endpoint.url) options.baseURL = model.endpoint.url
|
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 customFetch = options.fetch
|
||||||
const chunkTimeout = options.chunkTimeout
|
const chunkTimeout = options.chunkTimeout
|
||||||
|
|
@ -123,25 +127,25 @@ export const layer = Layer.effect(
|
||||||
|
|
||||||
return Service.of({
|
return Service.of({
|
||||||
language: Effect.fn("AISDK.language")(function* (model) {
|
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)
|
const existing = languages.get(key)
|
||||||
if (existing) return existing
|
if (existing) return existing
|
||||||
if (model.endpoint.type !== "aisdk")
|
if (model.api.type !== "aisdk")
|
||||||
return yield* new InitError({
|
return yield* new InitError({
|
||||||
providerID: model.providerID,
|
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({
|
const sdkKey = JSON.stringify({
|
||||||
providerID: model.providerID,
|
providerID: model.providerID,
|
||||||
endpoint: model.endpoint,
|
api: model.api,
|
||||||
options,
|
options,
|
||||||
})
|
})
|
||||||
const sdk =
|
const sdk =
|
||||||
sdks.get(sdkKey) ??
|
sdks.get(sdkKey) ??
|
||||||
(yield* plugin
|
(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
|
.pipe(initError(model.providerID))).sdk
|
||||||
if (!sdk)
|
if (!sdk)
|
||||||
return yield* new InitError({
|
return yield* new InitError({
|
||||||
|
|
|
||||||
|
|
@ -97,34 +97,29 @@ export const layer = Layer.effect(
|
||||||
|
|
||||||
const resolve = (model: ModelV2.Info) => {
|
const resolve = (model: ModelV2.Info) => {
|
||||||
const provider = state.get().providers.get(model.providerID)!.provider
|
const provider = state.get().providers.get(model.providerID)!.provider
|
||||||
const endpoint =
|
const api =
|
||||||
model.endpoint.type === "unknown"
|
model.api.type === "native" && !model.api.url && Object.keys(model.api.settings).length === 0
|
||||||
? provider.endpoint
|
? provider.api
|
||||||
: model.endpoint.type === "aisdk" && provider.endpoint.type === "aisdk" && !model.endpoint.url
|
: model.api.type === "aisdk" && provider.api.type === "aisdk" && !model.api.url
|
||||||
? { ...model.endpoint, url: provider.endpoint.url }
|
? { ...model.api, url: provider.api.url, settings: { ...provider.api.settings, ...model.api.settings } }
|
||||||
: model.endpoint
|
: model.api.type === "aisdk" && provider.api.type === "aisdk"
|
||||||
const options = {
|
? { ...model.api, settings: { ...provider.api.settings, ...model.api.settings } }
|
||||||
|
: model.api
|
||||||
|
const request = {
|
||||||
headers: {
|
headers: {
|
||||||
...provider.options.headers,
|
...provider.request.headers,
|
||||||
...model.options.headers,
|
...model.request.headers,
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
...provider.options.body,
|
...provider.request.body,
|
||||||
...model.options.body,
|
...model.request.body,
|
||||||
},
|
},
|
||||||
aisdk: {
|
variant: model.request.variant,
|
||||||
provider: {
|
|
||||||
...provider.options.aisdk.provider,
|
|
||||||
...model.options.aisdk.provider,
|
|
||||||
},
|
|
||||||
request: model.options.aisdk.request,
|
|
||||||
},
|
|
||||||
variant: model.options.variant,
|
|
||||||
}
|
}
|
||||||
return new ModelV2.Info({
|
return new ModelV2.Info({
|
||||||
...model,
|
...model,
|
||||||
endpoint,
|
api,
|
||||||
options,
|
request,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,10 +129,10 @@ export const layer = Layer.effect(
|
||||||
return match
|
return match
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizeEndpoint = (item: Draft<ProviderV2.Info> | Draft<ModelV2.Info>) => {
|
const normalizeApi = (item: Draft<ProviderV2.Info> | Draft<ModelV2.Info>) => {
|
||||||
if (item.endpoint.type !== "aisdk" || typeof item.options.aisdk.provider.baseURL !== "string") return
|
if (typeof item.request.body.baseURL !== "string") return
|
||||||
item.endpoint.url = item.options.aisdk.provider.baseURL
|
item.api.url = item.request.body.baseURL
|
||||||
delete item.options.aisdk.provider.baseURL
|
delete item.request.body.baseURL
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = State.create<Data, Editor>({
|
const state = State.create<Data, Editor>({
|
||||||
|
|
@ -157,7 +152,7 @@ export const layer = Layer.effect(
|
||||||
draft.providers.set(providerID, current)
|
draft.providers.set(providerID, current)
|
||||||
}
|
}
|
||||||
fn(current.provider)
|
fn(current.provider)
|
||||||
normalizeEndpoint(current.provider)
|
normalizeApi(current.provider)
|
||||||
},
|
},
|
||||||
remove: (providerID) => {
|
remove: (providerID) => {
|
||||||
draft.providers.delete(providerID)
|
draft.providers.delete(providerID)
|
||||||
|
|
@ -179,7 +174,7 @@ export const layer = Layer.effect(
|
||||||
fn(model)
|
fn(model)
|
||||||
model.id = modelID
|
model.id = modelID
|
||||||
model.providerID = providerID
|
model.providerID = providerID
|
||||||
normalizeEndpoint(model)
|
normalizeApi(model)
|
||||||
},
|
},
|
||||||
remove: (providerID, modelID) => {
|
remove: (providerID, modelID) => {
|
||||||
draft.providers.get(providerID)?.models.delete(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")({
|
export class Info extends Schema.Class<Info>("ConfigV2.Agent")({
|
||||||
model: Schema.String.pipe(Schema.optional),
|
model: Schema.String.pipe(Schema.optional),
|
||||||
variant: 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),
|
system: Schema.String.pipe(Schema.optional),
|
||||||
description: Schema.String.pipe(Schema.optional),
|
description: Schema.String.pipe(Schema.optional),
|
||||||
mode: Schema.Literals(["subagent", "primary", "all"]).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) {
|
if (item.variant !== undefined && agent.model !== undefined) {
|
||||||
agent.model.variant = ModelV2.VariantID.make(item.variant)
|
agent.model.variant = ModelV2.VariantID.make(item.variant)
|
||||||
}
|
}
|
||||||
if (item.options !== undefined) {
|
if (item.request !== undefined) {
|
||||||
Object.assign(agent.options.headers, item.options.headers ?? {})
|
Object.assign(agent.request.headers, item.request.headers ?? {})
|
||||||
Object.assign(agent.options.body, item.options.body ?? {})
|
Object.assign(agent.request.body, item.request.body ?? {})
|
||||||
Object.assign(agent.options.aisdk.provider, item.options.aisdk?.provider ?? {})
|
|
||||||
Object.assign(agent.options.aisdk.request, item.options.aisdk?.request ?? {})
|
|
||||||
}
|
}
|
||||||
if (item.system !== undefined) agent.system = item.system
|
if (item.system !== undefined) agent.system = item.system
|
||||||
if (item.description !== undefined) agent.description = item.description
|
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.name !== undefined) provider.name = item.name
|
||||||
if (item.env !== undefined) provider.env = [...item.env]
|
if (item.env !== undefined) provider.env = [...item.env]
|
||||||
provider.enabled = { via: "custom", data: {} }
|
provider.enabled = { via: "custom", data: {} }
|
||||||
if (item.endpoint !== undefined) provider.endpoint = { ...item.endpoint }
|
if (item.api !== undefined) provider.api = { ...item.api }
|
||||||
if (item.options !== undefined) {
|
if (item.request !== undefined) {
|
||||||
Object.assign(provider.options.headers, item.options.headers ?? {})
|
Object.assign(provider.request.headers, item.request.headers ?? {})
|
||||||
Object.assign(provider.options.body, item.options.body ?? {})
|
Object.assign(provider.request.body, item.request.body ?? {})
|
||||||
Object.assign(provider.options.aisdk.provider, item.options.aisdk?.provider ?? {})
|
|
||||||
Object.assign(provider.options.aisdk.request, item.options.aisdk?.request ?? {})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -37,7 +35,7 @@ export const Plugin = PluginV2.define({
|
||||||
if (config.api_id !== undefined) model.apiID = config.api_id
|
if (config.api_id !== undefined) model.apiID = config.api_id
|
||||||
if (config.family !== undefined) model.family = config.family
|
if (config.family !== undefined) model.family = config.family
|
||||||
if (config.name !== undefined) model.name = config.name
|
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) {
|
if (config.capabilities !== undefined) {
|
||||||
model.capabilities = {
|
model.capabilities = {
|
||||||
tools: config.capabilities.tools,
|
tools: config.capabilities.tools,
|
||||||
|
|
@ -45,12 +43,10 @@ export const Plugin = PluginV2.define({
|
||||||
output: [...config.capabilities.output],
|
output: [...config.capabilities.output],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (config.options !== undefined) {
|
if (config.request !== undefined) {
|
||||||
Object.assign(model.options.headers, config.options.headers ?? {})
|
Object.assign(model.request.headers, config.request.headers ?? {})
|
||||||
Object.assign(model.options.body, config.options.body ?? {})
|
Object.assign(model.request.body, config.request.body ?? {})
|
||||||
Object.assign(model.options.aisdk.provider, config.options.aisdk?.provider ?? {})
|
if (config.request.variant !== undefined) model.request.variant = config.request.variant
|
||||||
Object.assign(model.options.aisdk.request, config.options.aisdk?.request ?? {})
|
|
||||||
if (config.options.variant !== undefined) model.options.variant = config.options.variant
|
|
||||||
}
|
}
|
||||||
if (config.variants !== undefined) {
|
if (config.variants !== undefined) {
|
||||||
for (const variant of config.variants) {
|
for (const variant of config.variants) {
|
||||||
|
|
@ -60,17 +56,11 @@ export const Plugin = PluginV2.define({
|
||||||
id: variant.id,
|
id: variant.id,
|
||||||
headers: {},
|
headers: {},
|
||||||
body: {},
|
body: {},
|
||||||
aisdk: {
|
|
||||||
provider: {},
|
|
||||||
request: {},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
model.variants.push(existing)
|
model.variants.push(existing)
|
||||||
}
|
}
|
||||||
Object.assign(existing.headers, variant.headers ?? {})
|
Object.assign(existing.headers, variant.headers ?? {})
|
||||||
Object.assign(existing.body, variant.body ?? {})
|
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) {
|
if (config.cost !== undefined) {
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,9 @@ import { Schema } from "effect"
|
||||||
import { ProviderV2 } from "../provider"
|
import { ProviderV2 } from "../provider"
|
||||||
import { ModelV2 } from "../model"
|
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),
|
headers: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
|
||||||
body: Schema.Record(Schema.String, Schema.Unknown).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")({
|
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),
|
api_id: ModelV2.ID.pipe(Schema.optional),
|
||||||
family: ModelV2.Family.pipe(Schema.optional),
|
family: ModelV2.Family.pipe(Schema.optional),
|
||||||
name: Schema.String.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),
|
capabilities: ModelV2.Capabilities.pipe(Schema.optional),
|
||||||
options: Schema.Struct({
|
request: Schema.Struct({
|
||||||
...Options.fields,
|
...Request.fields,
|
||||||
variant: Schema.String.pipe(Schema.optional),
|
variant: Schema.String.pipe(Schema.optional),
|
||||||
}).pipe(Schema.optional),
|
}).pipe(Schema.optional),
|
||||||
variants: Schema.Struct({
|
variants: Schema.Struct({
|
||||||
id: ModelV2.VariantID,
|
id: ModelV2.VariantID,
|
||||||
...Options.fields,
|
...Request.fields,
|
||||||
}).pipe(Schema.Array, Schema.optional),
|
}).pipe(Schema.Array, Schema.optional),
|
||||||
cost: Schema.Union([Cost, Cost.pipe(Schema.Array)]).pipe(Schema.optional),
|
cost: Schema.Union([Cost, Cost.pipe(Schema.Array)]).pipe(Schema.optional),
|
||||||
disabled: Schema.Boolean.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")({
|
export class Info extends Schema.Class<Info>("ConfigV2.Provider")({
|
||||||
name: Schema.String.pipe(Schema.optional),
|
name: Schema.String.pipe(Schema.optional),
|
||||||
env: Schema.String.pipe(Schema.Array, Schema.optional),
|
env: Schema.String.pipe(Schema.Array, Schema.optional),
|
||||||
endpoint: ProviderV2.Endpoint.pipe(Schema.optional),
|
api: ProviderV2.Api.pipe(Schema.optional),
|
||||||
options: Options.pipe(Schema.optional),
|
request: Request.pipe(Schema.optional),
|
||||||
models: Schema.Record(Schema.String, Model).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,
|
providerID: ProviderV2.ID,
|
||||||
family: Family.pipe(Schema.optional),
|
family: Family.pipe(Schema.optional),
|
||||||
name: Schema.String,
|
name: Schema.String,
|
||||||
endpoint: ProviderV2.Endpoint,
|
api: ProviderV2.Api,
|
||||||
capabilities: Capabilities,
|
capabilities: Capabilities,
|
||||||
options: Schema.Struct({
|
request: Schema.Struct({
|
||||||
...ProviderV2.Options.fields,
|
...ProviderV2.Request.fields,
|
||||||
variant: Schema.String.pipe(Schema.optional),
|
variant: Schema.String.pipe(Schema.optional),
|
||||||
}),
|
}),
|
||||||
variants: Schema.Struct({
|
variants: Schema.Struct({
|
||||||
id: VariantID,
|
id: VariantID,
|
||||||
...ProviderV2.Options.fields,
|
...ProviderV2.Request.fields,
|
||||||
}).pipe(Schema.Array),
|
}).pipe(Schema.Array),
|
||||||
time: Schema.Struct({
|
time: Schema.Struct({
|
||||||
released: DateTimeUtcFromMillis,
|
released: DateTimeUtcFromMillis,
|
||||||
|
|
@ -69,26 +69,23 @@ export class Info extends Schema.Class<Info>("ModelV2.Info")({
|
||||||
}),
|
}),
|
||||||
}) {
|
}) {
|
||||||
static empty(providerID: ProviderV2.ID, modelID: ID): Info {
|
static empty(providerID: ProviderV2.ID, modelID: ID): Info {
|
||||||
return {
|
return new Info({
|
||||||
id: modelID,
|
id: modelID,
|
||||||
apiID: modelID,
|
apiID: modelID,
|
||||||
providerID,
|
providerID,
|
||||||
name: modelID,
|
name: modelID,
|
||||||
endpoint: {
|
api: {
|
||||||
type: "unknown",
|
type: "native",
|
||||||
|
settings: {},
|
||||||
},
|
},
|
||||||
capabilities: {
|
capabilities: {
|
||||||
tools: false,
|
tools: false,
|
||||||
input: [],
|
input: [],
|
||||||
output: [],
|
output: [],
|
||||||
},
|
},
|
||||||
options: {
|
request: {
|
||||||
headers: {},
|
headers: {},
|
||||||
body: {},
|
body: {},
|
||||||
aisdk: {
|
|
||||||
provider: {},
|
|
||||||
request: {},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
variants: [],
|
variants: [],
|
||||||
time: {
|
time: {
|
||||||
|
|
@ -101,7 +98,7 @@ export class Info extends Schema.Class<Info>("ModelV2.Info")({
|
||||||
context: 0,
|
context: 0,
|
||||||
output: 0,
|
output: 0,
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,10 @@ export const AccountPlugin = PluginV2.define({
|
||||||
service: account.serviceID,
|
service: account.serviceID,
|
||||||
}
|
}
|
||||||
if (account.credential.type === "api") {
|
if (account.credential.type === "api") {
|
||||||
provider.options.aisdk.provider.apiKey = account.credential.key
|
provider.request.body.apiKey = account.credential.key
|
||||||
Object.assign(provider.options.aisdk.provider, account.credential.metadata ?? {})
|
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),
|
id: ModelV2.VariantID.make(id),
|
||||||
headers: { ...(item.provider?.headers ?? {}) },
|
headers: { ...(item.provider?.headers ?? {}) },
|
||||||
body: { ...(item.provider?.body ?? {}) },
|
body: { ...(item.provider?.body ?? {}) },
|
||||||
aisdk: {
|
|
||||||
provider: {},
|
|
||||||
request: {},
|
|
||||||
},
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,14 +62,16 @@ export const ModelsDevPlugin = PluginV2.define({
|
||||||
catalog.provider.update(providerID, (provider) => {
|
catalog.provider.update(providerID, (provider) => {
|
||||||
provider.name = item.name
|
provider.name = item.name
|
||||||
provider.env = [...item.env]
|
provider.env = [...item.env]
|
||||||
provider.endpoint = item.npm
|
provider.api = item.npm
|
||||||
? {
|
? {
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: item.npm,
|
package: item.npm,
|
||||||
url: item.api,
|
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) => {
|
catalog.model.update(providerID, modelID, (draft) => {
|
||||||
draft.name = model.name
|
draft.name = model.name
|
||||||
draft.family = model.family ? ModelV2.Family.make(model.family) : undefined
|
draft.family = model.family ? ModelV2.Family.make(model.family) : undefined
|
||||||
draft.endpoint = model.provider?.npm
|
draft.api = model.provider?.npm
|
||||||
? {
|
? {
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: model.provider?.npm,
|
package: model.provider?.npm,
|
||||||
url: model.provider.api,
|
url: model.provider.api,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
type: "unknown",
|
type: "native",
|
||||||
|
url: model.provider?.api,
|
||||||
|
settings: {},
|
||||||
}
|
}
|
||||||
draft.capabilities = {
|
draft.capabilities = {
|
||||||
tools: model.tool_call,
|
tools: model.tool_call,
|
||||||
|
|
|
||||||
|
|
@ -52,15 +52,15 @@ export const AmazonBedrockPlugin = PluginV2.define({
|
||||||
return {
|
return {
|
||||||
"catalog.transform": Effect.fn(function* (evt) {
|
"catalog.transform": Effect.fn(function* (evt) {
|
||||||
for (const item of evt.provider.list()) {
|
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/amazon-bedrock") continue
|
if (item.provider.api.package !== "@ai-sdk/amazon-bedrock") continue
|
||||||
evt.provider.update(item.provider.id, (provider) => {
|
evt.provider.update(item.provider.id, (provider) => {
|
||||||
if (provider.endpoint.type !== "aisdk") return
|
if (provider.api.type !== "aisdk") return
|
||||||
if (typeof provider.options.aisdk.provider.endpoint !== "string") return
|
if (typeof provider.request.body.endpoint !== "string") return
|
||||||
// The AI SDK expects a base URL, but users configure Bedrock private/VPC
|
// The AI SDK expects a base URL, but users configure Bedrock private/VPC
|
||||||
// endpoints as `endpoint`; move it into the catalog endpoint URL once.
|
// endpoints as `endpoint`; move it into the catalog endpoint URL once.
|
||||||
provider.endpoint.url = provider.options.aisdk.provider.endpoint
|
provider.api.url = provider.request.body.endpoint
|
||||||
delete provider.options.aisdk.provider.endpoint
|
delete provider.request.body.endpoint
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ export const AnthropicPlugin = PluginV2.define({
|
||||||
return {
|
return {
|
||||||
"catalog.transform": Effect.fn(function* (evt) {
|
"catalog.transform": Effect.fn(function* (evt) {
|
||||||
for (const item of evt.provider.list()) {
|
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/anthropic") continue
|
if (item.provider.api.package !== "@ai-sdk/anthropic") continue
|
||||||
evt.provider.update(item.provider.id, (provider) => {
|
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"
|
"interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,14 @@ export const AzurePlugin = PluginV2.define({
|
||||||
return {
|
return {
|
||||||
"catalog.transform": Effect.fn(function* (evt) {
|
"catalog.transform": Effect.fn(function* (evt) {
|
||||||
for (const item of evt.provider.list()) {
|
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/azure") continue
|
if (item.provider.api.package !== "@ai-sdk/azure") continue
|
||||||
const configured = item.provider.options.aisdk.provider.resourceName
|
const configured = item.provider.request.body.resourceName
|
||||||
const resourceName =
|
const resourceName =
|
||||||
typeof configured === "string" && configured.trim() !== "" ? configured : process.env.AZURE_RESOURCE_NAME
|
typeof configured === "string" && configured.trim() !== "" ? configured : process.env.AZURE_RESOURCE_NAME
|
||||||
if (!resourceName) continue
|
if (!resourceName) continue
|
||||||
evt.provider.update(item.provider.id, (provider) => {
|
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 (
|
if (
|
||||||
!evt.options.resourceName &&
|
!evt.options.resourceName &&
|
||||||
!evt.options.baseURL &&
|
!evt.options.baseURL &&
|
||||||
(evt.model.endpoint.type !== "aisdk" || !evt.model.endpoint.url)
|
(evt.model.api.type !== "aisdk" || !evt.model.api.url)
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"AZURE_RESOURCE_NAME is missing, set it using env var or reconnecting the azure provider and setting it",
|
"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
|
const resourceName = process.env.AZURE_COGNITIVE_SERVICES_RESOURCE_NAME
|
||||||
if (!resourceName) return
|
if (!resourceName) return
|
||||||
for (const item of evt.provider.list()) {
|
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/openai-compatible") continue
|
if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue
|
||||||
if (!item.provider.id.includes("azure-cognitive-services")) continue
|
if (!item.provider.id.includes("azure-cognitive-services")) continue
|
||||||
evt.provider.update(item.provider.id, (provider) => {
|
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 {
|
return {
|
||||||
"catalog.transform": Effect.fn(function* (ctx) {
|
"catalog.transform": Effect.fn(function* (ctx) {
|
||||||
for (const item of ctx.provider.list()) {
|
for (const item of ctx.provider.list()) {
|
||||||
if (item.provider.endpoint.type !== "aisdk") continue
|
if (item.provider.api.type !== "aisdk") continue
|
||||||
if (item.provider.endpoint.package !== "@ai-sdk/cerebras") continue
|
if (item.provider.api.package !== "@ai-sdk/cerebras") continue
|
||||||
ctx.provider.update(item.provider.id, (provider) => {
|
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)
|
const item = evt.provider.get(providerID)
|
||||||
if (!item) return
|
if (!item) return
|
||||||
evt.provider.update(item.provider.id, (provider) => {
|
evt.provider.update(item.provider.id, (provider) => {
|
||||||
if (provider.endpoint.type !== "aisdk") return
|
if (provider.api.type !== "aisdk") return
|
||||||
if (provider.endpoint.url) return
|
if (provider.api.url) return
|
||||||
const accountId = resolveAccountId(provider.options.aisdk.provider)
|
const accountId = resolveAccountId(provider.request.body)
|
||||||
if (accountId) provider.endpoint.url = workersEndpoint(accountId)
|
if (accountId) provider.api.url = workersEndpoint(accountId)
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
"aisdk.sdk": Effect.fn(function* (evt) {
|
"aisdk.sdk": Effect.fn(function* (evt) {
|
||||||
if (evt.model.providerID !== providerID) return
|
if (evt.model.providerID !== providerID) return
|
||||||
if (evt.package !== "@ai-sdk/openai-compatible") 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"))
|
const mod = yield* Effect.promise(() => import("@ai-sdk/openai-compatible"))
|
||||||
evt.sdk = mod.createOpenAICompatible(sdkOptions(evt.options) as any)
|
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`
|
return `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/v1`
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasWorkersEndpoint(endpoint: ProviderV2.Endpoint) {
|
function hasWorkersEndpoint(api: ProviderV2.Api) {
|
||||||
return endpoint.type === "aisdk" && Boolean(endpoint.url)
|
return api.type === "aisdk" && Boolean(api.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
function sdkOptions(options: Record<string, any>) {
|
function sdkOptions(options: Record<string, any>) {
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,12 @@ export const GitLabPlugin = PluginV2.define({
|
||||||
if (evt.model.apiID.startsWith("duo-workflow-")) {
|
if (evt.model.apiID.startsWith("duo-workflow-")) {
|
||||||
const gitlab = yield* Effect.promise(() => import("gitlab-ai-provider")).pipe(Effect.orDie)
|
const gitlab = yield* Effect.promise(() => import("gitlab-ai-provider")).pipe(Effect.orDie)
|
||||||
const workflowRef =
|
const workflowRef =
|
||||||
typeof evt.model.options.aisdk.request.workflowRef === "string"
|
typeof evt.model.request.body.workflowRef === "string"
|
||||||
? evt.model.options.aisdk.request.workflowRef
|
? evt.model.request.body.workflowRef
|
||||||
: undefined
|
: undefined
|
||||||
const workflowDefinition =
|
const workflowDefinition =
|
||||||
typeof evt.model.options.aisdk.request.workflowDefinition === "string"
|
typeof evt.model.request.body.workflowDefinition === "string"
|
||||||
? evt.model.options.aisdk.request.workflowDefinition
|
? evt.model.request.body.workflowDefinition
|
||||||
: undefined
|
: undefined
|
||||||
const language = evt.sdk.workflowChat(
|
const language = evt.sdk.workflowChat(
|
||||||
gitlab.isWorkflowModel(evt.model.apiID) ? evt.model.apiID : "duo-workflow",
|
gitlab.isWorkflowModel(evt.model.apiID) ? evt.model.apiID : "duo-workflow",
|
||||||
|
|
|
||||||
|
|
@ -60,22 +60,22 @@ export const GoogleVertexPlugin = PluginV2.define({
|
||||||
return {
|
return {
|
||||||
"catalog.transform": Effect.fn(function* (evt) {
|
"catalog.transform": Effect.fn(function* (evt) {
|
||||||
for (const item of evt.provider.list()) {
|
for (const item of evt.provider.list()) {
|
||||||
if (item.provider.endpoint.type !== "aisdk") continue
|
if (item.provider.api.type !== "aisdk") continue
|
||||||
if (
|
if (
|
||||||
item.provider.endpoint.package !== "@ai-sdk/google-vertex" &&
|
item.provider.api.package !== "@ai-sdk/google-vertex" &&
|
||||||
!item.provider.endpoint.package.includes("@ai-sdk/openai-compatible")
|
!item.provider.api.package.includes("@ai-sdk/openai-compatible")
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
const project = resolveProject(item.provider.options.aisdk.provider)
|
const project = resolveProject(item.provider.request.body)
|
||||||
const location = String(resolveLocation(item.provider.options.aisdk.provider))
|
const location = String(resolveLocation(item.provider.request.body))
|
||||||
evt.provider.update(item.provider.id, (provider) => {
|
evt.provider.update(item.provider.id, (provider) => {
|
||||||
if (project) provider.options.aisdk.provider.project = project
|
if (project) provider.request.body.project = project
|
||||||
provider.options.aisdk.provider.location = location
|
provider.request.body.location = location
|
||||||
if (provider.endpoint.type === "aisdk" && provider.endpoint.url) {
|
if (provider.api.type === "aisdk" && provider.api.url) {
|
||||||
provider.endpoint.url = replaceVertexVars(provider.endpoint.url, project, location)
|
provider.api.url = replaceVertexVars(provider.api.url, project, location)
|
||||||
}
|
}
|
||||||
if (provider.endpoint.type === "aisdk" && provider.endpoint.package.includes("@ai-sdk/openai-compatible")) {
|
if (provider.api.type === "aisdk" && provider.api.package.includes("@ai-sdk/openai-compatible")) {
|
||||||
provider.options.aisdk.provider.fetch = authFetch(provider.options.aisdk.provider.fetch)
|
provider.request.body.fetch = authFetch(provider.request.body.fetch)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -111,21 +111,21 @@ export const GoogleVertexAnthropicPlugin = PluginV2.define({
|
||||||
return {
|
return {
|
||||||
"catalog.transform": Effect.fn(function* (evt) {
|
"catalog.transform": Effect.fn(function* (evt) {
|
||||||
for (const item of evt.provider.list()) {
|
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/anthropic") continue
|
if (item.provider.api.package !== "@ai-sdk/google-vertex/anthropic") continue
|
||||||
const project =
|
const project =
|
||||||
item.provider.options.aisdk.provider.project ??
|
item.provider.request.body.project ??
|
||||||
process.env.GOOGLE_CLOUD_PROJECT ??
|
process.env.GOOGLE_CLOUD_PROJECT ??
|
||||||
process.env.GCP_PROJECT ??
|
process.env.GCP_PROJECT ??
|
||||||
process.env.GCLOUD_PROJECT
|
process.env.GCLOUD_PROJECT
|
||||||
const location =
|
const location =
|
||||||
item.provider.options.aisdk.provider.location ??
|
item.provider.request.body.location ??
|
||||||
process.env.GOOGLE_CLOUD_LOCATION ??
|
process.env.GOOGLE_CLOUD_LOCATION ??
|
||||||
process.env.VERTEX_LOCATION ??
|
process.env.VERTEX_LOCATION ??
|
||||||
"global"
|
"global"
|
||||||
evt.provider.update(item.provider.id, (provider) => {
|
evt.provider.update(item.provider.id, (provider) => {
|
||||||
if (project) provider.options.aisdk.provider.project = project
|
if (project) provider.request.body.project = project
|
||||||
provider.options.aisdk.provider.location = location
|
provider.request.body.location = location
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@ export const KiloPlugin = PluginV2.define({
|
||||||
return {
|
return {
|
||||||
"catalog.transform": Effect.fn(function* (evt) {
|
"catalog.transform": Effect.fn(function* (evt) {
|
||||||
for (const item of evt.provider.list()) {
|
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/openai-compatible") continue
|
if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue
|
||||||
if (item.provider.endpoint.url !== "https://api.kilo.ai/api/gateway") continue
|
if (item.provider.api.url !== "https://api.kilo.ai/api/gateway") continue
|
||||||
evt.provider.update(item.provider.id, (provider) => {
|
evt.provider.update(item.provider.id, (provider) => {
|
||||||
provider.options.headers["HTTP-Referer"] = "https://opencode.ai/"
|
provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
|
||||||
provider.options.headers["X-Title"] = "opencode"
|
provider.request.headers["X-Title"] = "opencode"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,13 @@ export const LLMGatewayPlugin = PluginV2.define({
|
||||||
"catalog.transform": Effect.fn(function* (evt) {
|
"catalog.transform": Effect.fn(function* (evt) {
|
||||||
for (const item of evt.provider.list()) {
|
for (const item of evt.provider.list()) {
|
||||||
if (item.provider.enabled === false) continue
|
if (item.provider.enabled === false) continue
|
||||||
if (item.provider.endpoint.type !== "aisdk") continue
|
if (item.provider.api.type !== "aisdk") continue
|
||||||
if (item.provider.endpoint.package !== "@ai-sdk/openai-compatible") continue
|
if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue
|
||||||
if (item.provider.endpoint.url !== "https://api.llmgateway.io/v1") continue
|
if (item.provider.api.url !== "https://api.llmgateway.io/v1") continue
|
||||||
evt.provider.update(item.provider.id, (provider) => {
|
evt.provider.update(item.provider.id, (provider) => {
|
||||||
provider.options.headers["HTTP-Referer"] = "https://opencode.ai/"
|
provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
|
||||||
provider.options.headers["X-Title"] = "opencode"
|
provider.request.headers["X-Title"] = "opencode"
|
||||||
provider.options.headers["X-Source"] = "opencode"
|
provider.request.headers["X-Source"] = "opencode"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,13 @@ export const NvidiaPlugin = PluginV2.define({
|
||||||
return {
|
return {
|
||||||
"catalog.transform": Effect.fn(function* (evt) {
|
"catalog.transform": Effect.fn(function* (evt) {
|
||||||
for (const item of evt.provider.list()) {
|
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/openai-compatible") continue
|
if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue
|
||||||
if (item.provider.endpoint.url !== "https://integrate.api.nvidia.com/v1") continue
|
if (item.provider.api.url !== "https://integrate.api.nvidia.com/v1") continue
|
||||||
evt.provider.update(item.provider.id, (provider) => {
|
evt.provider.update(item.provider.id, (provider) => {
|
||||||
provider.options.headers["HTTP-Referer"] = "https://opencode.ai/"
|
provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
|
||||||
provider.options.headers["X-Title"] = "opencode"
|
provider.request.headers["X-Title"] = "opencode"
|
||||||
provider.options.headers["X-BILLING-INVOKE-ORIGIN"] ??= "OpenCode"
|
provider.request.headers["X-BILLING-INVOKE-ORIGIN"] ??= "OpenCode"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ export const OpenAIPlugin = PluginV2.define({
|
||||||
}),
|
}),
|
||||||
"catalog.transform": Effect.fn(function* (evt) {
|
"catalog.transform": Effect.fn(function* (evt) {
|
||||||
for (const item of evt.provider.list()) {
|
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/openai") continue
|
if (item.provider.api.package !== "@ai-sdk/openai") continue
|
||||||
if (!item.models.has(ModelV2.ID.make("gpt-5-chat-latest"))) 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) => {
|
evt.model.update(item.provider.id, ModelV2.ID.make("gpt-5-chat-latest"), (model) => {
|
||||||
// OpenAIPlugin sends OpenAI models through Responses; this alias is a
|
// OpenAIPlugin sends OpenAI models through Responses; this alias is a
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ export const OpencodePlugin = PluginV2.define({
|
||||||
hasKey = Boolean(
|
hasKey = Boolean(
|
||||||
process.env.OPENCODE_API_KEY ||
|
process.env.OPENCODE_API_KEY ||
|
||||||
item.provider.env.some((env) => process.env[env]) ||
|
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"),
|
(item.provider.enabled && item.provider.enabled.via === "account"),
|
||||||
)
|
)
|
||||||
evt.provider.update(item.provider.id, (provider) => {
|
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
|
if (hasKey) return
|
||||||
for (const model of item.models.values()) {
|
for (const model of item.models.values()) {
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@ export const OpenRouterPlugin = PluginV2.define({
|
||||||
return {
|
return {
|
||||||
"catalog.transform": Effect.fn(function* (evt) {
|
"catalog.transform": Effect.fn(function* (evt) {
|
||||||
for (const item of evt.provider.list()) {
|
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 !== "@openrouter/ai-sdk-provider") continue
|
if (item.provider.api.package !== "@openrouter/ai-sdk-provider") continue
|
||||||
evt.provider.update(item.provider.id, (provider) => {
|
evt.provider.update(item.provider.id, (provider) => {
|
||||||
provider.options.headers["HTTP-Referer"] = "https://opencode.ai/"
|
provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
|
||||||
provider.options.headers["X-Title"] = "opencode"
|
provider.request.headers["X-Title"] = "opencode"
|
||||||
})
|
})
|
||||||
for (const modelID of [ModelV2.ID.make("gpt-5-chat-latest"), ModelV2.ID.make("openai/gpt-5-chat")]) {
|
for (const modelID of [ModelV2.ID.make("gpt-5-chat-latest"), ModelV2.ID.make("openai/gpt-5-chat")]) {
|
||||||
if (!item.models.has(modelID)) continue
|
if (!item.models.has(modelID)) continue
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@ export const VercelPlugin = PluginV2.define({
|
||||||
return {
|
return {
|
||||||
"catalog.transform": Effect.fn(function* (evt) {
|
"catalog.transform": Effect.fn(function* (evt) {
|
||||||
for (const item of evt.provider.list()) {
|
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/vercel") continue
|
if (item.provider.api.package !== "@ai-sdk/vercel") continue
|
||||||
evt.provider.update(item.provider.id, (provider) => {
|
evt.provider.update(item.provider.id, (provider) => {
|
||||||
provider.options.headers["http-referer"] = "https://opencode.ai/"
|
provider.request.headers["http-referer"] = "https://opencode.ai/"
|
||||||
provider.options.headers["x-title"] = "opencode"
|
provider.request.headers["x-title"] = "opencode"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@ export const ZenmuxPlugin = PluginV2.define({
|
||||||
return {
|
return {
|
||||||
"catalog.transform": Effect.fn(function* (evt) {
|
"catalog.transform": Effect.fn(function* (evt) {
|
||||||
for (const item of evt.provider.list()) {
|
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/openai-compatible") continue
|
if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue
|
||||||
if (item.provider.endpoint.url !== "https://zenmux.ai/api/v1") continue
|
if (item.provider.api.url !== "https://zenmux.ai/api/v1") continue
|
||||||
evt.provider.update(item.provider.id, (provider) => {
|
evt.provider.update(item.provider.id, (provider) => {
|
||||||
provider.options.headers["HTTP-Referer"] ??= "https://opencode.ai/"
|
provider.request.headers["HTTP-Referer"] ??= "https://opencode.ai/"
|
||||||
provider.options.headers["X-Title"] ??= "opencode"
|
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 const ModelID = Schema.String.pipe(Schema.brand("ModelID"))
|
||||||
export type ModelID = typeof ModelID.Type
|
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({
|
const AISDK = Schema.Struct({
|
||||||
type: Schema.Literal("aisdk"),
|
type: Schema.Literal("aisdk"),
|
||||||
package: Schema.String,
|
package: Schema.String,
|
||||||
url: Schema.String.pipe(Schema.optional),
|
url: Schema.String.pipe(Schema.optional),
|
||||||
|
settings: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||||
})
|
})
|
||||||
|
|
||||||
const AnthropicMessages = Schema.Struct({
|
const Native = Schema.Struct({
|
||||||
type: Schema.Literal("anthropic/messages"),
|
type: Schema.Literal("native"),
|
||||||
url: Schema.String,
|
url: Schema.String.pipe(Schema.optional),
|
||||||
|
settings: Schema.Record(Schema.String, Schema.Unknown),
|
||||||
})
|
})
|
||||||
|
|
||||||
const UnknownEndpoint = Schema.Struct({
|
export const Api = Schema.Union([AISDK, Native]).pipe(Schema.toTaggedUnion("type"))
|
||||||
type: Schema.Literal("unknown"),
|
export type Api = typeof Api.Type
|
||||||
})
|
|
||||||
|
|
||||||
export const Endpoint = Schema.Union([
|
export const Request = Schema.Struct({
|
||||||
UnknownEndpoint,
|
|
||||||
OpenAIResponses,
|
|
||||||
OpenAICompletions,
|
|
||||||
AnthropicMessages,
|
|
||||||
AISDK,
|
|
||||||
]).pipe(Schema.toTaggedUnion("type"))
|
|
||||||
export type Endpoint = typeof Endpoint.Type
|
|
||||||
|
|
||||||
export const Options = Schema.Struct({
|
|
||||||
headers: Schema.Record(Schema.String, Schema.String),
|
headers: Schema.Record(Schema.String, Schema.String),
|
||||||
body: Schema.Record(Schema.String, Schema.Any),
|
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")({
|
export class Info extends Schema.Class<Info>("ProviderV2.Info")({
|
||||||
id: ID,
|
id: ID,
|
||||||
|
|
@ -98,26 +66,23 @@ export class Info extends Schema.Class<Info>("ProviderV2.Info")({
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
env: Schema.String.pipe(Schema.Array),
|
env: Schema.String.pipe(Schema.Array),
|
||||||
endpoint: Endpoint,
|
api: Api,
|
||||||
options: Options,
|
request: Request,
|
||||||
}) {
|
}) {
|
||||||
static empty(providerID: ID): Info {
|
static empty(providerID: ID): Info {
|
||||||
return {
|
return new Info({
|
||||||
id: providerID,
|
id: providerID,
|
||||||
name: providerID,
|
name: providerID,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
env: [],
|
env: [],
|
||||||
endpoint: {
|
api: {
|
||||||
type: "unknown",
|
type: "native",
|
||||||
|
settings: {},
|
||||||
},
|
},
|
||||||
options: {
|
request: {
|
||||||
headers: {},
|
headers: {},
|
||||||
body: {},
|
body: {},
|
||||||
aisdk: {
|
|
||||||
provider: {},
|
|
||||||
request: {},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { ConfigAgentV1 } from "./agent"
|
||||||
import { ConfigMCPV1 } from "./mcp"
|
import { ConfigMCPV1 } from "./mcp"
|
||||||
import { ConfigPermissionV1 } from "./permission"
|
import { ConfigPermissionV1 } from "./permission"
|
||||||
import { ConfigProviderV1 } from "./provider"
|
import { ConfigProviderV1 } from "./provider"
|
||||||
|
import { ConfigProviderOptionsV1 } from "./provider-options"
|
||||||
|
|
||||||
const keys = new Set([
|
const keys = new Set([
|
||||||
"logLevel",
|
"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),
|
...Object.entries(info.mode ?? {}).map(([name, agent]) => [name, { ...agent, mode: "primary" as const }] as const),
|
||||||
]
|
]
|
||||||
if (!entries.length) return undefined
|
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) {
|
function migrateAgent(info: ConfigAgentV1.Info) {
|
||||||
|
|
@ -111,7 +112,7 @@ function migrateAgent(info: ConfigAgentV1.Info) {
|
||||||
return {
|
return {
|
||||||
model: info.model,
|
model: info.model,
|
||||||
variant: info.variant,
|
variant: info.variant,
|
||||||
options: Object.keys(body).length ? { body } : undefined,
|
request: Object.keys(body).length ? { body } : undefined,
|
||||||
system: info.prompt,
|
system: info.prompt,
|
||||||
description: info.description,
|
description: info.description,
|
||||||
mode: info.mode,
|
mode: info.mode,
|
||||||
|
|
@ -160,22 +161,27 @@ function providers(info?: Readonly<Record<string, ConfigProviderV1.Info>>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function migrateProvider(info: ConfigProviderV1.Info) {
|
function migrateProvider(info: ConfigProviderV1.Info) {
|
||||||
|
const lowerer = ConfigProviderOptionsV1.get(info.npm)
|
||||||
|
const options = lowerer.provider(info.options ?? {})
|
||||||
return {
|
return {
|
||||||
name: info.name,
|
name: info.name,
|
||||||
env: info.env,
|
env: info.env,
|
||||||
endpoint: info.npm && {
|
api: info.npm
|
||||||
|
? {
|
||||||
type: "aisdk" as const,
|
type: "aisdk" as const,
|
||||||
package: info.npm,
|
package: info.npm,
|
||||||
url: info.api ?? (typeof info.options?.baseURL === "string" ? info.options.baseURL : undefined),
|
url: info.api ?? options.url,
|
||||||
},
|
settings: options.settings ?? {},
|
||||||
options: info.options && { body: info.options },
|
}
|
||||||
|
: undefined,
|
||||||
|
request: info.options && { headers: options.headers, body: options.body },
|
||||||
models:
|
models:
|
||||||
info.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 && [
|
const costs = info.cost && [
|
||||||
{
|
{
|
||||||
input: info.cost.input,
|
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
|
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 ?? [] }
|
? { tools: info.tool_call ?? false, input: info.modalities?.input ?? [], output: info.modalities?.output ?? [] }
|
||||||
: undefined
|
: undefined
|
||||||
|
const lowerer = ConfigProviderOptionsV1.get(info.provider?.npm ?? packageName)
|
||||||
return {
|
return {
|
||||||
api_id: info.id,
|
api_id: info.id,
|
||||||
family: info.family,
|
family: info.family,
|
||||||
name: info.name,
|
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,
|
capabilities,
|
||||||
options: (info.headers || info.options) && { headers: info.headers, body: info.options },
|
request: (info.headers || info.options) && {
|
||||||
variants: info.variants && Object.entries(info.variants).map(([id, options]) => ({ id, body: 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,
|
cost: costs,
|
||||||
disabled: info.status === "deprecated" ? true : undefined,
|
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)))
|
||||||
|
}
|
||||||
|
|
|
||||||
209
packages/core/src/v1/config/provider-options.ts
Normal file
209
packages/core/src/v1/config/provider-options.ts
Normal file
|
|
@ -0,0 +1,209 @@
|
||||||
|
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 = { ...(isRecord(result.metadata) ? result.metadata : {}), user_id: options.metadata.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)
|
||||||
|
}
|
||||||
|
|
@ -32,10 +32,7 @@ function context(
|
||||||
updates.push({
|
updates.push({
|
||||||
id: providerID,
|
id: providerID,
|
||||||
enabled: provider.enabled,
|
enabled: provider.enabled,
|
||||||
apiKey:
|
apiKey: typeof provider.request.body.apiKey === "string" ? provider.request.body.apiKey : undefined,
|
||||||
typeof provider.options.aisdk.provider.apiKey === "string"
|
|
||||||
? provider.options.aisdk.provider.apiKey
|
|
||||||
: undefined,
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
remove: (providerID) => {
|
remove: (providerID) => {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ const it = testEffect(
|
||||||
)
|
)
|
||||||
|
|
||||||
describe("CatalogV2", () => {
|
describe("CatalogV2", () => {
|
||||||
it.effect("normalizes provider baseURL into endpoint url", () =>
|
it.effect("normalizes provider baseURL into api url", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const catalog = yield* Catalog.Service
|
const catalog = yield* Catalog.Service
|
||||||
const providerID = ProviderV2.ID.make("test")
|
const providerID = ProviderV2.ID.make("test")
|
||||||
|
|
@ -28,16 +28,16 @@ describe("CatalogV2", () => {
|
||||||
|
|
||||||
yield* transform((catalog) =>
|
yield* transform((catalog) =>
|
||||||
catalog.provider.update(providerID, (provider) => {
|
catalog.provider.update(providerID, (provider) => {
|
||||||
provider.endpoint = {
|
provider.api = {
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "@ai-sdk/openai-compatible",
|
package: "@ai-sdk/openai-compatible",
|
||||||
url: "https://default.example.com",
|
url: "https://default.example.com",
|
||||||
}
|
}
|
||||||
provider.options.aisdk.provider.baseURL = "https://override.example.com"
|
provider.request.body.baseURL = "https://override.example.com"
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
expect((yield* catalog.provider.get(providerID)).endpoint).toEqual({
|
expect((yield* catalog.provider.get(providerID)).api).toEqual({
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "@ai-sdk/openai-compatible",
|
package: "@ai-sdk/openai-compatible",
|
||||||
url: "https://override.example.com",
|
url: "https://override.example.com",
|
||||||
|
|
@ -45,7 +45,7 @@ describe("CatalogV2", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("normalizes model baseURL into endpoint url", () =>
|
it.effect("normalizes model baseURL into api url", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const catalog = yield* Catalog.Service
|
const catalog = yield* Catalog.Service
|
||||||
const providerID = ProviderV2.ID.make("test")
|
const providerID = ProviderV2.ID.make("test")
|
||||||
|
|
@ -54,27 +54,28 @@ describe("CatalogV2", () => {
|
||||||
|
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
catalog.provider.update(providerID, (provider) => {
|
catalog.provider.update(providerID, (provider) => {
|
||||||
provider.endpoint = {
|
provider.api = {
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "@ai-sdk/openai-compatible",
|
package: "@ai-sdk/openai-compatible",
|
||||||
url: "https://provider.example.com",
|
url: "https://provider.example.com",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
catalog.model.update(providerID, modelID, (model) => {
|
catalog.model.update(providerID, modelID, (model) => {
|
||||||
model.endpoint = { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://model.example.com" }
|
model.api = { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://model.example.com" }
|
||||||
model.options.aisdk.provider.baseURL = "https://override.example.com"
|
model.request.body.baseURL = "https://override.example.com"
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
expect((yield* catalog.model.get(providerID, modelID)).endpoint).toEqual({
|
expect((yield* catalog.model.get(providerID, modelID)).api).toEqual({
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "@ai-sdk/openai-compatible",
|
package: "@ai-sdk/openai-compatible",
|
||||||
url: "https://override.example.com",
|
url: "https://override.example.com",
|
||||||
|
settings: {},
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("resolves unknown model endpoint from provider endpoint", () =>
|
it.effect("resolves default model api from provider api", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const catalog = yield* Catalog.Service
|
const catalog = yield* Catalog.Service
|
||||||
const providerID = ProviderV2.ID.make("test")
|
const providerID = ProviderV2.ID.make("test")
|
||||||
|
|
@ -83,7 +84,7 @@ describe("CatalogV2", () => {
|
||||||
|
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
catalog.provider.update(providerID, (provider) => {
|
catalog.provider.update(providerID, (provider) => {
|
||||||
provider.endpoint = {
|
provider.api = {
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "@ai-sdk/openai-compatible",
|
package: "@ai-sdk/openai-compatible",
|
||||||
url: "https://provider.example.com",
|
url: "https://provider.example.com",
|
||||||
|
|
@ -92,7 +93,7 @@ describe("CatalogV2", () => {
|
||||||
catalog.model.update(providerID, modelID, () => {})
|
catalog.model.update(providerID, modelID, () => {})
|
||||||
})
|
})
|
||||||
|
|
||||||
expect((yield* catalog.model.get(providerID, modelID)).endpoint).toEqual({
|
expect((yield* catalog.model.get(providerID, modelID)).api).toEqual({
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "@ai-sdk/openai-compatible",
|
package: "@ai-sdk/openai-compatible",
|
||||||
url: "https://provider.example.com",
|
url: "https://provider.example.com",
|
||||||
|
|
@ -115,16 +116,16 @@ describe("CatalogV2", () => {
|
||||||
Effect.sync(() => {
|
Effect.sync(() => {
|
||||||
const item = evt.provider.get(providerID)
|
const item = evt.provider.get(providerID)
|
||||||
if (!item) return
|
if (!item) return
|
||||||
seen.push(item.provider.endpoint.type)
|
seen.push(item.provider.api.type)
|
||||||
if (item?.provider.endpoint.type === "aisdk") seen.push(item.provider.endpoint.url)
|
if (item?.provider.api.type === "aisdk") seen.push(item.provider.api.url)
|
||||||
seen.push(item?.provider.options.aisdk.provider.baseURL)
|
seen.push(item?.provider.request.body.baseURL)
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
yield* transform((catalog) =>
|
yield* transform((catalog) =>
|
||||||
catalog.provider.update(providerID, (provider) => {
|
catalog.provider.update(providerID, (provider) => {
|
||||||
provider.endpoint = { type: "aisdk", package: "@ai-sdk/openai-compatible" }
|
provider.api = { type: "aisdk", package: "@ai-sdk/openai-compatible" }
|
||||||
provider.options.aisdk.provider.baseURL = "https://provider.example.com"
|
provider.request.body.baseURL = "https://provider.example.com"
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -187,7 +188,7 @@ describe("CatalogV2", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("resolves provider and model option merges", () =>
|
it.effect("resolves provider and model request merges", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const catalog = yield* Catalog.Service
|
const catalog = yield* Catalog.Service
|
||||||
const providerID = ProviderV2.ID.make("test")
|
const providerID = ProviderV2.ID.make("test")
|
||||||
|
|
@ -196,25 +197,21 @@ describe("CatalogV2", () => {
|
||||||
|
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
catalog.provider.update(providerID, (provider) => {
|
catalog.provider.update(providerID, (provider) => {
|
||||||
provider.options.headers.provider = "provider"
|
provider.request.headers.provider = "provider"
|
||||||
provider.options.headers.shared = "provider"
|
provider.request.headers.shared = "provider"
|
||||||
provider.options.body.provider = true
|
provider.request.body.provider = true
|
||||||
provider.options.aisdk.provider.provider = true
|
|
||||||
})
|
})
|
||||||
catalog.model.update(providerID, modelID, (model) => {
|
catalog.model.update(providerID, modelID, (model) => {
|
||||||
model.options.headers.model = "model"
|
model.request.headers.model = "model"
|
||||||
model.options.headers.shared = "model"
|
model.request.headers.shared = "model"
|
||||||
model.options.body.model = true
|
model.request.body.model = true
|
||||||
model.options.aisdk.provider.model = true
|
model.request.body.request = true
|
||||||
model.options.aisdk.request.request = true
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const model = yield* catalog.model.get(providerID, modelID)
|
const model = yield* catalog.model.get(providerID, modelID)
|
||||||
expect(model.options.headers).toEqual({ provider: "provider", shared: "model", model: "model" })
|
expect(model.request.headers).toEqual({ provider: "provider", shared: "model", model: "model" })
|
||||||
expect(model.options.body).toEqual({ provider: true, model: true })
|
expect(model.request.body).toEqual({ provider: true, model: true, request: true })
|
||||||
expect(model.options.aisdk.provider).toEqual({ provider: true, model: true })
|
|
||||||
expect(model.options.aisdk.request).toEqual({ request: true })
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,10 +107,9 @@ describe("ConfigAgentPlugin.Plugin", () => {
|
||||||
hidden: true,
|
hidden: true,
|
||||||
color: "warning",
|
color: "warning",
|
||||||
steps: 12,
|
steps: 12,
|
||||||
options: {
|
request: {
|
||||||
headers: { first: "one", shared: "first" },
|
headers: { first: "one", shared: "first" },
|
||||||
body: { enabled: true },
|
body: { enabled: true, profile: "review", effort: "medium" },
|
||||||
aisdk: { provider: { profile: "review" }, request: { effort: "medium" } },
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -121,10 +120,9 @@ describe("ConfigAgentPlugin.Plugin", () => {
|
||||||
info: decode({
|
info: decode({
|
||||||
agents: {
|
agents: {
|
||||||
reviewer: {
|
reviewer: {
|
||||||
options: {
|
request: {
|
||||||
headers: { shared: "last", second: "two" },
|
headers: { shared: "last", second: "two" },
|
||||||
body: { retries: 2 },
|
body: { retries: 2, effort: "high" },
|
||||||
aisdk: { request: { effort: "high" } },
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -149,10 +147,9 @@ describe("ConfigAgentPlugin.Plugin", () => {
|
||||||
steps: 12,
|
steps: 12,
|
||||||
model: { providerID: "anthropic", id: "claude-sonnet", variant: undefined },
|
model: { providerID: "anthropic", id: "claude-sonnet", variant: undefined },
|
||||||
})
|
})
|
||||||
expect(reviewer.options).toEqual({
|
expect(reviewer.request).toEqual({
|
||||||
headers: { first: "one", shared: "last", second: "two" },
|
headers: { first: "one", shared: "last", second: "two" },
|
||||||
body: { enabled: true, retries: 2 },
|
body: { enabled: true, profile: "review", retries: 2, effort: "high" },
|
||||||
aisdk: { provider: { profile: "review" }, request: { effort: "high" } },
|
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import fs from "fs/promises"
|
import fs from "fs/promises"
|
||||||
import { describe, expect } from "bun:test"
|
import { describe, expect } from "bun:test"
|
||||||
import { Effect, Layer } from "effect"
|
import { Effect, Layer, Schema } from "effect"
|
||||||
|
import { FastCheck } from "effect/testing"
|
||||||
import { Config } from "@opencode-ai/core/config"
|
import { Config } from "@opencode-ai/core/config"
|
||||||
import { ConfigProvider } from "@opencode-ai/core/config/provider"
|
import { ConfigProvider } from "@opencode-ai/core/config/provider"
|
||||||
import { ConfigMigrateV1 } from "@opencode-ai/core/v1/config/migrate"
|
import { ConfigMigrateV1 } from "@opencode-ai/core/v1/config/migrate"
|
||||||
|
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
|
||||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||||
import { Global } from "@opencode-ai/core/global"
|
import { Global } from "@opencode-ai/core/global"
|
||||||
import { Location } from "@opencode-ai/core/location"
|
import { Location } from "@opencode-ai/core/location"
|
||||||
|
|
@ -41,14 +43,10 @@ function testLayer(
|
||||||
}
|
}
|
||||||
|
|
||||||
const provider = {
|
const provider = {
|
||||||
endpoint: { type: "unknown" },
|
api: { type: "native", settings: {} },
|
||||||
options: {
|
request: {
|
||||||
headers: {},
|
headers: {},
|
||||||
body: {},
|
body: {},
|
||||||
aisdk: {
|
|
||||||
provider: {},
|
|
||||||
request: {},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
models: {},
|
models: {},
|
||||||
}
|
}
|
||||||
|
|
@ -62,6 +60,46 @@ describe("Config", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("migrates arbitrary v1 configuration into valid v2 configuration", () =>
|
||||||
|
Effect.sync(() => {
|
||||||
|
FastCheck.assert(
|
||||||
|
FastCheck.property(Schema.toArbitrary(ConfigV1.Info), (info) => {
|
||||||
|
Schema.decodeUnknownSync(Config.Info)(ConfigMigrateV1.migrate(info), { errors: "all" })
|
||||||
|
}),
|
||||||
|
{ numRuns: 100 },
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("migrates v1 provider setup options into AISDK settings", () =>
|
||||||
|
Effect.sync(() => {
|
||||||
|
const migrated = ConfigMigrateV1.migrate({
|
||||||
|
provider: {
|
||||||
|
bedrock: {
|
||||||
|
npm: "@ai-sdk/amazon-bedrock",
|
||||||
|
options: {
|
||||||
|
headers: { "x-test": "1" },
|
||||||
|
body: { trace: true },
|
||||||
|
region: "us-east-1",
|
||||||
|
profile: "dev",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(migrated.providers?.bedrock?.api).toEqual({
|
||||||
|
type: "aisdk",
|
||||||
|
package: "@ai-sdk/amazon-bedrock",
|
||||||
|
url: undefined,
|
||||||
|
settings: { region: "us-east-1", profile: "dev" },
|
||||||
|
})
|
||||||
|
expect(migrated.providers?.bedrock?.request).toEqual({
|
||||||
|
headers: { "x-test": "1" },
|
||||||
|
body: { trace: true },
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.live("returns an empty configuration when directory files do not exist", () =>
|
it.live("returns an empty configuration when directory files do not exist", () =>
|
||||||
Effect.acquireRelease(
|
Effect.acquireRelease(
|
||||||
Effect.promise(() => tmpdir()),
|
Effect.promise(() => tmpdir()),
|
||||||
|
|
@ -186,9 +224,9 @@ describe("Config", () => {
|
||||||
reviewer: {
|
reviewer: {
|
||||||
model: "openrouter/openai/gpt-5",
|
model: "openrouter/openai/gpt-5",
|
||||||
variant: "high",
|
variant: "high",
|
||||||
options: {
|
request: {
|
||||||
headers: { "x-agent": "reviewer" },
|
headers: { "x-agent": "reviewer" },
|
||||||
aisdk: { request: { reasoningEffort: "high" } },
|
body: { reasoningEffort: "high" },
|
||||||
},
|
},
|
||||||
description: "Review changes for correctness",
|
description: "Review changes for correctness",
|
||||||
system: "Find regressions.",
|
system: "Find regressions.",
|
||||||
|
|
@ -266,22 +304,21 @@ describe("Config", () => {
|
||||||
{ action: "bash", resource: "*", effect: "ask" },
|
{ action: "bash", resource: "*", effect: "ask" },
|
||||||
{ action: "bash", resource: "git status", effect: "allow" },
|
{ action: "bash", resource: "git status", effect: "allow" },
|
||||||
])
|
])
|
||||||
expect(documents[0]?.info.agents?.reviewer).toEqual({
|
const reviewer = documents[0]?.info.agents?.reviewer
|
||||||
model: "openrouter/openai/gpt-5",
|
expect(reviewer?.model).toBe("openrouter/openai/gpt-5")
|
||||||
variant: "high",
|
expect(reviewer?.variant).toBe("high")
|
||||||
options: {
|
expect(reviewer?.request).toEqual({
|
||||||
headers: { "x-agent": "reviewer" },
|
headers: { "x-agent": "reviewer" },
|
||||||
aisdk: { request: { reasoningEffort: "high" } },
|
body: { reasoningEffort: "high" },
|
||||||
},
|
|
||||||
description: "Review changes for correctness",
|
|
||||||
system: "Find regressions.",
|
|
||||||
mode: "subagent",
|
|
||||||
hidden: false,
|
|
||||||
color: "warning",
|
|
||||||
steps: 12,
|
|
||||||
disabled: false,
|
|
||||||
permissions: [{ action: "edit", resource: "*", effect: "deny" }],
|
|
||||||
})
|
})
|
||||||
|
expect(reviewer?.description).toBe("Review changes for correctness")
|
||||||
|
expect(reviewer?.system).toBe("Find regressions.")
|
||||||
|
expect(reviewer?.mode).toBe("subagent")
|
||||||
|
expect(reviewer?.hidden).toBe(false)
|
||||||
|
expect(reviewer?.color).toBe("warning")
|
||||||
|
expect(reviewer?.steps).toBe(12)
|
||||||
|
expect(reviewer?.disabled).toBe(false)
|
||||||
|
expect(reviewer?.permissions).toEqual([{ action: "edit", resource: "*", effect: "deny" }])
|
||||||
expect(documents[0]?.info.snapshots).toBe(false)
|
expect(documents[0]?.info.snapshots).toBe(false)
|
||||||
expect(documents[0]?.info.watcher).toEqual({ ignore: ["node_modules/**", "dist/**", ".git"] })
|
expect(documents[0]?.info.watcher).toEqual({ ignore: ["node_modules/**", "dist/**", ".git"] })
|
||||||
expect(documents[0]?.info.formatter).toEqual({
|
expect(documents[0]?.info.formatter).toEqual({
|
||||||
|
|
@ -379,6 +416,24 @@ describe("Config", () => {
|
||||||
skills: { paths: ["./skills"], urls: ["https://example.com/.well-known/skills/"] },
|
skills: { paths: ["./skills"], urls: ["https://example.com/.well-known/skills/"] },
|
||||||
reference: { docs: { path: "../docs" } },
|
reference: { docs: { path: "../docs" } },
|
||||||
attachment: { image: { auto_resize: false, max_width: 1200 } },
|
attachment: { image: { auto_resize: false, max_width: 1200 } },
|
||||||
|
provider: {
|
||||||
|
custom: {
|
||||||
|
options: { apiKey: "secret" },
|
||||||
|
models: {
|
||||||
|
model: {
|
||||||
|
options: { reasoningEffort: "high" },
|
||||||
|
variants: { fast: { temperature: 0.2 } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
openai: {
|
||||||
|
npm: "@ai-sdk/openai",
|
||||||
|
options: { apiKey: "secret", organization: "org" },
|
||||||
|
models: {
|
||||||
|
model: { options: { reasoningEffort: "high", serviceTier: "priority" } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
compaction: { auto: true, tail_turns: 3, preserve_recent_tokens: 2000, reserved: 10000 },
|
compaction: { auto: true, tail_turns: 3, preserve_recent_tokens: 2000, reserved: 10000 },
|
||||||
experimental: { mcp_timeout: 5000 },
|
experimental: { mcp_timeout: 5000 },
|
||||||
mcp: {
|
mcp: {
|
||||||
|
|
@ -410,7 +465,7 @@ describe("Config", () => {
|
||||||
expect(documents[0]?.info.agents?.reviewer).toMatchObject({
|
expect(documents[0]?.info.agents?.reviewer).toMatchObject({
|
||||||
system: "Review changes.",
|
system: "Review changes.",
|
||||||
disabled: true,
|
disabled: true,
|
||||||
options: { body: { temperature: 0.2 } },
|
request: { body: { temperature: 0.2 } },
|
||||||
permissions: [{ action: "read", resource: "*", effect: "allow" }],
|
permissions: [{ action: "read", resource: "*", effect: "allow" }],
|
||||||
})
|
})
|
||||||
expect(documents[0]?.info.plugins).toEqual([
|
expect(documents[0]?.info.plugins).toEqual([
|
||||||
|
|
@ -420,6 +475,20 @@ describe("Config", () => {
|
||||||
expect(documents[0]?.info.skills).toEqual(["./skills", "https://example.com/.well-known/skills/"])
|
expect(documents[0]?.info.skills).toEqual(["./skills", "https://example.com/.well-known/skills/"])
|
||||||
expect(documents[0]?.info.references).toEqual({ docs: { path: "../docs" } })
|
expect(documents[0]?.info.references).toEqual({ docs: { path: "../docs" } })
|
||||||
expect(documents[0]?.info.attachments).toEqual({ image: { auto_resize: false, max_width: 1200 } })
|
expect(documents[0]?.info.attachments).toEqual({ image: { auto_resize: false, max_width: 1200 } })
|
||||||
|
expect(documents[0]?.info.providers?.custom).toMatchObject({
|
||||||
|
request: { body: { apiKey: "secret" } },
|
||||||
|
models: {
|
||||||
|
model: {
|
||||||
|
request: { body: { reasoningEffort: "high" } },
|
||||||
|
variants: [{ id: "fast", body: { temperature: 0.2 } }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(documents[0]?.info.providers?.openai).toMatchObject({
|
||||||
|
api: { settings: {} },
|
||||||
|
request: { headers: { Authorization: "Bearer secret", "OpenAI-Organization": "org" } },
|
||||||
|
models: { model: { request: { body: { reasoning_effort: "high", service_tier: "priority" } } } },
|
||||||
|
})
|
||||||
expect(documents[0]?.info.compaction).toEqual({
|
expect(documents[0]?.info.compaction).toEqual({
|
||||||
auto: true,
|
auto: true,
|
||||||
prune: undefined,
|
prune: undefined,
|
||||||
|
|
|
||||||
208
packages/core/test/config/provider-options.test.ts
Normal file
208
packages/core/test/config/provider-options.test.ts
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import { ConfigProviderOptionsV1 } from "@opencode-ai/core/v1/config/provider-options"
|
||||||
|
|
||||||
|
describe("ConfigProviderOptionsV1", () => {
|
||||||
|
test("keeps raw provider and request options unchanged", () => {
|
||||||
|
const lowerer = ConfigProviderOptionsV1.get("custom-provider")
|
||||||
|
|
||||||
|
expect(lowerer.provider({ apiKey: "secret", headers: { "x-test": "1" }, nested: { camelCase: true } })).toEqual({
|
||||||
|
body: { apiKey: "secret", headers: { "x-test": "1" }, nested: { camelCase: true } },
|
||||||
|
})
|
||||||
|
expect(lowerer.request({ nested: { camelCase: true } })).toEqual({ nested: { camelCase: true } })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("falls back to raw lowering for prototype property package names", () => {
|
||||||
|
expect(ConfigProviderOptionsV1.get("toString").provider({ enabled: true })).toEqual({ body: { enabled: true } })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("lowers OpenAI provider and request options", () => {
|
||||||
|
const lowerer = ConfigProviderOptionsV1.get("@ai-sdk/openai")
|
||||||
|
|
||||||
|
expect(
|
||||||
|
lowerer.provider({
|
||||||
|
apiKey: "secret",
|
||||||
|
baseURL: "https://openai.example/v1",
|
||||||
|
organization: "org",
|
||||||
|
project: "project",
|
||||||
|
headers: { "x-test": "1" },
|
||||||
|
body: { store: true },
|
||||||
|
timeout: 1000,
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
url: "https://openai.example/v1",
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer secret",
|
||||||
|
"OpenAI-Organization": "org",
|
||||||
|
"OpenAI-Project": "project",
|
||||||
|
"x-test": "1",
|
||||||
|
},
|
||||||
|
body: { store: true },
|
||||||
|
settings: { timeout: 1000 },
|
||||||
|
})
|
||||||
|
expect(lowerer.request({ reasoningEffort: "high", nestedValue: { camelCase: true } })).toEqual({
|
||||||
|
reasoning_effort: "high",
|
||||||
|
nested_value: { camel_case: true },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("lowers Anthropic provider and request options", () => {
|
||||||
|
const lowerer = ConfigProviderOptionsV1.get("@ai-sdk/anthropic")
|
||||||
|
|
||||||
|
expect(
|
||||||
|
lowerer.provider({
|
||||||
|
apiKey: "secret",
|
||||||
|
authToken: "token",
|
||||||
|
baseURL: "https://anthropic.example",
|
||||||
|
headers: { "x-test": "1" },
|
||||||
|
body: { beta: true },
|
||||||
|
generateId: "custom",
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
url: "https://anthropic.example",
|
||||||
|
headers: { "x-api-key": "secret", Authorization: "Bearer token", "x-test": "1" },
|
||||||
|
body: { beta: true },
|
||||||
|
settings: { generateId: "custom" },
|
||||||
|
})
|
||||||
|
expect(
|
||||||
|
lowerer.request({
|
||||||
|
effort: "high",
|
||||||
|
taskBudget: 1024,
|
||||||
|
metadata: { userId: "user", traceId: "trace" },
|
||||||
|
nestedValue: { camelCase: true },
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
output_config: { effort: "high", task_budget: 1024 },
|
||||||
|
metadata: { user_id: "user", trace_id: "trace" },
|
||||||
|
nested_value: { camel_case: true },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("lowers Google provider and request options", () => {
|
||||||
|
const lowerer = ConfigProviderOptionsV1.get("@ai-sdk/google")
|
||||||
|
|
||||||
|
expect(
|
||||||
|
lowerer.provider({
|
||||||
|
apiKey: "secret",
|
||||||
|
baseURL: "https://google.example",
|
||||||
|
headers: { "x-test": "1" },
|
||||||
|
body: { trace: true },
|
||||||
|
project: "project",
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
url: "https://google.example",
|
||||||
|
headers: { "x-goog-api-key": "secret", "x-test": "1" },
|
||||||
|
body: { trace: true },
|
||||||
|
settings: { project: "project" },
|
||||||
|
})
|
||||||
|
expect(
|
||||||
|
lowerer.request({
|
||||||
|
thinkingConfig: { thinkingBudget: 1024 },
|
||||||
|
responseModalities: ["TEXT"],
|
||||||
|
mediaResolution: "high",
|
||||||
|
imageConfig: { aspectRatio: "16:9" },
|
||||||
|
safetySettings: ["safe"],
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
safetySettings: ["safe"],
|
||||||
|
generationConfig: {
|
||||||
|
thinkingConfig: { thinkingBudget: 1024 },
|
||||||
|
responseModalities: ["TEXT"],
|
||||||
|
mediaResolution: "high",
|
||||||
|
imageConfig: { aspectRatio: "16:9" },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("lowers Azure provider options and uses OpenAI request lowering", () => {
|
||||||
|
const lowerer = ConfigProviderOptionsV1.get("@ai-sdk/azure")
|
||||||
|
|
||||||
|
expect(
|
||||||
|
lowerer.provider({
|
||||||
|
apiKey: "secret",
|
||||||
|
baseURL: "https://azure.example",
|
||||||
|
headers: { "x-test": "1" },
|
||||||
|
body: { trace: true },
|
||||||
|
resourceName: "resource",
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
url: "https://azure.example",
|
||||||
|
headers: { "api-key": "secret", "x-test": "1" },
|
||||||
|
body: { trace: true },
|
||||||
|
settings: { resourceName: "resource" },
|
||||||
|
})
|
||||||
|
expect(lowerer.request({ reasoningEffort: "high" })).toEqual({ reasoning_effort: "high" })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("lowers Amazon Bedrock provider and request options", () => {
|
||||||
|
const lowerer = ConfigProviderOptionsV1.get("@ai-sdk/amazon-bedrock")
|
||||||
|
|
||||||
|
expect(
|
||||||
|
lowerer.provider({
|
||||||
|
headers: { "x-test": "1" },
|
||||||
|
body: { trace: true },
|
||||||
|
region: "us-east-1",
|
||||||
|
profile: "dev",
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
headers: { "x-test": "1" },
|
||||||
|
body: { trace: true },
|
||||||
|
settings: { region: "us-east-1", profile: "dev" },
|
||||||
|
})
|
||||||
|
expect(lowerer.request({ temperature: 0.2 })).toEqual({
|
||||||
|
additionalModelRequestFields: { temperature: 0.2 },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("lowers OpenAI-compatible provider and request options", () => {
|
||||||
|
const lowerer = ConfigProviderOptionsV1.get("@ai-sdk/openai-compatible")
|
||||||
|
|
||||||
|
expect(
|
||||||
|
lowerer.provider({
|
||||||
|
baseURL: "https://compatible.example/v1",
|
||||||
|
headers: { "x-test": "1" },
|
||||||
|
body: { trace: true },
|
||||||
|
apiKey: "secret",
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
url: "https://compatible.example/v1",
|
||||||
|
headers: { "x-test": "1" },
|
||||||
|
body: { trace: true },
|
||||||
|
settings: { apiKey: "secret" },
|
||||||
|
})
|
||||||
|
expect(lowerer.request({ reasoningEffort: "high", serviceTier: "priority" })).toEqual({
|
||||||
|
reasoning_effort: "high",
|
||||||
|
serviceTier: "priority",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
"@ai-sdk/cerebras",
|
||||||
|
"@ai-sdk/deepinfra",
|
||||||
|
"@ai-sdk/groq",
|
||||||
|
"@ai-sdk/mistral",
|
||||||
|
"@ai-sdk/togetherai",
|
||||||
|
"@ai-sdk/xai",
|
||||||
|
"@openrouter/ai-sdk-provider",
|
||||||
|
"ai-gateway-provider",
|
||||||
|
"venice-ai-sdk-provider",
|
||||||
|
])("uses OpenAI-compatible lowering for %s", (packageName) => {
|
||||||
|
const lowerer = ConfigProviderOptionsV1.get(packageName)
|
||||||
|
|
||||||
|
expect(lowerer.provider({ baseURL: "https://example.test", apiKey: "secret" })).toEqual({
|
||||||
|
url: "https://example.test",
|
||||||
|
headers: undefined,
|
||||||
|
body: undefined,
|
||||||
|
settings: { apiKey: "secret" },
|
||||||
|
})
|
||||||
|
expect(lowerer.request({ reasoningEffort: "high" })).toEqual({ reasoning_effort: "high" })
|
||||||
|
})
|
||||||
|
|
||||||
|
test.each(["@ai-sdk/google-vertex", "@ai-sdk/google-vertex/anthropic"])("uses provider family lowering for %s", (packageName) => {
|
||||||
|
const lowerer = ConfigProviderOptionsV1.get(packageName)
|
||||||
|
|
||||||
|
expect(lowerer.provider({ baseURL: "https://example.test", profile: "dev" })).toMatchObject({
|
||||||
|
url: "https://example.test",
|
||||||
|
settings: { profile: "dev" },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -8,7 +8,7 @@ import { PluginV2 } from "@opencode-ai/core/plugin"
|
||||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||||
import { it } from "../plugin/provider-helper"
|
import { it } from "../plugin/provider-helper"
|
||||||
|
|
||||||
function options(headers: Record<string, string>, variant?: string) {
|
function request(headers: Record<string, string>, variant?: string) {
|
||||||
return {
|
return {
|
||||||
headers,
|
headers,
|
||||||
variant,
|
variant,
|
||||||
|
|
@ -35,8 +35,8 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
||||||
custom: {
|
custom: {
|
||||||
name: "Configured",
|
name: "Configured",
|
||||||
env: ["CUSTOM_API_KEY"],
|
env: ["CUSTOM_API_KEY"],
|
||||||
endpoint: { type: "unknown" },
|
api: { type: "native", settings: {} },
|
||||||
options: options({ first: "first", shared: "first" }),
|
request: request({ first: "first", shared: "first" }),
|
||||||
models: {
|
models: {
|
||||||
chat: {
|
chat: {
|
||||||
name: "First",
|
name: "First",
|
||||||
|
|
@ -44,7 +44,7 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
||||||
disabled: true,
|
disabled: true,
|
||||||
limit: { context: 100, output: 50 },
|
limit: { context: 100, output: 50 },
|
||||||
cost: { input: 1, output: 2 },
|
cost: { input: 1, output: 2 },
|
||||||
options: options({ first: "first", shared: "first" }, "retained"),
|
request: request({ first: "first", shared: "first" }, "retained"),
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
id: "fast",
|
id: "fast",
|
||||||
|
|
@ -62,14 +62,14 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
||||||
info: decode({
|
info: decode({
|
||||||
providers: {
|
providers: {
|
||||||
custom: {
|
custom: {
|
||||||
endpoint: { type: "aisdk", package: "custom-sdk", url: "https://example.test" },
|
api: { type: "aisdk", package: "custom-sdk", url: "https://example.test" },
|
||||||
options: options({ last: "last", shared: "last" }),
|
request: request({ last: "last", shared: "last" }),
|
||||||
models: {
|
models: {
|
||||||
chat: {
|
chat: {
|
||||||
api_id: "api-chat",
|
api_id: "api-chat",
|
||||||
name: "Last",
|
name: "Last",
|
||||||
limit: { output: 75 },
|
limit: { output: 75 },
|
||||||
options: options({ last: "last", shared: "last" }),
|
request: request({ last: "last", shared: "last" }),
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
id: "fast",
|
id: "fast",
|
||||||
|
|
@ -110,16 +110,16 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
||||||
expect(provider.name).toBe("Renamed")
|
expect(provider.name).toBe("Renamed")
|
||||||
expect(provider.env).toEqual(["CUSTOM_API_KEY"])
|
expect(provider.env).toEqual(["CUSTOM_API_KEY"])
|
||||||
expect(provider.enabled).toEqual({ via: "custom", data: {} })
|
expect(provider.enabled).toEqual({ via: "custom", data: {} })
|
||||||
expect(provider.endpoint).toEqual({ type: "aisdk", package: "custom-sdk", url: "https://example.test" })
|
expect(provider.api).toEqual({ type: "aisdk", package: "custom-sdk", url: "https://example.test" })
|
||||||
expect(provider.options.headers).toEqual({ first: "first", shared: "last", last: "last" })
|
expect(provider.request.headers).toEqual({ first: "first", shared: "last", last: "last" })
|
||||||
expect(model.apiID).toBe(ModelV2.ID.make("api-chat"))
|
expect(model.apiID).toBe(ModelV2.ID.make("api-chat"))
|
||||||
expect(model.name).toBe("Last")
|
expect(model.name).toBe("Last")
|
||||||
expect(model.capabilities).toEqual({ tools: true, input: ["text"], output: ["text"] })
|
expect(model.capabilities).toEqual({ tools: true, input: ["text"], output: ["text"] })
|
||||||
expect(model.enabled).toBe(false)
|
expect(model.enabled).toBe(false)
|
||||||
expect(model.limit).toEqual({ context: 100, output: 75 })
|
expect(model.limit).toEqual({ context: 100, output: 75 })
|
||||||
expect(model.cost).toEqual([{ input: 1, output: 2, cache: { read: 0, write: 0 }, tier: undefined }])
|
expect(model.cost).toEqual([{ input: 1, output: 2, cache: { read: 0, write: 0 }, tier: undefined }])
|
||||||
expect(model.options.headers).toEqual({ first: "first", shared: "last", last: "last" })
|
expect(model.request.headers).toEqual({ first: "first", shared: "last", last: "last" })
|
||||||
expect(model.options.variant).toBe("retained")
|
expect(model.request.variant).toBe("retained")
|
||||||
expect(model.variants.map((variant) => variant.id)).toEqual([
|
expect(model.variants.map((variant) => variant.id)).toEqual([
|
||||||
ModelV2.VariantID.make("fast"),
|
ModelV2.VariantID.make("fast"),
|
||||||
ModelV2.VariantID.make("slow"),
|
ModelV2.VariantID.make("slow"),
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ function bedrockFetch(sdk: unknown, modelID = "anthropic.claude-sonnet-4-5") {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("AmazonBedrockPlugin", () => {
|
describe("AmazonBedrockPlugin", () => {
|
||||||
it.effect("moves endpoint option to endpoint URL", () =>
|
it.effect("moves endpoint option to api URL", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const plugin = yield* PluginV2.Service
|
const plugin = yield* PluginV2.Service
|
||||||
const catalog = yield* Catalog.Service
|
const catalog = yield* Catalog.Service
|
||||||
|
|
@ -27,25 +27,24 @@ describe("AmazonBedrockPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const bedrock = provider("amazon-bedrock", {
|
const bedrock = provider("amazon-bedrock", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/amazon-bedrock" },
|
api: { type: "aisdk", package: "@ai-sdk/amazon-bedrock" },
|
||||||
options: {
|
request: {
|
||||||
headers: {},
|
headers: {},
|
||||||
body: {},
|
body: { endpoint: "https://bedrock.example" },
|
||||||
aisdk: { provider: { endpoint: "https://bedrock.example" }, request: {} },
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
catalog.provider.update(bedrock.id, (item) => {
|
catalog.provider.update(bedrock.id, (item) => {
|
||||||
item.endpoint = bedrock.endpoint
|
item.api = bedrock.api
|
||||||
item.options = bedrock.options
|
item.request = bedrock.request
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
const result = yield* catalog.provider.get(ProviderV2.ID.amazonBedrock)
|
const result = yield* catalog.provider.get(ProviderV2.ID.amazonBedrock)
|
||||||
expect(result.endpoint).toEqual({
|
expect(result.api).toEqual({
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "@ai-sdk/amazon-bedrock",
|
package: "@ai-sdk/amazon-bedrock",
|
||||||
url: "https://bedrock.example",
|
url: "https://bedrock.example",
|
||||||
})
|
})
|
||||||
expect(result.options.aisdk.provider.endpoint).toBeUndefined()
|
expect(result.request.body.endpoint).toBeUndefined()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,18 @@ describe("AnthropicPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const item = provider("anthropic", {
|
const item = provider("anthropic", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/anthropic" },
|
api: { type: "aisdk", package: "@ai-sdk/anthropic" },
|
||||||
options: { headers: { Existing: "1" }, body: {}, aisdk: { provider: {}, request: {} } },
|
request: { headers: { Existing: "1" }, body: {} },
|
||||||
})
|
})
|
||||||
catalog.provider.update(item.id, (draft) => {
|
catalog.provider.update(item.id, (draft) => {
|
||||||
draft.endpoint = item.endpoint
|
draft.api = item.api
|
||||||
draft.options = item.options
|
draft.request = item.request
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.anthropic)).options.headers["anthropic-beta"]).toBe(
|
expect((yield* catalog.provider.get(ProviderV2.ID.anthropic)).request.headers["anthropic-beta"]).toBe(
|
||||||
"interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
|
"interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
|
||||||
)
|
)
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.anthropic)).options.headers.Existing).toBe("1")
|
expect((yield* catalog.provider.get(ProviderV2.ID.anthropic)).request.headers.Existing).toBe("1")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -37,7 +37,7 @@ describe("AnthropicPlugin", () => {
|
||||||
yield* plugin.add(AnthropicPlugin)
|
yield* plugin.add(AnthropicPlugin)
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => catalog.provider.update(provider("openai").id, () => {}))
|
yield* transform((catalog) => catalog.provider.update(provider("openai").id, () => {}))
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.openai)).options.headers["anthropic-beta"]).toBeUndefined()
|
expect((yield* catalog.provider.get(ProviderV2.ID.openai)).request.headers["anthropic-beta"]).toBeUndefined()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,17 +16,17 @@ describe("AzureCognitiveServicesPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
catalog.provider.update(ProviderV2.ID.make("azure-cognitive-services"), (item) => {
|
catalog.provider.update(ProviderV2.ID.make("azure-cognitive-services"), (item) => {
|
||||||
item.endpoint = { type: "aisdk", package: "@ai-sdk/openai-compatible" }
|
item.api = { type: "aisdk", package: "@ai-sdk/openai-compatible" }
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
const result = yield* catalog.provider.get(ProviderV2.ID.make("azure-cognitive-services"))
|
const result = yield* catalog.provider.get(ProviderV2.ID.make("azure-cognitive-services"))
|
||||||
expect(result.endpoint).toEqual({
|
expect(result.api).toEqual({
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "@ai-sdk/openai-compatible",
|
package: "@ai-sdk/openai-compatible",
|
||||||
url: "https://cognitive.cognitiveservices.azure.com/openai",
|
url: "https://cognitive.cognitiveservices.azure.com/openai",
|
||||||
})
|
})
|
||||||
expect(result.options.aisdk.provider.baseURL).toBeUndefined()
|
expect(result.request.body.baseURL).toBeUndefined()
|
||||||
expect(result.options.aisdk.provider.resourceName).toBeUndefined()
|
expect(result.request.body.resourceName).toBeUndefined()
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -40,22 +40,22 @@ describe("AzureCognitiveServicesPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const azure = provider("azure-cognitive-services", {
|
const azure = provider("azure-cognitive-services", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible" },
|
api: { type: "aisdk", package: "@ai-sdk/openai-compatible" },
|
||||||
})
|
})
|
||||||
const openai = provider("openai")
|
const openai = provider("openai")
|
||||||
catalog.provider.update(azure.id, (item) => {
|
catalog.provider.update(azure.id, (item) => {
|
||||||
item.endpoint = azure.endpoint
|
item.api = azure.api
|
||||||
})
|
})
|
||||||
catalog.provider.update(openai.id, (item) => {
|
catalog.provider.update(openai.id, (item) => {
|
||||||
item.endpoint = openai.endpoint
|
item.api = openai.api
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
const azure = yield* catalog.provider.get(ProviderV2.ID.make("azure-cognitive-services"))
|
const azure = yield* catalog.provider.get(ProviderV2.ID.make("azure-cognitive-services"))
|
||||||
const openai = yield* catalog.provider.get(ProviderV2.ID.openai)
|
const openai = yield* catalog.provider.get(ProviderV2.ID.openai)
|
||||||
expect(azure.options.aisdk.provider.baseURL).toBeUndefined()
|
expect(azure.request.body.baseURL).toBeUndefined()
|
||||||
expect(azure.endpoint).toEqual({ type: "aisdk", package: "@ai-sdk/openai-compatible" })
|
expect(azure.api).toEqual({ type: "aisdk", package: "@ai-sdk/openai-compatible" })
|
||||||
expect(openai.options.aisdk.provider.baseURL).toBeUndefined()
|
expect(openai.request.body.baseURL).toBeUndefined()
|
||||||
expect(openai.endpoint).toEqual({ type: "aisdk", package: "test-provider" })
|
expect(openai.api).toEqual({ type: "aisdk", package: "test-provider" })
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,10 @@ describe("AzurePlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
catalog.provider.update(ProviderV2.ID.azure, (item) => {
|
catalog.provider.update(ProviderV2.ID.azure, (item) => {
|
||||||
item.endpoint = { type: "aisdk", package: "@ai-sdk/azure" }
|
item.api = { type: "aisdk", package: "@ai-sdk/azure" }
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.azure)).options.aisdk.provider.resourceName).toBe("from-env")
|
expect((yield* catalog.provider.get(ProviderV2.ID.azure)).request.body.resourceName).toBe("from-env")
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -51,19 +51,19 @@ describe("AzurePlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const azure = provider("azure", {
|
const azure = provider("azure", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/azure" },
|
api: { type: "aisdk", package: "@ai-sdk/azure" },
|
||||||
options: { headers: {}, body: {}, aisdk: { provider: { resourceName: "from-config" }, request: {} } },
|
request: { headers: {}, body: { resourceName: "from-config" } },
|
||||||
})
|
})
|
||||||
catalog.provider.update(azure.id, (item) => {
|
catalog.provider.update(azure.id, (item) => {
|
||||||
item.endpoint = azure.endpoint
|
item.api = azure.api
|
||||||
item.options = azure.options
|
item.request = azure.request
|
||||||
})
|
})
|
||||||
catalog.provider.update(ProviderV2.ID.openai, () => {})
|
catalog.provider.update(ProviderV2.ID.openai, () => {})
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.azure)).options.aisdk.provider.resourceName).toBe(
|
expect((yield* catalog.provider.get(ProviderV2.ID.azure)).request.body.resourceName).toBe(
|
||||||
"from-config",
|
"from-config",
|
||||||
)
|
)
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.openai)).options.aisdk.provider.resourceName).toBeUndefined()
|
expect((yield* catalog.provider.get(ProviderV2.ID.openai)).request.body.resourceName).toBeUndefined()
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -100,10 +100,10 @@ describe("AzurePlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
catalog.provider.update(ProviderV2.ID.azure, (item) => {
|
catalog.provider.update(ProviderV2.ID.azure, (item) => {
|
||||||
item.endpoint = { type: "aisdk", package: "@ai-sdk/azure" }
|
item.api = { type: "aisdk", package: "@ai-sdk/azure" }
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.azure)).options.aisdk.provider.resourceName).toBe(
|
expect((yield* catalog.provider.get(ProviderV2.ID.azure)).request.body.resourceName).toBe(
|
||||||
"from-account",
|
"from-account",
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
|
@ -119,15 +119,15 @@ describe("AzurePlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const azure = provider("azure", {
|
const azure = provider("azure", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/azure" },
|
api: { type: "aisdk", package: "@ai-sdk/azure" },
|
||||||
options: { headers: {}, body: {}, aisdk: { provider: { resourceName: "" }, request: {} } },
|
request: { headers: {}, body: { resourceName: "" } },
|
||||||
})
|
})
|
||||||
catalog.provider.update(azure.id, (item) => {
|
catalog.provider.update(azure.id, (item) => {
|
||||||
item.endpoint = azure.endpoint
|
item.api = azure.api
|
||||||
item.options = azure.options
|
item.request = azure.request
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.azure)).options.aisdk.provider.resourceName).toBe("from-env")
|
expect((yield* catalog.provider.get(ProviderV2.ID.azure)).request.body.resourceName).toBe("from-env")
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -141,15 +141,15 @@ describe("AzurePlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const azure = provider("azure", {
|
const azure = provider("azure", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/azure" },
|
api: { type: "aisdk", package: "@ai-sdk/azure" },
|
||||||
options: { headers: {}, body: {}, aisdk: { provider: { resourceName: " " }, request: {} } },
|
request: { headers: {}, body: { resourceName: " " } },
|
||||||
})
|
})
|
||||||
catalog.provider.update(azure.id, (item) => {
|
catalog.provider.update(azure.id, (item) => {
|
||||||
item.endpoint = azure.endpoint
|
item.api = azure.api
|
||||||
item.options = azure.options
|
item.request = azure.request
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.azure)).options.aisdk.provider.resourceName).toBe("from-env")
|
expect((yield* catalog.provider.get(ProviderV2.ID.azure)).request.body.resourceName).toBe("from-env")
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -227,7 +227,7 @@ describe("AzurePlugin", () => {
|
||||||
"aisdk.language",
|
"aisdk.language",
|
||||||
{
|
{
|
||||||
model: model("azure", "deployment", {
|
model: model("azure", "deployment", {
|
||||||
options: { headers: {}, body: {}, aisdk: { provider: {}, request: { useCompletionUrls: true } } },
|
request: { headers: {}, body: { useCompletionUrls: true } },
|
||||||
}),
|
}),
|
||||||
sdk: fakeSelectorSdk(calls),
|
sdk: fakeSelectorSdk(calls),
|
||||||
options: {},
|
options: {},
|
||||||
|
|
|
||||||
|
|
@ -27,11 +27,11 @@ describe("CerebrasPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
catalog.provider.update(ProviderV2.ID.make("cerebras"), (item) => {
|
catalog.provider.update(ProviderV2.ID.make("cerebras"), (item) => {
|
||||||
item.endpoint = { type: "aisdk", package: "@ai-sdk/cerebras" }
|
item.api = { type: "aisdk", package: "@ai-sdk/cerebras" }
|
||||||
item.options.headers.Existing = "1"
|
item.request.headers.Existing = "1"
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("cerebras"))).options.headers).toEqual({
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("cerebras"))).request.headers).toEqual({
|
||||||
Existing: "1",
|
Existing: "1",
|
||||||
"X-Cerebras-3rd-Party-Integration": "opencode",
|
"X-Cerebras-3rd-Party-Integration": "opencode",
|
||||||
})
|
})
|
||||||
|
|
@ -45,7 +45,7 @@ describe("CerebrasPlugin", () => {
|
||||||
yield* plugin.add(CerebrasPlugin)
|
yield* plugin.add(CerebrasPlugin)
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => catalog.provider.update(ProviderV2.ID.make("groq"), () => {}))
|
yield* transform((catalog) => catalog.provider.update(ProviderV2.ID.make("groq"), () => {}))
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("groq"))).options.headers).toEqual({})
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("groq"))).request.headers).toEqual({})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,20 +54,20 @@ describe("CloudflareWorkersAIPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) =>
|
yield* transform((catalog) =>
|
||||||
catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => {
|
catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => {
|
||||||
provider.endpoint = { type: "aisdk", package: "test-provider" }
|
provider.api = { type: "aisdk", package: "test-provider" }
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const provider = yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai"))
|
const provider = yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai"))
|
||||||
const sdk = yield* plugin.trigger(
|
const sdk = yield* plugin.trigger(
|
||||||
"aisdk.sdk",
|
"aisdk.sdk",
|
||||||
{
|
{
|
||||||
model: model("cloudflare-workers-ai", "@cf/model", { endpoint: provider.endpoint }),
|
model: model("cloudflare-workers-ai", "@cf/model", { api: provider.api }),
|
||||||
package: "@ai-sdk/openai-compatible",
|
package: "@ai-sdk/openai-compatible",
|
||||||
options: { name: "cloudflare-workers-ai", headers: { custom: "header" } },
|
options: { name: "cloudflare-workers-ai", headers: { custom: "header" } },
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
)
|
)
|
||||||
expect(provider.endpoint).toEqual({
|
expect(provider.api).toEqual({
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "test-provider",
|
package: "test-provider",
|
||||||
url: "https://api.cloudflare.com/client/v4/accounts/acct/ai/v1",
|
url: "https://api.cloudflare.com/client/v4/accounts/acct/ai/v1",
|
||||||
|
|
@ -86,10 +86,10 @@ describe("CloudflareWorkersAIPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) =>
|
yield* transform((catalog) =>
|
||||||
catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => {
|
catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => {
|
||||||
provider.endpoint = { type: "aisdk", package: "test-provider", url: "https://proxy.example/v1" }
|
provider.api = { type: "aisdk", package: "test-provider", url: "https://proxy.example/v1" }
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai"))).endpoint).toEqual({
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai"))).api).toEqual({
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "test-provider",
|
package: "test-provider",
|
||||||
url: "https://proxy.example/v1",
|
url: "https://proxy.example/v1",
|
||||||
|
|
@ -107,7 +107,7 @@ describe("CloudflareWorkersAIPlugin", () => {
|
||||||
"aisdk.sdk",
|
"aisdk.sdk",
|
||||||
{
|
{
|
||||||
model: model("cloudflare-workers-ai", "@cf/model", {
|
model: model("cloudflare-workers-ai", "@cf/model", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://proxy.example/v1" },
|
api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://proxy.example/v1" },
|
||||||
}),
|
}),
|
||||||
package: "@ai-sdk/openai-compatible",
|
package: "@ai-sdk/openai-compatible",
|
||||||
options: { name: "cloudflare-workers-ai", baseURL: "https://proxy.example/v1" },
|
options: { name: "cloudflare-workers-ai", baseURL: "https://proxy.example/v1" },
|
||||||
|
|
@ -152,10 +152,10 @@ describe("CloudflareWorkersAIPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) =>
|
yield* transform((catalog) =>
|
||||||
catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => {
|
catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => {
|
||||||
provider.endpoint = { type: "aisdk", package: "test-provider" }
|
provider.api = { type: "aisdk", package: "test-provider" }
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai"))).endpoint).toEqual({
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai"))).api).toEqual({
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "test-provider",
|
package: "test-provider",
|
||||||
url: "https://api.cloudflare.com/client/v4/accounts/account-acct/ai/v1",
|
url: "https://api.cloudflare.com/client/v4/accounts/account-acct/ai/v1",
|
||||||
|
|
@ -173,11 +173,11 @@ describe("CloudflareWorkersAIPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) =>
|
yield* transform((catalog) =>
|
||||||
catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => {
|
catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => {
|
||||||
provider.endpoint = { type: "aisdk", package: "test-provider" }
|
provider.api = { type: "aisdk", package: "test-provider" }
|
||||||
provider.options.aisdk.provider.accountId = "configured-acct"
|
provider.request.body.accountId = "configured-acct"
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai"))).endpoint).toEqual({
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai"))).api).toEqual({
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "test-provider",
|
package: "test-provider",
|
||||||
url: "https://api.cloudflare.com/client/v4/accounts/env-acct/ai/v1",
|
url: "https://api.cloudflare.com/client/v4/accounts/env-acct/ai/v1",
|
||||||
|
|
@ -195,7 +195,7 @@ describe("CloudflareWorkersAIPlugin", () => {
|
||||||
"aisdk.sdk",
|
"aisdk.sdk",
|
||||||
{
|
{
|
||||||
model: model("cloudflare-workers-ai", "@cf/model", {
|
model: model("cloudflare-workers-ai", "@cf/model", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://proxy.example/v1" },
|
api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://proxy.example/v1" },
|
||||||
}),
|
}),
|
||||||
package: "@ai-sdk/openai-compatible",
|
package: "@ai-sdk/openai-compatible",
|
||||||
options: {
|
options: {
|
||||||
|
|
@ -224,7 +224,7 @@ describe("CloudflareWorkersAIPlugin", () => {
|
||||||
"aisdk.sdk",
|
"aisdk.sdk",
|
||||||
{
|
{
|
||||||
model: model("cloudflare-workers-ai", "@cf/model", {
|
model: model("cloudflare-workers-ai", "@cf/model", {
|
||||||
endpoint: {
|
api: {
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "@ai-sdk/openai-compatible",
|
package: "@ai-sdk/openai-compatible",
|
||||||
url: "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1",
|
url: "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1",
|
||||||
|
|
@ -273,7 +273,7 @@ describe("CloudflareWorkersAIPlugin", () => {
|
||||||
"aisdk.sdk",
|
"aisdk.sdk",
|
||||||
{
|
{
|
||||||
model: model("cloudflare-workers-ai", "@cf/model", {
|
model: model("cloudflare-workers-ai", "@cf/model", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/anthropic", url: "https://proxy.example/v1" },
|
api: { type: "aisdk", package: "@ai-sdk/anthropic", url: "https://proxy.example/v1" },
|
||||||
}),
|
}),
|
||||||
package: "@ai-sdk/anthropic",
|
package: "@ai-sdk/anthropic",
|
||||||
options: { name: "cloudflare-workers-ai" },
|
options: { name: "cloudflare-workers-ai" },
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ describe("DeepInfraPlugin", () => {
|
||||||
yield* plugin.add(DeepInfraPlugin)
|
yield* plugin.add(DeepInfraPlugin)
|
||||||
const language = yield* aisdk.language(
|
const language = yield* aisdk.language(
|
||||||
model("deepinfra", "meta-llama/Llama-3.3-70B-Instruct", {
|
model("deepinfra", "meta-llama/Llama-3.3-70B-Instruct", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/deepinfra" },
|
api: { type: "aisdk", package: "@ai-sdk/deepinfra" },
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
expect(language.provider).toBe("deepinfra.chat")
|
expect(language.provider).toBe("deepinfra.chat")
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ describe("DynamicProviderPlugin", () => {
|
||||||
const aisdk = yield* AISDK.Service
|
const aisdk = yield* AISDK.Service
|
||||||
yield* plugin.add(dynamicPlugin(npmEntrypointLayer(Option.none<string>())))
|
yield* plugin.add(dynamicPlugin(npmEntrypointLayer(Option.none<string>())))
|
||||||
const exit = yield* aisdk
|
const exit = yield* aisdk
|
||||||
.language(model("missing-entrypoint", "alias", { endpoint: { type: "aisdk", package: "fixture-provider" } }))
|
.language(model("missing-entrypoint", "alias", { api: { type: "aisdk", package: "fixture-provider" } }))
|
||||||
.pipe(Effect.exit)
|
.pipe(Effect.exit)
|
||||||
expect(exit._tag).toBe("Failure")
|
expect(exit._tag).toBe("Failure")
|
||||||
if (exit._tag === "Failure") expect(Cause.prettyErrors(exit.cause).join("\n")).toContain("AISDK.InitError")
|
if (exit._tag === "Failure") expect(Cause.prettyErrors(exit.cause).join("\n")).toContain("AISDK.InitError")
|
||||||
|
|
@ -136,7 +136,7 @@ describe("DynamicProviderPlugin", () => {
|
||||||
yield* plugin.add(dynamicPlugin())
|
yield* plugin.add(dynamicPlugin())
|
||||||
const exit = yield* aisdk
|
const exit = yield* aisdk
|
||||||
.language(
|
.language(
|
||||||
model("bad-import", "alias", { endpoint: { type: "aisdk", package: "file:///missing/provider-factory.js" } }),
|
model("bad-import", "alias", { api: { type: "aisdk", package: "file:///missing/provider-factory.js" } }),
|
||||||
)
|
)
|
||||||
.pipe(Effect.exit)
|
.pipe(Effect.exit)
|
||||||
expect(exit._tag).toBe("Failure")
|
expect(exit._tag).toBe("Failure")
|
||||||
|
|
@ -151,7 +151,7 @@ describe("DynamicProviderPlugin", () => {
|
||||||
const tmp = yield* tempEntrypoint("export const notAProviderFactory = true\n")
|
const tmp = yield* tempEntrypoint("export const notAProviderFactory = true\n")
|
||||||
yield* plugin.add(dynamicPlugin(npmEntrypointLayer(Option.some(tmp.entrypoint))))
|
yield* plugin.add(dynamicPlugin(npmEntrypointLayer(Option.some(tmp.entrypoint))))
|
||||||
const exit = yield* aisdk
|
const exit = yield* aisdk
|
||||||
.language(model("missing-factory", "alias", { endpoint: { type: "aisdk", package: "fixture-provider" } }))
|
.language(model("missing-factory", "alias", { api: { type: "aisdk", package: "fixture-provider" } }))
|
||||||
.pipe(Effect.exit)
|
.pipe(Effect.exit)
|
||||||
expect(exit._tag).toBe("Failure")
|
expect(exit._tag).toBe("Failure")
|
||||||
if (exit._tag === "Failure") expect(Cause.prettyErrors(exit.cause).join("\n")).toContain("AISDK.InitError")
|
if (exit._tag === "Failure") expect(Cause.prettyErrors(exit.cause).join("\n")).toContain("AISDK.InitError")
|
||||||
|
|
@ -166,7 +166,7 @@ describe("DynamicProviderPlugin", () => {
|
||||||
const language = yield* aisdk.language(
|
const language = yield* aisdk.language(
|
||||||
model("custom", "alias", {
|
model("custom", "alias", {
|
||||||
apiID: ModelV2.ID.make("test-model-api"),
|
apiID: ModelV2.ID.make("test-model-api"),
|
||||||
endpoint: { type: "aisdk", package: fixtureProvider },
|
api: { type: "aisdk", package: fixtureProvider },
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
expect(language).toMatchObject({ modelID: "test-model-api", options: { name: "custom" } })
|
expect(language).toMatchObject({ modelID: "test-model-api", options: { name: "custom" } })
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,7 @@ describe("GitLabPlugin", () => {
|
||||||
{
|
{
|
||||||
model: model("gitlab", "claude"),
|
model: model("gitlab", "claude"),
|
||||||
package: "gitlab-ai-provider",
|
package: "gitlab-ai-provider",
|
||||||
options: provider.options.aisdk.provider,
|
options: provider.request.body,
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
)
|
)
|
||||||
|
|
@ -238,7 +238,7 @@ describe("GitLabPlugin", () => {
|
||||||
{
|
{
|
||||||
model: model("gitlab", "claude"),
|
model: model("gitlab", "claude"),
|
||||||
package: "gitlab-ai-provider",
|
package: "gitlab-ai-provider",
|
||||||
options: provider.options.aisdk.provider,
|
options: provider.request.body,
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
)
|
)
|
||||||
|
|
@ -256,10 +256,9 @@ describe("GitLabPlugin", () => {
|
||||||
"aisdk.language",
|
"aisdk.language",
|
||||||
{
|
{
|
||||||
model: model("gitlab", "duo-workflow-custom", {
|
model: model("gitlab", "duo-workflow-custom", {
|
||||||
options: {
|
request: {
|
||||||
headers: {},
|
headers: {},
|
||||||
body: {},
|
body: { workflowRef: "ref", workflowDefinition: "definition" },
|
||||||
aisdk: { provider: {}, request: { workflowRef: "ref", workflowDefinition: "definition" } },
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
sdk: {
|
sdk: {
|
||||||
|
|
@ -320,10 +319,9 @@ describe("GitLabPlugin", () => {
|
||||||
"aisdk.language",
|
"aisdk.language",
|
||||||
{
|
{
|
||||||
model: model("gitlab", "duo-workflow-custom", {
|
model: model("gitlab", "duo-workflow-custom", {
|
||||||
options: {
|
request: {
|
||||||
headers: {},
|
headers: {},
|
||||||
body: {},
|
body: { featureFlags: { request_flag: true } },
|
||||||
aisdk: { provider: {}, request: { featureFlags: { request_flag: true } } },
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
sdk: {
|
sdk: {
|
||||||
|
|
@ -350,7 +348,7 @@ describe("GitLabPlugin", () => {
|
||||||
"aisdk.language",
|
"aisdk.language",
|
||||||
{
|
{
|
||||||
model: model("gitlab", "claude", {
|
model: model("gitlab", "claude", {
|
||||||
options: { headers: { h: "v" }, body: {}, aisdk: { provider: {}, request: {} } },
|
request: { headers: { h: "v" }, body: {} },
|
||||||
}),
|
}),
|
||||||
sdk: {
|
sdk: {
|
||||||
workflowChat: () => undefined,
|
workflowChat: () => undefined,
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,12 @@ describe("GoogleVertexAnthropicPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) =>
|
yield* transform((catalog) =>
|
||||||
catalog.provider.update(ProviderV2.ID.make("google-vertex-anthropic"), (provider) => {
|
catalog.provider.update(ProviderV2.ID.make("google-vertex-anthropic"), (provider) => {
|
||||||
provider.endpoint = { type: "aisdk", package: "@ai-sdk/google-vertex/anthropic" }
|
provider.api = { type: "aisdk", package: "@ai-sdk/google-vertex/anthropic" }
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const provider = yield* catalog.provider.get(ProviderV2.ID.make("google-vertex-anthropic"))
|
const provider = yield* catalog.provider.get(ProviderV2.ID.make("google-vertex-anthropic"))
|
||||||
expect(provider.options.aisdk.provider.project).toBe("cloud-project")
|
expect(provider.request.body.project).toBe("cloud-project")
|
||||||
expect(provider.options.aisdk.provider.location).toBe("cloud-location")
|
expect(provider.request.body.location).toBe("cloud-location")
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -44,14 +44,14 @@ describe("GoogleVertexAnthropicPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) =>
|
yield* transform((catalog) =>
|
||||||
catalog.provider.update(ProviderV2.ID.make("google-vertex-anthropic"), (provider) => {
|
catalog.provider.update(ProviderV2.ID.make("google-vertex-anthropic"), (provider) => {
|
||||||
provider.endpoint = { type: "aisdk", package: "@ai-sdk/google-vertex/anthropic" }
|
provider.api = { type: "aisdk", package: "@ai-sdk/google-vertex/anthropic" }
|
||||||
provider.options.aisdk.provider.project = "configured-project"
|
provider.request.body.project = "configured-project"
|
||||||
provider.options.aisdk.provider.location = "configured-location"
|
provider.request.body.location = "configured-location"
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const provider = yield* catalog.provider.get(ProviderV2.ID.make("google-vertex-anthropic"))
|
const provider = yield* catalog.provider.get(ProviderV2.ID.make("google-vertex-anthropic"))
|
||||||
expect(provider.options.aisdk.provider.project).toBe("configured-project")
|
expect(provider.request.body.project).toBe("configured-project")
|
||||||
expect(provider.options.aisdk.provider.location).toBe("configured-location")
|
expect(provider.request.body.location).toBe("configured-location")
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ describe("GoogleVertexPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) =>
|
yield* transform((catalog) =>
|
||||||
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
|
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
|
||||||
provider.endpoint = {
|
provider.api = {
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "@ai-sdk/openai-compatible",
|
package: "@ai-sdk/openai-compatible",
|
||||||
url: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}",
|
url: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}",
|
||||||
|
|
@ -61,9 +61,9 @@ describe("GoogleVertexPlugin", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const provider = yield* catalog.provider.get(ProviderV2.ID.make("google-vertex"))
|
const provider = yield* catalog.provider.get(ProviderV2.ID.make("google-vertex"))
|
||||||
expect(provider.options.aisdk.provider.project).toBe("google-cloud-project")
|
expect(provider.request.body.project).toBe("google-cloud-project")
|
||||||
expect(provider.options.aisdk.provider.location).toBe("google-vertex-location")
|
expect(provider.request.body.location).toBe("google-vertex-location")
|
||||||
expect(provider.endpoint).toEqual({
|
expect(provider.api).toEqual({
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "@ai-sdk/openai-compatible",
|
package: "@ai-sdk/openai-compatible",
|
||||||
url: "https://google-vertex-location-aiplatform.googleapis.com/v1/projects/google-cloud-project/locations/google-vertex-location",
|
url: "https://google-vertex-location-aiplatform.googleapis.com/v1/projects/google-cloud-project/locations/google-vertex-location",
|
||||||
|
|
@ -92,7 +92,7 @@ describe("GoogleVertexPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) =>
|
yield* transform((catalog) =>
|
||||||
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
|
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
|
||||||
provider.endpoint = {
|
provider.api = {
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "@ai-sdk/openai-compatible",
|
package: "@ai-sdk/openai-compatible",
|
||||||
url: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}",
|
url: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}",
|
||||||
|
|
@ -104,7 +104,7 @@ describe("GoogleVertexPlugin", () => {
|
||||||
"aisdk.sdk",
|
"aisdk.sdk",
|
||||||
{
|
{
|
||||||
model: model("google-vertex", "gemini", {
|
model: model("google-vertex", "gemini", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/google-vertex" },
|
api: { type: "aisdk", package: "@ai-sdk/google-vertex" },
|
||||||
}),
|
}),
|
||||||
package: "@ai-sdk/google-vertex",
|
package: "@ai-sdk/google-vertex",
|
||||||
options: { name: "google-vertex" },
|
options: { name: "google-vertex" },
|
||||||
|
|
@ -112,8 +112,8 @@ describe("GoogleVertexPlugin", () => {
|
||||||
{},
|
{},
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(provider.options.aisdk.provider.project).toBe("vertex-project")
|
expect(provider.request.body.project).toBe("vertex-project")
|
||||||
expect(provider.endpoint).toEqual({
|
expect(provider.api).toEqual({
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "@ai-sdk/openai-compatible",
|
package: "@ai-sdk/openai-compatible",
|
||||||
url: "https://europe-west4-aiplatform.googleapis.com/v1/projects/vertex-project/locations/europe-west4",
|
url: "https://europe-west4-aiplatform.googleapis.com/v1/projects/vertex-project/locations/europe-west4",
|
||||||
|
|
@ -142,19 +142,19 @@ describe("GoogleVertexPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) =>
|
yield* transform((catalog) =>
|
||||||
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
|
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
|
||||||
provider.endpoint = {
|
provider.api = {
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "@ai-sdk/openai-compatible",
|
package: "@ai-sdk/openai-compatible",
|
||||||
url: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}",
|
url: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}",
|
||||||
}
|
}
|
||||||
provider.options.aisdk.provider.project = "config-project"
|
provider.request.body.project = "config-project"
|
||||||
provider.options.aisdk.provider.location = "global"
|
provider.request.body.location = "global"
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const provider = yield* catalog.provider.get(ProviderV2.ID.make("google-vertex"))
|
const provider = yield* catalog.provider.get(ProviderV2.ID.make("google-vertex"))
|
||||||
expect(provider.options.aisdk.provider.project).toBe("config-project")
|
expect(provider.request.body.project).toBe("config-project")
|
||||||
expect(provider.options.aisdk.provider.location).toBe("global")
|
expect(provider.request.body.location).toBe("global")
|
||||||
expect(provider.endpoint).toEqual({
|
expect(provider.api).toEqual({
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "@ai-sdk/openai-compatible",
|
package: "@ai-sdk/openai-compatible",
|
||||||
url: "https://aiplatform.googleapis.com/v1/projects/config-project/locations/global",
|
url: "https://aiplatform.googleapis.com/v1/projects/config-project/locations/global",
|
||||||
|
|
@ -171,17 +171,17 @@ describe("GoogleVertexPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) =>
|
yield* transform((catalog) =>
|
||||||
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
|
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
|
||||||
provider.endpoint = {
|
provider.api = {
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "@ai-sdk/openai-compatible",
|
package: "@ai-sdk/openai-compatible",
|
||||||
url: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}",
|
url: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}",
|
||||||
}
|
}
|
||||||
provider.options.aisdk.provider.project = "config-project"
|
provider.request.body.project = "config-project"
|
||||||
provider.options.aisdk.provider.location = "eu"
|
provider.request.body.location = "eu"
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const provider = yield* catalog.provider.get(ProviderV2.ID.make("google-vertex"))
|
const provider = yield* catalog.provider.get(ProviderV2.ID.make("google-vertex"))
|
||||||
expect(provider.endpoint).toEqual({
|
expect(provider.api).toEqual({
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "@ai-sdk/openai-compatible",
|
package: "@ai-sdk/openai-compatible",
|
||||||
url: "https://eu-aiplatform.googleapis.com/v1/projects/config-project/locations/eu",
|
url: "https://eu-aiplatform.googleapis.com/v1/projects/config-project/locations/eu",
|
||||||
|
|
@ -207,13 +207,13 @@ describe("GoogleVertexPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) =>
|
yield* transform((catalog) =>
|
||||||
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
|
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
|
||||||
provider.endpoint = { type: "aisdk", package: "@ai-sdk/google-vertex" }
|
provider.api = { type: "aisdk", package: "@ai-sdk/google-vertex" }
|
||||||
provider.options.aisdk.provider.project = "config-project"
|
provider.request.body.project = "config-project"
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const provider = yield* catalog.provider.get(ProviderV2.ID.make("google-vertex"))
|
const provider = yield* catalog.provider.get(ProviderV2.ID.make("google-vertex"))
|
||||||
expect(provider.options.aisdk.provider.project).toBe("config-project")
|
expect(provider.request.body.project).toBe("config-project")
|
||||||
expect(provider.options.aisdk.provider.location).toBe("us-central1")
|
expect(provider.request.body.location).toBe("us-central1")
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -233,7 +233,7 @@ describe("GoogleVertexPlugin", () => {
|
||||||
"aisdk.sdk",
|
"aisdk.sdk",
|
||||||
{
|
{
|
||||||
model: model("google-vertex", "gemini", {
|
model: model("google-vertex", "gemini", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/google-vertex" },
|
api: { type: "aisdk", package: "@ai-sdk/google-vertex" },
|
||||||
}),
|
}),
|
||||||
package: "@ai-sdk/google-vertex",
|
package: "@ai-sdk/google-vertex",
|
||||||
options: { name: "google-vertex" },
|
options: { name: "google-vertex" },
|
||||||
|
|
@ -283,7 +283,7 @@ describe("GoogleVertexPlugin", () => {
|
||||||
"aisdk.sdk",
|
"aisdk.sdk",
|
||||||
{
|
{
|
||||||
model: model("google-vertex", "gemini", {
|
model: model("google-vertex", "gemini", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible" },
|
api: { type: "aisdk", package: "@ai-sdk/openai-compatible" },
|
||||||
}),
|
}),
|
||||||
package: "@ai-sdk/openai-compatible",
|
package: "@ai-sdk/openai-compatible",
|
||||||
options: { name: "google-vertex" },
|
options: { name: "google-vertex" },
|
||||||
|
|
|
||||||
|
|
@ -52,17 +52,13 @@ describe("GooglePlugin", () => {
|
||||||
const language = yield* aisdk.language(
|
const language = yield* aisdk.language(
|
||||||
model("custom-google", "alias", {
|
model("custom-google", "alias", {
|
||||||
apiID: ModelV2.ID.make("gemini-api"),
|
apiID: ModelV2.ID.make("gemini-api"),
|
||||||
endpoint: {
|
api: {
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "@ai-sdk/google",
|
package: "@ai-sdk/google",
|
||||||
},
|
},
|
||||||
options: {
|
request: {
|
||||||
headers: {},
|
headers: {},
|
||||||
body: {},
|
body: { apiKey: "test" },
|
||||||
aisdk: {
|
|
||||||
provider: { apiKey: "test" },
|
|
||||||
request: {},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -83,17 +83,13 @@ describe("GroqPlugin", () => {
|
||||||
const result = yield* aisdk.language(
|
const result = yield* aisdk.language(
|
||||||
model("groq", "alias", {
|
model("groq", "alias", {
|
||||||
apiID: ModelV2.ID.make("llama-api"),
|
apiID: ModelV2.ID.make("llama-api"),
|
||||||
endpoint: {
|
api: {
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "@ai-sdk/groq",
|
package: "@ai-sdk/groq",
|
||||||
},
|
},
|
||||||
options: {
|
request: {
|
||||||
headers: {},
|
headers: {},
|
||||||
body: {},
|
body: { apiKey: "test" },
|
||||||
aisdk: {
|
|
||||||
provider: { apiKey: "test" },
|
|
||||||
request: {},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -54,43 +54,45 @@ export const it = testEffect(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
export function provider(providerID: string, options?: Partial<ProviderV2.Info>) {
|
type ProviderInput = Partial<Omit<ProviderV2.Info, "api" | "request">> & {
|
||||||
|
api?: ProviderV2.Api
|
||||||
|
request?: ProviderV2.Request
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModelInput = Partial<Omit<ModelV2.Info, "api" | "request">> & {
|
||||||
|
api?: ProviderV2.Api
|
||||||
|
request?: ModelV2.Info["request"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function provider(providerID: string, options?: ProviderInput) {
|
||||||
return new ProviderV2.Info({
|
return new ProviderV2.Info({
|
||||||
...ProviderV2.Info.empty(ProviderV2.ID.make(providerID)),
|
...ProviderV2.Info.empty(ProviderV2.ID.make(providerID)),
|
||||||
endpoint: {
|
api: options?.api ?? {
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "test-provider",
|
package: "test-provider",
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
options: {
|
request: {
|
||||||
headers: {},
|
headers: {},
|
||||||
body: {},
|
body: {},
|
||||||
aisdk: {
|
...options?.request,
|
||||||
provider: {},
|
|
||||||
request: {},
|
|
||||||
},
|
|
||||||
...options?.options,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function model(providerID: string, modelID: string, options?: Partial<ModelV2.Info>) {
|
export function model(providerID: string, modelID: string, options?: ModelInput) {
|
||||||
return new ModelV2.Info({
|
return new ModelV2.Info({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)),
|
...ModelV2.Info.empty(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)),
|
||||||
apiID: ModelV2.ID.make(modelID),
|
apiID: ModelV2.ID.make(modelID),
|
||||||
endpoint: {
|
api: options?.api ?? {
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "test-provider",
|
package: "test-provider",
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
options: {
|
request: {
|
||||||
headers: {},
|
headers: {},
|
||||||
body: {},
|
body: {},
|
||||||
aisdk: {
|
...options?.request,
|
||||||
provider: {},
|
|
||||||
request: {},
|
|
||||||
},
|
|
||||||
...options?.options,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,21 +25,21 @@ describe("KiloPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const kilo = provider("kilo", {
|
const kilo = provider("kilo", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.kilo.ai/api/gateway" },
|
api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.kilo.ai/api/gateway" },
|
||||||
options: { headers: { Existing: "value" }, body: {}, aisdk: { provider: {}, request: {} } },
|
request: { headers: { Existing: "value" }, body: {} },
|
||||||
})
|
})
|
||||||
catalog.provider.update(kilo.id, (draft) => {
|
catalog.provider.update(kilo.id, (draft) => {
|
||||||
draft.endpoint = kilo.endpoint
|
draft.api = kilo.api
|
||||||
draft.options = kilo.options
|
draft.request = kilo.request
|
||||||
})
|
})
|
||||||
catalog.provider.update(provider("openrouter").id, () => {})
|
catalog.provider.update(provider("openrouter").id, () => {})
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo"))).options.headers).toEqual({
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo"))).request.headers).toEqual({
|
||||||
Existing: "value",
|
Existing: "value",
|
||||||
"HTTP-Referer": "https://opencode.ai/",
|
"HTTP-Referer": "https://opencode.ai/",
|
||||||
"X-Title": "opencode",
|
"X-Title": "opencode",
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.openrouter)).options.headers).toEqual({})
|
expect((yield* catalog.provider.get(ProviderV2.ID.openrouter)).request.headers).toEqual({})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -51,21 +51,21 @@ describe("KiloPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const item = provider("kilo", {
|
const item = provider("kilo", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.kilo.ai/api/gateway" },
|
api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.kilo.ai/api/gateway" },
|
||||||
})
|
})
|
||||||
catalog.provider.update(item.id, (draft) => {
|
catalog.provider.update(item.id, (draft) => {
|
||||||
draft.endpoint = item.endpoint
|
draft.api = item.api
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const result = yield* catalog.provider.get(ProviderV2.ID.make("kilo"))
|
const result = yield* catalog.provider.get(ProviderV2.ID.make("kilo"))
|
||||||
expect(result.options.headers).toEqual({
|
expect(result.request.headers).toEqual({
|
||||||
"HTTP-Referer": "https://opencode.ai/",
|
"HTTP-Referer": "https://opencode.ai/",
|
||||||
"X-Title": "opencode",
|
"X-Title": "opencode",
|
||||||
})
|
})
|
||||||
expect(result.options.headers).not.toHaveProperty("http-referer")
|
expect(result.request.headers).not.toHaveProperty("http-referer")
|
||||||
expect(result.options.headers).not.toHaveProperty("x-title")
|
expect(result.request.headers).not.toHaveProperty("x-title")
|
||||||
expect(result.options.headers).not.toHaveProperty("X-Source")
|
expect(result.request.headers).not.toHaveProperty("X-Source")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -77,24 +77,24 @@ describe("KiloPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const kilo = provider("kilo", {
|
const kilo = provider("kilo", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.kilo.ai/api/gateway" },
|
api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.kilo.ai/api/gateway" },
|
||||||
})
|
})
|
||||||
catalog.provider.update(kilo.id, (draft) => {
|
catalog.provider.update(kilo.id, (draft) => {
|
||||||
draft.endpoint = kilo.endpoint
|
draft.api = kilo.api
|
||||||
})
|
})
|
||||||
const custom = provider("custom-kilo", {
|
const custom = provider("custom-kilo", {
|
||||||
endpoint: { type: "aisdk", package: "kilo" },
|
api: { type: "aisdk", package: "kilo" },
|
||||||
})
|
})
|
||||||
catalog.provider.update(custom.id, (draft) => {
|
catalog.provider.update(custom.id, (draft) => {
|
||||||
draft.endpoint = custom.endpoint
|
draft.api = custom.api
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo"))).options.headers).toEqual({
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo"))).request.headers).toEqual({
|
||||||
"HTTP-Referer": "https://opencode.ai/",
|
"HTTP-Referer": "https://opencode.ai/",
|
||||||
"X-Title": "opencode",
|
"X-Title": "opencode",
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("custom-kilo"))).options.headers).toEqual({})
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("custom-kilo"))).request.headers).toEqual({})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,13 @@ describe("LLMGatewayPlugin", () => {
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const llmgateway = provider("llmgateway", {
|
const llmgateway = provider("llmgateway", {
|
||||||
enabled: { via: "env", name: "LLMGATEWAY_API_KEY" },
|
enabled: { via: "env", name: "LLMGATEWAY_API_KEY" },
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.llmgateway.io/v1" },
|
api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.llmgateway.io/v1" },
|
||||||
options: { headers: { Existing: "value" }, body: {}, aisdk: { provider: {}, request: {} } },
|
request: { headers: { Existing: "value" }, body: {} },
|
||||||
})
|
})
|
||||||
catalog.provider.update(llmgateway.id, (draft) => {
|
catalog.provider.update(llmgateway.id, (draft) => {
|
||||||
draft.enabled = llmgateway.enabled
|
draft.enabled = llmgateway.enabled
|
||||||
draft.endpoint = llmgateway.endpoint
|
draft.api = llmgateway.api
|
||||||
draft.options = llmgateway.options
|
draft.request = llmgateway.request
|
||||||
})
|
})
|
||||||
const openrouter = provider("openrouter", {
|
const openrouter = provider("openrouter", {
|
||||||
enabled: { via: "env", name: "OPENROUTER_API_KEY" },
|
enabled: { via: "env", name: "OPENROUTER_API_KEY" },
|
||||||
|
|
@ -41,13 +41,13 @@ describe("LLMGatewayPlugin", () => {
|
||||||
draft.enabled = openrouter.enabled
|
draft.enabled = openrouter.enabled
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("llmgateway"))).options.headers).toEqual({
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("llmgateway"))).request.headers).toEqual({
|
||||||
Existing: "value",
|
Existing: "value",
|
||||||
"HTTP-Referer": "https://opencode.ai/",
|
"HTTP-Referer": "https://opencode.ai/",
|
||||||
"X-Title": "opencode",
|
"X-Title": "opencode",
|
||||||
"X-Source": "opencode",
|
"X-Source": "opencode",
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.openrouter)).options.headers).toEqual({})
|
expect((yield* catalog.provider.get(ProviderV2.ID.openrouter)).request.headers).toEqual({})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -59,15 +59,15 @@ describe("LLMGatewayPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const item = provider("llmgateway", {
|
const item = provider("llmgateway", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.llmgateway.io/v1" },
|
api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.llmgateway.io/v1" },
|
||||||
})
|
})
|
||||||
catalog.provider.update(item.id, (draft) => {
|
catalog.provider.update(item.id, (draft) => {
|
||||||
draft.endpoint = item.endpoint
|
draft.api = item.api
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("llmgateway"))).enabled).toBe(false)
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("llmgateway"))).enabled).toBe(false)
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("llmgateway"))).options.headers).toEqual({})
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("llmgateway"))).request.headers).toEqual({})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -25,22 +25,22 @@ describe("NvidiaPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const nvidia = provider("nvidia", {
|
const nvidia = provider("nvidia", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://integrate.api.nvidia.com/v1" },
|
api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://integrate.api.nvidia.com/v1" },
|
||||||
options: { headers: { Existing: "value" }, body: {}, aisdk: { provider: {}, request: {} } },
|
request: { headers: { Existing: "value" }, body: {} },
|
||||||
})
|
})
|
||||||
catalog.provider.update(nvidia.id, (draft) => {
|
catalog.provider.update(nvidia.id, (draft) => {
|
||||||
draft.endpoint = nvidia.endpoint
|
draft.api = nvidia.api
|
||||||
draft.options = nvidia.options
|
draft.request = nvidia.request
|
||||||
})
|
})
|
||||||
catalog.provider.update(provider("openrouter").id, () => {})
|
catalog.provider.update(provider("openrouter").id, () => {})
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia"))).options.headers).toEqual({
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia"))).request.headers).toEqual({
|
||||||
Existing: "value",
|
Existing: "value",
|
||||||
"HTTP-Referer": "https://opencode.ai/",
|
"HTTP-Referer": "https://opencode.ai/",
|
||||||
"X-Title": "opencode",
|
"X-Title": "opencode",
|
||||||
"X-BILLING-INVOKE-ORIGIN": "OpenCode",
|
"X-BILLING-INVOKE-ORIGIN": "OpenCode",
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.openrouter)).options.headers).toEqual({})
|
expect((yield* catalog.provider.get(ProviderV2.ID.openrouter)).request.headers).toEqual({})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -52,16 +52,16 @@ describe("NvidiaPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const item = provider("nvidia", {
|
const item = provider("nvidia", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://integrate.api.nvidia.com/v1" },
|
api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://integrate.api.nvidia.com/v1" },
|
||||||
options: { headers: {}, body: {}, aisdk: { provider: {}, request: {} } },
|
request: { headers: {}, body: {} },
|
||||||
})
|
})
|
||||||
catalog.provider.update(item.id, (draft) => {
|
catalog.provider.update(item.id, (draft) => {
|
||||||
draft.endpoint = item.endpoint
|
draft.api = item.api
|
||||||
draft.options = item.options
|
draft.request = item.request
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia"))).options.headers).toEqual({
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia"))).request.headers).toEqual({
|
||||||
"HTTP-Referer": "https://opencode.ai/",
|
"HTTP-Referer": "https://opencode.ai/",
|
||||||
"X-Title": "opencode",
|
"X-Title": "opencode",
|
||||||
"X-BILLING-INVOKE-ORIGIN": "OpenCode",
|
"X-BILLING-INVOKE-ORIGIN": "OpenCode",
|
||||||
|
|
@ -77,20 +77,19 @@ describe("NvidiaPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const item = provider("nvidia", {
|
const item = provider("nvidia", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://integrate.api.nvidia.com/v1" },
|
api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://integrate.api.nvidia.com/v1" },
|
||||||
options: {
|
request: {
|
||||||
headers: { "X-BILLING-INVOKE-ORIGIN": "CustomOrigin" },
|
headers: { "X-BILLING-INVOKE-ORIGIN": "CustomOrigin" },
|
||||||
body: {},
|
body: { baseURL: "https://integrate.api.nvidia.com/v1" },
|
||||||
aisdk: { provider: { baseURL: "https://integrate.api.nvidia.com/v1" }, request: {} },
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
catalog.provider.update(item.id, (draft) => {
|
catalog.provider.update(item.id, (draft) => {
|
||||||
draft.endpoint = item.endpoint
|
draft.api = item.api
|
||||||
draft.options = item.options
|
draft.request = item.request
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia"))).options.headers).toEqual({
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia"))).request.headers).toEqual({
|
||||||
"HTTP-Referer": "https://opencode.ai/",
|
"HTTP-Referer": "https://opencode.ai/",
|
||||||
"X-Title": "opencode",
|
"X-Title": "opencode",
|
||||||
"X-BILLING-INVOKE-ORIGIN": "CustomOrigin",
|
"X-BILLING-INVOKE-ORIGIN": "CustomOrigin",
|
||||||
|
|
|
||||||
|
|
@ -79,9 +79,9 @@ describe("OpenAIPlugin", () => {
|
||||||
yield* plugin.add(OpenAIPlugin)
|
yield* plugin.add(OpenAIPlugin)
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const item = provider("openai", { endpoint: { type: "aisdk", package: "@ai-sdk/openai" } })
|
const item = provider("openai", { api: { type: "aisdk", package: "@ai-sdk/openai" } })
|
||||||
catalog.provider.update(item.id, (draft) => {
|
catalog.provider.update(item.id, (draft) => {
|
||||||
draft.endpoint = item.endpoint
|
draft.api = item.api
|
||||||
})
|
})
|
||||||
catalog.model.update(item.id, ModelV2.ID.make("gpt-5"), () => {})
|
catalog.model.update(item.id, ModelV2.ID.make("gpt-5"), () => {})
|
||||||
catalog.model.update(item.id, ModelV2.ID.make("gpt-5-chat-latest"), () => {})
|
catalog.model.update(item.id, ModelV2.ID.make("gpt-5-chat-latest"), () => {})
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ describe("OpencodePlugin", () => {
|
||||||
draft.cost = [...paid.cost]
|
draft.cost = [...paid.cost]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.aisdk.provider.apiKey).toBe("public")
|
expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).request.body.apiKey).toBe("public")
|
||||||
expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(false)
|
expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(false)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|
@ -54,7 +54,7 @@ describe("OpencodePlugin", () => {
|
||||||
draft.cost = [...free.cost]
|
draft.cost = [...free.cost]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.aisdk.provider.apiKey).toBe("public")
|
expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).request.body.apiKey).toBe("public")
|
||||||
expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("free"))).enabled).toBe(true)
|
expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("free"))).enabled).toBe(true)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|
@ -75,7 +75,7 @@ describe("OpencodePlugin", () => {
|
||||||
draft.cost = [...outputOnly.cost]
|
draft.cost = [...outputOnly.cost]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.aisdk.provider.apiKey).toBe("public")
|
expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).request.body.apiKey).toBe("public")
|
||||||
expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("output-only"))).enabled).toBe(true)
|
expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("output-only"))).enabled).toBe(true)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|
@ -96,7 +96,7 @@ describe("OpencodePlugin", () => {
|
||||||
draft.cost = [...paid.cost]
|
draft.cost = [...paid.cost]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.aisdk.provider.apiKey).toBeUndefined()
|
expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).request.body.apiKey).toBeUndefined()
|
||||||
expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true)
|
expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|
@ -119,7 +119,7 @@ describe("OpencodePlugin", () => {
|
||||||
draft.cost = [...paid.cost]
|
draft.cost = [...paid.cost]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.aisdk.provider.apiKey).toBeUndefined()
|
expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).request.body.apiKey).toBeUndefined()
|
||||||
expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true)
|
expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|
@ -134,24 +134,20 @@ describe("OpencodePlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const item = provider("opencode", {
|
const item = provider("opencode", {
|
||||||
options: {
|
request: {
|
||||||
headers: {},
|
headers: {},
|
||||||
body: {},
|
body: { apiKey: "configured" },
|
||||||
aisdk: {
|
|
||||||
provider: { apiKey: "configured" },
|
|
||||||
request: {},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
catalog.provider.update(item.id, (draft) => {
|
catalog.provider.update(item.id, (draft) => {
|
||||||
draft.options = item.options
|
draft.request = item.request
|
||||||
})
|
})
|
||||||
const paid = model("opencode", "paid", { cost: cost(1) })
|
const paid = model("opencode", "paid", { cost: cost(1) })
|
||||||
catalog.model.update(item.id, paid.id, (draft) => {
|
catalog.model.update(item.id, paid.id, (draft) => {
|
||||||
draft.cost = [...paid.cost]
|
draft.cost = [...paid.cost]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.aisdk.provider.apiKey).toBe("configured")
|
expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).request.body.apiKey).toBe("configured")
|
||||||
expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true)
|
expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|
@ -174,7 +170,7 @@ describe("OpencodePlugin", () => {
|
||||||
draft.cost = [...paid.cost]
|
draft.cost = [...paid.cost]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.aisdk.provider.apiKey).toBeUndefined()
|
expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).request.body.apiKey).toBeUndefined()
|
||||||
expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true)
|
expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|
@ -195,7 +191,7 @@ describe("OpencodePlugin", () => {
|
||||||
draft.cost = [...paid.cost]
|
draft.cost = [...paid.cost]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.openai)).options.aisdk.provider.apiKey).toBeUndefined()
|
expect((yield* catalog.provider.get(ProviderV2.ID.openai)).request.body.apiKey).toBeUndefined()
|
||||||
expect((yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("paid"))).enabled).toBe(true)
|
expect((yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("paid"))).enabled).toBe(true)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -26,22 +26,22 @@ describe("OpenRouterPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const openrouter = provider("openrouter", {
|
const openrouter = provider("openrouter", {
|
||||||
endpoint: { type: "aisdk", package: "@openrouter/ai-sdk-provider" },
|
api: { type: "aisdk", package: "@openrouter/ai-sdk-provider" },
|
||||||
options: { headers: { Existing: "value" }, body: {}, aisdk: { provider: {}, request: {} } },
|
request: { headers: { Existing: "value" }, body: {} },
|
||||||
})
|
})
|
||||||
catalog.provider.update(openrouter.id, (item) => {
|
catalog.provider.update(openrouter.id, (item) => {
|
||||||
item.endpoint = openrouter.endpoint
|
item.api = openrouter.api
|
||||||
item.options = openrouter.options
|
item.request = openrouter.request
|
||||||
})
|
})
|
||||||
catalog.provider.update(ProviderV2.ID.make("nvidia"), () => {})
|
catalog.provider.update(ProviderV2.ID.make("nvidia"), () => {})
|
||||||
})
|
})
|
||||||
|
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("openrouter"))).options.headers).toEqual({
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("openrouter"))).request.headers).toEqual({
|
||||||
Existing: "value",
|
Existing: "value",
|
||||||
"HTTP-Referer": "https://opencode.ai/",
|
"HTTP-Referer": "https://opencode.ai/",
|
||||||
"X-Title": "opencode",
|
"X-Title": "opencode",
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia"))).options.headers).toEqual({})
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia"))).request.headers).toEqual({})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -78,10 +78,10 @@ describe("OpenRouterPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const openrouter = provider("openrouter", {
|
const openrouter = provider("openrouter", {
|
||||||
endpoint: { type: "aisdk", package: "@openrouter/ai-sdk-provider" },
|
api: { type: "aisdk", package: "@openrouter/ai-sdk-provider" },
|
||||||
})
|
})
|
||||||
catalog.provider.update(openrouter.id, (item) => {
|
catalog.provider.update(openrouter.id, (item) => {
|
||||||
item.endpoint = openrouter.endpoint
|
item.api = openrouter.api
|
||||||
})
|
})
|
||||||
catalog.provider.update(ProviderV2.ID.openai, () => {})
|
catalog.provider.update(ProviderV2.ID.openai, () => {})
|
||||||
for (const item of [
|
for (const item of [
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,15 @@ describe("VercelPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const item = provider("vercel", {
|
const item = provider("vercel", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/vercel" },
|
api: { type: "aisdk", package: "@ai-sdk/vercel" },
|
||||||
options: { headers: { Existing: "1" }, body: {}, aisdk: { provider: {}, request: {} } },
|
request: { headers: { Existing: "1" }, body: {} },
|
||||||
})
|
})
|
||||||
catalog.provider.update(item.id, (draft) => {
|
catalog.provider.update(item.id, (draft) => {
|
||||||
draft.endpoint = item.endpoint
|
draft.api = item.api
|
||||||
draft.options = item.options
|
draft.request = item.request
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel"))).options.headers).toEqual({
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel"))).request.headers).toEqual({
|
||||||
Existing: "1",
|
Existing: "1",
|
||||||
"http-referer": "https://opencode.ai/",
|
"http-referer": "https://opencode.ai/",
|
||||||
"x-title": "opencode",
|
"x-title": "opencode",
|
||||||
|
|
@ -38,15 +38,15 @@ describe("VercelPlugin", () => {
|
||||||
yield* plugin.add(VercelPlugin)
|
yield* plugin.add(VercelPlugin)
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const item = provider("vercel", { endpoint: { type: "aisdk", package: "@ai-sdk/vercel" } })
|
const item = provider("vercel", { api: { type: "aisdk", package: "@ai-sdk/vercel" } })
|
||||||
catalog.provider.update(item.id, (draft) => {
|
catalog.provider.update(item.id, (draft) => {
|
||||||
draft.endpoint = item.endpoint
|
draft.api = item.api
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel"))).options.headers).not.toHaveProperty(
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel"))).request.headers).not.toHaveProperty(
|
||||||
"HTTP-Referer",
|
"HTTP-Referer",
|
||||||
)
|
)
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel"))).options.headers).not.toHaveProperty("X-Title")
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel"))).request.headers).not.toHaveProperty("X-Title")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -71,7 +71,7 @@ describe("VercelPlugin", () => {
|
||||||
yield* plugin.add(VercelPlugin)
|
yield* plugin.add(VercelPlugin)
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => catalog.provider.update(provider("gateway").id, () => {}))
|
yield* transform((catalog) => catalog.provider.update(provider("gateway").id, () => {}))
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("gateway"))).options.headers).toEqual({})
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("gateway"))).request.headers).toEqual({})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ const it = testEffect(PluginV2.locationLayer.pipe(Layer.provide(EventV2.defaultL
|
||||||
const model = new ModelV2.Info({
|
const model = new ModelV2.Info({
|
||||||
...ModelV2.Info.empty(ProviderV2.ID.make("xai"), ModelV2.ID.make("grok-4")),
|
...ModelV2.Info.empty(ProviderV2.ID.make("xai"), ModelV2.ID.make("grok-4")),
|
||||||
apiID: ModelV2.ID.make("grok-4"),
|
apiID: ModelV2.ID.make("grok-4"),
|
||||||
endpoint: {
|
api: {
|
||||||
type: "aisdk",
|
type: "aisdk",
|
||||||
package: "@ai-sdk/xai",
|
package: "@ai-sdk/xai",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -25,15 +25,15 @@ describe("ZenmuxPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const item = provider("zenmux", {
|
const item = provider("zenmux", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://zenmux.ai/api/v1" },
|
api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://zenmux.ai/api/v1" },
|
||||||
})
|
})
|
||||||
catalog.provider.update(item.id, (draft) => {
|
catalog.provider.update(item.id, (draft) => {
|
||||||
draft.endpoint = item.endpoint
|
draft.api = item.api
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
const result = yield* catalog.provider.get(ProviderV2.ID.make("zenmux"))
|
const result = yield* catalog.provider.get(ProviderV2.ID.make("zenmux"))
|
||||||
expect(result.options.headers).toEqual({ "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode" })
|
expect(result.request.headers).toEqual({ "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode" })
|
||||||
expect(Object.keys(result.options.headers).sort()).toEqual(["HTTP-Referer", "X-Title"])
|
expect(Object.keys(result.request.headers).sort()).toEqual(["HTTP-Referer", "X-Title"])
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -45,16 +45,16 @@ describe("ZenmuxPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const item = provider("zenmux", {
|
const item = provider("zenmux", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://zenmux.ai/api/v1" },
|
api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://zenmux.ai/api/v1" },
|
||||||
options: { headers: { Existing: "value" }, body: {}, aisdk: { provider: {}, request: {} } },
|
request: { headers: { Existing: "value" }, body: {} },
|
||||||
})
|
})
|
||||||
catalog.provider.update(item.id, (draft) => {
|
catalog.provider.update(item.id, (draft) => {
|
||||||
draft.endpoint = item.endpoint
|
draft.api = item.api
|
||||||
draft.options = item.options
|
draft.request = item.request
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("zenmux"))).options.headers).toEqual({
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("zenmux"))).request.headers).toEqual({
|
||||||
Existing: "value",
|
Existing: "value",
|
||||||
"HTTP-Referer": "https://opencode.ai/",
|
"HTTP-Referer": "https://opencode.ai/",
|
||||||
"X-Title": "opencode",
|
"X-Title": "opencode",
|
||||||
|
|
@ -70,20 +70,19 @@ describe("ZenmuxPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const item = provider("zenmux", {
|
const item = provider("zenmux", {
|
||||||
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://zenmux.ai/api/v1" },
|
api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://zenmux.ai/api/v1" },
|
||||||
options: {
|
request: {
|
||||||
headers: { "HTTP-Referer": "https://example.com/", "X-Title": "custom-title" },
|
headers: { "HTTP-Referer": "https://example.com/", "X-Title": "custom-title" },
|
||||||
body: {},
|
body: {},
|
||||||
aisdk: { provider: {}, request: {} },
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
catalog.provider.update(item.id, (draft) => {
|
catalog.provider.update(item.id, (draft) => {
|
||||||
draft.endpoint = item.endpoint
|
draft.api = item.api
|
||||||
draft.options = item.options
|
draft.request = item.request
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.make("zenmux"))).options.headers).toEqual({
|
expect((yield* catalog.provider.get(ProviderV2.ID.make("zenmux"))).request.headers).toEqual({
|
||||||
"HTTP-Referer": "https://example.com/",
|
"HTTP-Referer": "https://example.com/",
|
||||||
"X-Title": "custom-title",
|
"X-Title": "custom-title",
|
||||||
})
|
})
|
||||||
|
|
@ -98,18 +97,17 @@ describe("ZenmuxPlugin", () => {
|
||||||
const transform = yield* catalog.transform()
|
const transform = yield* catalog.transform()
|
||||||
yield* transform((catalog) => {
|
yield* transform((catalog) => {
|
||||||
const item = provider("openrouter", {
|
const item = provider("openrouter", {
|
||||||
options: {
|
request: {
|
||||||
headers: { "HTTP-Referer": "https://example.com/", "X-Title": "custom-title" },
|
headers: { "HTTP-Referer": "https://example.com/", "X-Title": "custom-title" },
|
||||||
body: {},
|
body: {},
|
||||||
aisdk: { provider: {}, request: {} },
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
catalog.provider.update(item.id, (draft) => {
|
catalog.provider.update(item.id, (draft) => {
|
||||||
draft.options = item.options
|
draft.request = item.request
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
expect((yield* catalog.provider.get(ProviderV2.ID.openrouter)).options.headers).toEqual({
|
expect((yield* catalog.provider.get(ProviderV2.ID.openrouter)).request.headers).toEqual({
|
||||||
"HTTP-Referer": "https://example.com/",
|
"HTTP-Referer": "https://example.com/",
|
||||||
"X-Title": "custom-title",
|
"X-Title": "custom-title",
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2689,55 +2689,34 @@ export type ModelV2Info = {
|
||||||
providerID: string
|
providerID: string
|
||||||
family?: string
|
family?: string
|
||||||
name: string
|
name: string
|
||||||
endpoint:
|
api:
|
||||||
| {
|
|
||||||
type: "unknown"
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "openai/responses"
|
|
||||||
url: string
|
|
||||||
websocket?: boolean
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "openai/completions"
|
|
||||||
url: string
|
|
||||||
reasoning?:
|
|
||||||
| {
|
|
||||||
type: "reasoning_content"
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "reasoning_details"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "anthropic/messages"
|
|
||||||
url: string
|
|
||||||
}
|
|
||||||
| {
|
| {
|
||||||
type: "aisdk"
|
type: "aisdk"
|
||||||
package: string
|
package: string
|
||||||
url?: string
|
url?: string
|
||||||
|
settings?: {
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "native"
|
||||||
|
url?: string
|
||||||
|
settings: {
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
}
|
}
|
||||||
capabilities: {
|
capabilities: {
|
||||||
tools: boolean
|
tools: boolean
|
||||||
input: Array<string>
|
input: Array<string>
|
||||||
output: Array<string>
|
output: Array<string>
|
||||||
}
|
}
|
||||||
options: {
|
request: {
|
||||||
headers: {
|
headers: {
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
}
|
}
|
||||||
body: {
|
body: {
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
aisdk: {
|
|
||||||
provider: {
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
request: {
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
variant?: string
|
variant?: string
|
||||||
}
|
}
|
||||||
variants: Array<{
|
variants: Array<{
|
||||||
|
|
@ -2748,14 +2727,6 @@ export type ModelV2Info = {
|
||||||
body: {
|
body: {
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
aisdk: {
|
|
||||||
provider: {
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
request: {
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}>
|
}>
|
||||||
time: {
|
time: {
|
||||||
released: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
released: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||||
|
|
@ -3640,50 +3611,29 @@ export type ProviderV2Info = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
env: Array<string>
|
env: Array<string>
|
||||||
endpoint:
|
api:
|
||||||
| {
|
|
||||||
type: "unknown"
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "openai/responses"
|
|
||||||
url: string
|
|
||||||
websocket?: boolean
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "openai/completions"
|
|
||||||
url: string
|
|
||||||
reasoning?:
|
|
||||||
| {
|
|
||||||
type: "reasoning_content"
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "reasoning_details"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "anthropic/messages"
|
|
||||||
url: string
|
|
||||||
}
|
|
||||||
| {
|
| {
|
||||||
type: "aisdk"
|
type: "aisdk"
|
||||||
package: string
|
package: string
|
||||||
url?: string
|
url?: string
|
||||||
|
settings?: {
|
||||||
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
options: {
|
}
|
||||||
|
| {
|
||||||
|
type: "native"
|
||||||
|
url?: string
|
||||||
|
settings: {
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request: {
|
||||||
headers: {
|
headers: {
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
}
|
}
|
||||||
body: {
|
body: {
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
aisdk: {
|
|
||||||
provider: {
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
request: {
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3748,55 +3698,34 @@ export type ModelV2Info1 = {
|
||||||
providerID: string
|
providerID: string
|
||||||
family?: string
|
family?: string
|
||||||
name: string
|
name: string
|
||||||
endpoint:
|
api:
|
||||||
| {
|
|
||||||
type: "unknown"
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "openai/responses"
|
|
||||||
url: string
|
|
||||||
websocket?: boolean
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "openai/completions"
|
|
||||||
url: string
|
|
||||||
reasoning?:
|
|
||||||
| {
|
|
||||||
type: "reasoning_content"
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "reasoning_details"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "anthropic/messages"
|
|
||||||
url: string
|
|
||||||
}
|
|
||||||
| {
|
| {
|
||||||
type: "aisdk"
|
type: "aisdk"
|
||||||
package: string
|
package: string
|
||||||
url?: string
|
url?: string
|
||||||
|
settings?: {
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "native"
|
||||||
|
url?: string
|
||||||
|
settings: {
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
}
|
}
|
||||||
capabilities: {
|
capabilities: {
|
||||||
tools: boolean
|
tools: boolean
|
||||||
input: Array<string>
|
input: Array<string>
|
||||||
output: Array<string>
|
output: Array<string>
|
||||||
}
|
}
|
||||||
options: {
|
request: {
|
||||||
headers: {
|
headers: {
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
}
|
}
|
||||||
body: {
|
body: {
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
aisdk: {
|
|
||||||
provider: {
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
request: {
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
variant?: string
|
variant?: string
|
||||||
}
|
}
|
||||||
variants: Array<{
|
variants: Array<{
|
||||||
|
|
@ -3807,14 +3736,6 @@ export type ModelV2Info1 = {
|
||||||
body: {
|
body: {
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
aisdk: {
|
|
||||||
provider: {
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
request: {
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}>
|
}>
|
||||||
time: {
|
time: {
|
||||||
released: number | "NaN" | "Infinity" | "-Infinity"
|
released: number | "NaN" | "Infinity" | "-Infinity"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue