refactor(schema): flatten provider identity
This commit is contained in:
parent
315ba3720e
commit
1a9bd8c1fd
16 changed files with 262 additions and 436 deletions
|
|
@ -7,7 +7,6 @@ import { EventV2 } from "./event"
|
|||
import { Policy } from "./policy"
|
||||
import { State } from "./state"
|
||||
import { Integration } from "./integration"
|
||||
import { ProviderOverlay } from "./provider-overlay"
|
||||
|
||||
export type ProviderRecord = {
|
||||
provider: ProviderV2.MutableInfo
|
||||
|
|
@ -78,32 +77,16 @@ export const layer = Layer.effect(
|
|||
|
||||
const projectModel = (model: ModelV2.Info, provider: ProviderV2.Info) => {
|
||||
const api =
|
||||
model.api.type === "native" && !model.api.package
|
||||
? {
|
||||
...provider.api,
|
||||
id: model.api.id,
|
||||
url: model.api.url ?? provider.api.url,
|
||||
settings: ProviderOverlay.mergeRecords(provider.api.settings, model.api.settings),
|
||||
}
|
||||
: model.api.type === "native" && provider.api.type === "native" && !model.api.url
|
||||
? {
|
||||
...model.api,
|
||||
package: model.api.package ?? provider.api.package,
|
||||
url: provider.api.url,
|
||||
settings: ProviderOverlay.mergeRecords(provider.api.settings, model.api.settings),
|
||||
}
|
||||
model.api.type === "native" && !model.api.url && Object.keys(model.api.settings).length === 0
|
||||
? { ...provider.api, id: model.api.id }
|
||||
: model.api.type === "aisdk" && provider.api.type === "aisdk" && !model.api.url
|
||||
? {
|
||||
...model.api,
|
||||
url: provider.api.url,
|
||||
settings: ProviderOverlay.mergeRecords(provider.api.settings, model.api.settings),
|
||||
}
|
||||
? { ...model.api, url: provider.api.url, settings: { ...provider.api.settings, ...model.api.settings } }
|
||||
: model.api.type === "aisdk" && provider.api.type === "aisdk"
|
||||
? { ...model.api, settings: ProviderOverlay.mergeRecords(provider.api.settings, model.api.settings) }
|
||||
? { ...model.api, settings: { ...provider.api.settings, ...model.api.settings } }
|
||||
: model.api
|
||||
const request = {
|
||||
headers: ProviderOverlay.mergeHeaders(provider.request.headers, model.request.headers),
|
||||
body: ProviderOverlay.mergeRecords(provider.request.body, model.request.body),
|
||||
headers: { ...provider.request.headers, ...model.request.headers },
|
||||
body: { ...provider.request.body, ...model.request.body },
|
||||
variant: model.request.variant,
|
||||
}
|
||||
return ModelV2.Info.make({
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { Effect } from "effect"
|
|||
import { Config } from "../../config"
|
||||
import { ModelV2 } from "../../model"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { ProviderOverlay } from "../../provider-overlay"
|
||||
|
||||
export const Plugin = define({
|
||||
id: "config-provider",
|
||||
|
|
@ -48,90 +47,22 @@ export const Plugin = define({
|
|||
const model = ModelV2.parse(configuredDefault)
|
||||
catalog.model.default.set(model.providerID, model.modelID)
|
||||
}
|
||||
const providerAiSDK = new Map<string, boolean>()
|
||||
const modelAiSDK = new Map<string, boolean>()
|
||||
for (const file of files) {
|
||||
for (const [id, item] of Object.entries(file.info.providers ?? {})) {
|
||||
const providerID = id
|
||||
if (item.aiSDK !== undefined) providerAiSDK.set(providerID, item.aiSDK)
|
||||
catalog.provider.update(providerID, (provider) => {
|
||||
if (item.name !== undefined) provider.name = item.name
|
||||
if (item.package !== undefined) {
|
||||
const settings = ProviderOverlay.mergeRecords(provider.api.settings, item.settings)
|
||||
const url =
|
||||
item.settings && Object.hasOwn(item.settings, "baseURL")
|
||||
? typeof settings.baseURL === "string"
|
||||
? settings.baseURL
|
||||
: undefined
|
||||
: provider.api.url
|
||||
provider.api = (providerAiSDK.get(providerID) ?? provider.api.type === "aisdk")
|
||||
? { type: "aisdk", package: item.package, ...(url === undefined ? {} : { url }), settings }
|
||||
: { type: "native", package: item.package, ...(url === undefined ? {} : { url }), settings }
|
||||
} else if (item.settings !== undefined) {
|
||||
provider.api.settings = ProviderOverlay.mergeRecords(provider.api.settings, item.settings)
|
||||
if (Object.hasOwn(item.settings, "baseURL")) {
|
||||
provider.api.url =
|
||||
typeof provider.api.settings.baseURL === "string" ? provider.api.settings.baseURL : undefined
|
||||
}
|
||||
if (item.api !== undefined) provider.api = { ...item.api }
|
||||
if (item.request !== undefined) {
|
||||
Object.assign(provider.request.headers, item.request.headers)
|
||||
Object.assign(provider.request.body, item.request.body)
|
||||
}
|
||||
if (item.package === undefined && item.aiSDK !== undefined) {
|
||||
if (item.aiSDK && provider.api.type === "native" && provider.api.package !== undefined) {
|
||||
provider.api = { ...provider.api, type: "aisdk", package: provider.api.package }
|
||||
}
|
||||
if (!item.aiSDK && provider.api.type === "aisdk") {
|
||||
provider.api = { ...provider.api, type: "native", settings: provider.api.settings ?? {} }
|
||||
}
|
||||
}
|
||||
ProviderOverlay.assign(provider.request, { headers: item.headers, body: item.body })
|
||||
})
|
||||
const providerApi = catalog.provider.get(providerID)?.provider.api
|
||||
for (const [id, config] of Object.entries(item.models ?? {})) {
|
||||
const modelKey = `${providerID}/${id}`
|
||||
if (config.aiSDK !== undefined) modelAiSDK.set(modelKey, config.aiSDK)
|
||||
catalog.model.update(providerID, id, (model) => {
|
||||
if (config.family !== undefined) model.family = config.family
|
||||
if (config.name !== undefined) model.name = config.name
|
||||
if (config.id !== undefined) model.api.id = config.id
|
||||
if (config.package !== undefined) {
|
||||
const aiSDK =
|
||||
modelAiSDK.get(modelKey) ?? providerAiSDK.get(providerID) ?? providerApi?.type === "aisdk"
|
||||
const settings = ProviderOverlay.mergeRecords(model.api.settings, config.settings)
|
||||
const url =
|
||||
config.settings && Object.hasOwn(config.settings, "baseURL")
|
||||
? typeof settings.baseURL === "string"
|
||||
? settings.baseURL
|
||||
: undefined
|
||||
: model.api.url
|
||||
model.api = aiSDK
|
||||
? {
|
||||
id: model.api.id,
|
||||
type: "aisdk",
|
||||
package: config.package,
|
||||
...(url === undefined ? {} : { url }),
|
||||
settings,
|
||||
}
|
||||
: {
|
||||
id: model.api.id,
|
||||
type: "native",
|
||||
package: config.package,
|
||||
...(url === undefined ? {} : { url }),
|
||||
settings,
|
||||
}
|
||||
} else if (config.settings !== undefined) {
|
||||
model.api.settings = ProviderOverlay.mergeRecords(model.api.settings, config.settings)
|
||||
if (Object.hasOwn(config.settings, "baseURL")) {
|
||||
model.api.url =
|
||||
typeof model.api.settings.baseURL === "string" ? model.api.settings.baseURL : undefined
|
||||
}
|
||||
}
|
||||
if (config.package === undefined && config.aiSDK !== undefined) {
|
||||
if (config.aiSDK && model.api.type === "native" && model.api.package !== undefined) {
|
||||
model.api = { ...model.api, type: "aisdk", package: model.api.package }
|
||||
}
|
||||
if (!config.aiSDK && model.api.type === "aisdk") {
|
||||
model.api = { ...model.api, type: "native", settings: model.api.settings ?? {} }
|
||||
}
|
||||
}
|
||||
if (config.api !== undefined) model.api = { ...model.api, ...config.api }
|
||||
if (config.capabilities !== undefined) {
|
||||
model.capabilities = {
|
||||
tools: config.capabilities.tools,
|
||||
|
|
@ -139,24 +70,24 @@ export const Plugin = define({
|
|||
output: [...config.capabilities.output],
|
||||
}
|
||||
}
|
||||
if (config.headers !== undefined || config.body !== undefined) {
|
||||
ProviderOverlay.assign(model.request, { headers: config.headers, body: config.body })
|
||||
if (config.request !== undefined) {
|
||||
Object.assign(model.request.headers, config.request.headers)
|
||||
Object.assign(model.request.body, config.request.body)
|
||||
if (config.request.variant !== undefined) model.request.variant = config.request.variant
|
||||
}
|
||||
if (config.variant !== undefined) model.request.variant = config.variant
|
||||
if (config.variants !== undefined) {
|
||||
for (const variant of config.variants) {
|
||||
let existing = model.variants.find((item) => item.id === variant.id)
|
||||
if (!existing) {
|
||||
existing = {
|
||||
id: variant.id,
|
||||
settings: {},
|
||||
headers: {},
|
||||
body: {},
|
||||
}
|
||||
model.variants.push(existing)
|
||||
}
|
||||
existing.settings = ProviderOverlay.mergeRecords(existing.settings, variant.settings)
|
||||
ProviderOverlay.assign(existing, { headers: variant.headers, body: variant.body })
|
||||
Object.assign(existing.headers, variant.headers)
|
||||
Object.assign(existing.body, variant.body)
|
||||
}
|
||||
}
|
||||
if (config.cost !== undefined) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
export * as ConfigProvider from "./provider"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { ProviderV2 } from "../provider"
|
||||
import { ModelV2 } from "../model"
|
||||
|
||||
export class Request extends Schema.Class<Request>("ConfigV2.Provider.Request")({
|
||||
|
|
@ -8,11 +9,6 @@ export class Request extends Schema.Class<Request>("ConfigV2.Provider.Request")(
|
|||
body: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||
}) {}
|
||||
|
||||
const Overlays = {
|
||||
settings: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||
...Request.fields,
|
||||
}
|
||||
|
||||
class Cache extends Schema.Class<Cache>("ConfigV2.Model.Cost.Cache")({
|
||||
read: Schema.Finite.pipe(Schema.optional),
|
||||
write: Schema.Finite.pipe(Schema.optional),
|
||||
|
|
@ -34,18 +30,32 @@ class Limit extends Schema.Class<Limit>("ConfigV2.Model.Limit")({
|
|||
output: Schema.Int.pipe(Schema.optional),
|
||||
}) {}
|
||||
|
||||
const ModelApi = Schema.Union([
|
||||
Schema.Struct({
|
||||
id: ModelV2.ID.pipe(Schema.optional),
|
||||
...ProviderV2.AISDK.fields,
|
||||
}),
|
||||
Schema.Struct({
|
||||
id: ModelV2.ID.pipe(Schema.optional),
|
||||
...ProviderV2.Native.fields,
|
||||
}),
|
||||
Schema.Struct({
|
||||
id: ModelV2.ID,
|
||||
}),
|
||||
])
|
||||
|
||||
class Model extends Schema.Class<Model>("ConfigV2.Model")({
|
||||
id: ModelV2.ID.pipe(Schema.optional),
|
||||
package: Schema.String.pipe(Schema.optional),
|
||||
aiSDK: Schema.Boolean.pipe(Schema.optional),
|
||||
...Overlays,
|
||||
family: ModelV2.Family.pipe(Schema.optional),
|
||||
name: Schema.String.pipe(Schema.optional),
|
||||
api: ModelApi.pipe(Schema.optional),
|
||||
capabilities: ModelV2.Capabilities.pipe(Schema.optional),
|
||||
variant: Schema.String.pipe(Schema.optional),
|
||||
request: Schema.Struct({
|
||||
...Request.fields,
|
||||
variant: Schema.String.pipe(Schema.optional),
|
||||
}).pipe(Schema.optional),
|
||||
variants: Schema.Struct({
|
||||
id: ModelV2.VariantID,
|
||||
...Overlays,
|
||||
...Request.fields,
|
||||
}).pipe(Schema.Array, Schema.optional),
|
||||
cost: Schema.Union([Cost, Cost.pipe(Schema.Array)]).pipe(Schema.optional),
|
||||
disabled: Schema.Boolean.pipe(Schema.optional),
|
||||
|
|
@ -55,8 +65,7 @@ class Model extends Schema.Class<Model>("ConfigV2.Model")({
|
|||
export class Info extends Schema.Class<Info>("ConfigV2.Provider")({
|
||||
name: Schema.String.pipe(Schema.optional),
|
||||
env: Schema.String.pipe(Schema.Array, Schema.optional),
|
||||
package: Schema.String.pipe(Schema.optional),
|
||||
aiSDK: Schema.Boolean.pipe(Schema.optional),
|
||||
...Overlays,
|
||||
api: ProviderV2.Api.pipe(Schema.optional),
|
||||
request: Request.pipe(Schema.optional),
|
||||
models: Schema.Record(Schema.String, Model).pipe(Schema.optional),
|
||||
}) {}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { Types } from "effect"
|
||||
import { Schema } from "effect"
|
||||
import { Model } from "@opencode-ai/schema/model"
|
||||
import { ProviderV2 } from "./provider"
|
||||
import { withStatics } from "./schema"
|
||||
import type { DeepMutable } from "./schema"
|
||||
|
||||
export const ID = Model.ID
|
||||
export type ID = typeof ID.Type
|
||||
|
|
@ -8,10 +10,6 @@ export type ID = typeof ID.Type
|
|||
export const VariantID = Model.VariantID
|
||||
export type VariantID = typeof VariantID.Type
|
||||
|
||||
export const Package = Model.Package
|
||||
export type Package = Model.Package
|
||||
|
||||
// Grouping of models, eg claude opus, claude sonnet
|
||||
export const Family = Model.Family
|
||||
export type Family = Model.Family
|
||||
|
||||
|
|
@ -23,15 +21,62 @@ export const Cost = Model.Cost
|
|||
export const Ref = Model.Ref
|
||||
export type Ref = typeof Ref.Type
|
||||
|
||||
export const Api = Model.Api
|
||||
export type Api = Model.Api
|
||||
// Temporary runtime schema until core catalog consumers migrate to the flat
|
||||
// package identity in @opencode-ai/schema.
|
||||
export const Api = Schema.Union([
|
||||
Schema.Struct({ id: ID, ...ProviderV2.AISDK.fields }),
|
||||
Schema.Struct({ id: ID, ...ProviderV2.Native.fields }),
|
||||
]).pipe(Schema.toTaggedUnion("type"))
|
||||
export type Api = typeof Api.Type
|
||||
|
||||
export const Info = Model.Info
|
||||
export type Info = Model.Info
|
||||
export interface Info extends Schema.Schema.Type<typeof Info> {}
|
||||
export const Info = Schema.Struct({
|
||||
id: ID,
|
||||
providerID: ProviderV2.ID,
|
||||
family: Family.pipe(Schema.optional),
|
||||
name: Schema.String,
|
||||
api: Api,
|
||||
capabilities: Capabilities,
|
||||
request: Schema.Struct({
|
||||
...ProviderV2.Request.fields,
|
||||
variant: Schema.String.pipe(Schema.optional),
|
||||
}),
|
||||
variants: Schema.Struct({
|
||||
id: VariantID,
|
||||
...ProviderV2.Request.fields,
|
||||
}).pipe(Schema.Array, Schema.mutable),
|
||||
time: Schema.Struct({ released: Schema.Finite }),
|
||||
cost: Cost.pipe(Schema.Array, Schema.mutable),
|
||||
status: Schema.Literals(["alpha", "beta", "deprecated", "active"]),
|
||||
enabled: Schema.Boolean,
|
||||
limit: Schema.Struct({
|
||||
context: Schema.Int,
|
||||
input: Schema.Int.pipe(Schema.optional),
|
||||
output: Schema.Int,
|
||||
}),
|
||||
})
|
||||
.annotate({ identifier: "ModelV2.Info" })
|
||||
.pipe(
|
||||
withStatics((schema) => ({
|
||||
empty: (providerID: ProviderV2.ID, modelID: ID) =>
|
||||
schema.make({
|
||||
id: modelID,
|
||||
providerID,
|
||||
name: modelID,
|
||||
api: { id: modelID, type: "native", settings: {} },
|
||||
capabilities: { tools: false, input: [], output: [] },
|
||||
request: { headers: {}, body: {} },
|
||||
variants: [],
|
||||
time: { released: 0 },
|
||||
cost: [],
|
||||
status: "active",
|
||||
enabled: true,
|
||||
limit: { context: 0, output: 0 },
|
||||
}),
|
||||
})),
|
||||
)
|
||||
|
||||
export type MutableInfo = Omit<Types.DeepMutable<Info>, "api"> & {
|
||||
api: ProviderV2.MutableApi<Api>
|
||||
}
|
||||
export type MutableInfo = Omit<DeepMutable<Info>, "api"> & { api: ProviderV2.MutableApi<Api> }
|
||||
|
||||
export function parse(input: string): { providerID: ProviderV2.ID; modelID: ID } {
|
||||
const [providerID, ...modelID] = input.split("/")
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
export * as ProviderOverlay from "./provider-overlay"
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
|
||||
export const mergeRecords = (...items: ReadonlyArray<Readonly<Record<string, unknown>> | undefined>) => {
|
||||
const result: Record<string, unknown> = {}
|
||||
for (const item of items) {
|
||||
for (const [key, value] of Object.entries(item ?? {})) {
|
||||
result[key] = isRecord(result[key]) && isRecord(value) ? mergeRecords(result[key], value) : value
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export const mergeHeaders = (...items: ReadonlyArray<Readonly<Record<string, string>> | undefined>) => {
|
||||
const result = new Map<string, readonly [string, string]>()
|
||||
for (const item of items) {
|
||||
for (const entry of Object.entries(item ?? {})) result.set(entry[0].toLowerCase(), entry)
|
||||
}
|
||||
return Object.fromEntries(result.values())
|
||||
}
|
||||
|
||||
export const assign = (
|
||||
target: { headers: Record<string, string>; body: Record<string, unknown> },
|
||||
overlay: { readonly headers?: Readonly<Record<string, string>>; readonly body?: Readonly<Record<string, unknown>> },
|
||||
) => {
|
||||
const headers = mergeHeaders(target.headers, overlay.headers)
|
||||
Object.keys(target.headers).forEach((key) => delete target.headers[key])
|
||||
Object.assign(target.headers, headers)
|
||||
const body = mergeRecords(target.body, overlay.body)
|
||||
Object.keys(target.body).forEach((key) => delete target.body[key])
|
||||
Object.assign(target.body, body)
|
||||
}
|
||||
|
|
@ -1,30 +1,61 @@
|
|||
export * as ProviderV2 from "./provider"
|
||||
|
||||
import { Types } from "effect"
|
||||
import { Schema } from "effect"
|
||||
import { Provider } from "@opencode-ai/schema/provider"
|
||||
import { Integration } from "./integration"
|
||||
import { withStatics } from "./schema"
|
||||
import type { DeepMutable } from "./schema"
|
||||
|
||||
export const ID = Provider.ID
|
||||
export type ID = typeof ID.Type
|
||||
|
||||
export const Overlays = Provider.Overlays
|
||||
// Temporary runtime schema until core catalog consumers migrate to the flat
|
||||
// package identity in @opencode-ai/schema.
|
||||
export interface AISDK extends Schema.Schema.Type<typeof AISDK> {}
|
||||
export const AISDK = Schema.Struct({
|
||||
type: Schema.Literal("aisdk"),
|
||||
package: Schema.String,
|
||||
url: Schema.String.pipe(Schema.optional),
|
||||
settings: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||
})
|
||||
|
||||
export const Package = Provider.Package
|
||||
export type Package = Provider.Package
|
||||
export interface Native extends Schema.Schema.Type<typeof Native> {}
|
||||
export const Native = Schema.Struct({
|
||||
type: Schema.Literal("native"),
|
||||
url: Schema.String.pipe(Schema.optional),
|
||||
settings: Schema.Record(Schema.String, Schema.Unknown),
|
||||
})
|
||||
|
||||
export const AISDK = Provider.AISDK
|
||||
|
||||
export const Native = Provider.Native
|
||||
|
||||
export const Api = Provider.Api
|
||||
export type Api = Provider.Api
|
||||
export type MutableApi<T extends Api = Api> = T extends Api
|
||||
? Omit<Types.DeepMutable<T>, "settings"> & (undefined extends T["settings"] ? { settings?: any } : { settings: any })
|
||||
: never
|
||||
export const Api = Schema.Union([AISDK, Native]).pipe(Schema.toTaggedUnion("type"))
|
||||
export type Api = typeof Api.Type
|
||||
|
||||
export const Request = Provider.Request
|
||||
export type Request = Provider.Request
|
||||
|
||||
export const Info = Provider.Info
|
||||
export type Info = Provider.Info
|
||||
export interface Info extends Schema.Schema.Type<typeof Info> {}
|
||||
export const Info = Schema.Struct({
|
||||
id: ID,
|
||||
integrationID: Integration.ID.pipe(Schema.optional),
|
||||
name: Schema.String,
|
||||
disabled: Schema.Boolean.pipe(Schema.optional),
|
||||
api: Api,
|
||||
request: Request,
|
||||
})
|
||||
.annotate({ identifier: "ProviderV2.Info" })
|
||||
.pipe(
|
||||
withStatics((schema) => ({
|
||||
empty: (id: ID) =>
|
||||
schema.make({
|
||||
id,
|
||||
name: id,
|
||||
api: { type: "native", settings: {} },
|
||||
request: { headers: {}, body: {} },
|
||||
}),
|
||||
})),
|
||||
)
|
||||
|
||||
export type MutableInfo = Omit<Types.DeepMutable<Info>, "api"> & { api: MutableApi }
|
||||
export type MutableApi<T extends Api = Api> = T extends Api
|
||||
? Omit<DeepMutable<T>, "settings"> & (undefined extends T["settings"] ? { settings?: any } : { settings: any })
|
||||
: never
|
||||
|
||||
export type MutableInfo = Omit<DeepMutable<Info>, "api"> & { api: MutableApi }
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import { Credential } from "../../credential"
|
|||
import { Integration } from "../../integration"
|
||||
import { ModelV2 } from "../../model"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { ProviderOverlay } from "../../provider-overlay"
|
||||
import { SessionSchema } from "../schema"
|
||||
|
||||
export class ModelNotSelectedError extends Schema.TaggedErrorClass<ModelNotSelectedError>()(
|
||||
|
|
@ -118,13 +117,8 @@ const withVariant = (
|
|||
return Effect.succeed(
|
||||
variant
|
||||
? produce(model, (draft) => {
|
||||
if (variant.settings !== undefined) {
|
||||
draft.api.settings = ProviderOverlay.mergeRecords(draft.api.settings, variant.settings)
|
||||
if (Object.hasOwn(variant.settings, "baseURL")) {
|
||||
draft.api.url = typeof draft.api.settings.baseURL === "string" ? draft.api.settings.baseURL : undefined
|
||||
}
|
||||
}
|
||||
ProviderOverlay.assign(draft.request, variant)
|
||||
Object.assign(draft.request.headers, variant.headers)
|
||||
Object.assign(draft.request.body, variant.body)
|
||||
})
|
||||
: model,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -173,14 +173,15 @@ function migrateProvider(info: ConfigProviderV1.Info) {
|
|||
return {
|
||||
name: info.name,
|
||||
env: info.env,
|
||||
package: info.npm,
|
||||
aiSDK: info.npm ? true : undefined,
|
||||
settings: {
|
||||
...options.settings,
|
||||
...(info.api ?? options.url ? { baseURL: info.api ?? options.url } : {}),
|
||||
},
|
||||
headers: options.headers,
|
||||
body: options.body,
|
||||
api: info.npm
|
||||
? {
|
||||
type: "aisdk" as const,
|
||||
package: info.npm,
|
||||
url: info.api ?? options.url,
|
||||
settings: options.settings ?? {},
|
||||
}
|
||||
: undefined,
|
||||
request: info.options && { headers: options.headers, body: options.body },
|
||||
models:
|
||||
info.models &&
|
||||
Object.fromEntries(Object.entries(info.models).map(([name, model]) => [name, migrateModel(model, info.npm)])),
|
||||
|
|
@ -215,13 +216,22 @@ function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: st
|
|||
return {
|
||||
family: info.family,
|
||||
name: info.name,
|
||||
id: info.id,
|
||||
package: info.provider?.npm,
|
||||
aiSDK: info.provider?.npm ? true : undefined,
|
||||
settings: info.provider?.api ? { baseURL: info.provider.api } : undefined,
|
||||
api: info.provider?.npm
|
||||
? {
|
||||
...(info.id === undefined ? {} : { id: info.id }),
|
||||
type: "aisdk" as const,
|
||||
package: info.provider.npm,
|
||||
url: info.provider.api,
|
||||
settings: {},
|
||||
}
|
||||
: info.id === undefined
|
||||
? undefined
|
||||
: { id: info.id },
|
||||
capabilities,
|
||||
headers: info.headers,
|
||||
body: request,
|
||||
request: (info.headers || request) && {
|
||||
headers: info.headers,
|
||||
body: request,
|
||||
},
|
||||
variants:
|
||||
info.variants &&
|
||||
Object.entries(info.variants).map(([id, options]) => ({
|
||||
|
|
|
|||
|
|
@ -213,7 +213,6 @@ describe("CatalogV2", () => {
|
|||
type: "aisdk",
|
||||
package: "@ai-sdk/openai-compatible",
|
||||
url: "https://provider.example.com",
|
||||
settings: {},
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -103,10 +103,13 @@ describe("Config", () => {
|
|||
},
|
||||
})
|
||||
|
||||
expect(migrated.providers?.bedrock).toMatchObject({
|
||||
expect(migrated.providers?.bedrock?.api).toEqual({
|
||||
type: "aisdk",
|
||||
package: "@ai-sdk/amazon-bedrock",
|
||||
aiSDK: true,
|
||||
url: undefined,
|
||||
settings: { region: "us-east-1", profile: "dev" },
|
||||
})
|
||||
expect(migrated.providers?.bedrock?.request).toEqual({
|
||||
headers: { "x-test": "1" },
|
||||
body: { trace: true },
|
||||
})
|
||||
|
|
@ -582,34 +585,34 @@ describe("Config", () => {
|
|||
})
|
||||
expect(documents[0]?.info.attachments).toEqual({ image: { auto_resize: false, max_width: 1200 } })
|
||||
expect(documents[0]?.info.providers?.custom).toMatchObject({
|
||||
body: { apiKey: "secret" },
|
||||
request: { body: { apiKey: "secret" } },
|
||||
models: {
|
||||
model: {
|
||||
body: { reasoningEffort: "high" },
|
||||
request: { body: { reasoningEffort: "high" } },
|
||||
variants: [{ id: "fast", body: { temperature: 0.2 } }],
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(documents[0]?.info.providers?.openai).toMatchObject({
|
||||
package: "@ai-sdk/openai",
|
||||
aiSDK: true,
|
||||
settings: {},
|
||||
headers: { Authorization: "Bearer secret", "OpenAI-Organization": "org" },
|
||||
api: { settings: {} },
|
||||
request: { headers: { Authorization: "Bearer secret", "OpenAI-Organization": "org" } },
|
||||
models: {
|
||||
model: {
|
||||
body: { temperature: 0.3, reasoning_effort: "high", service_tier: "priority" },
|
||||
request: {
|
||||
body: { temperature: 0.3, reasoning_effort: "high", service_tier: "priority" },
|
||||
},
|
||||
variants: [{ id: "high", body: { reasoning_effort: "high", reasoning_summary: "auto" } }],
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(documents[0]?.info.providers?.anthropic).toMatchObject({
|
||||
package: "@ai-sdk/anthropic",
|
||||
aiSDK: true,
|
||||
models: {
|
||||
model: {
|
||||
body: {
|
||||
output_config: { effort: "high", task_budget: 4096 },
|
||||
metadata: { user_id: "user-1" },
|
||||
request: {
|
||||
body: {
|
||||
output_config: { effort: "high", task_budget: 4096 },
|
||||
metadata: { user_id: "user-1" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -45,90 +45,16 @@ function withEnv<A, E, R>(vars: Record<string, string | undefined>, effect: () =
|
|||
)
|
||||
}
|
||||
|
||||
function request(headers: Record<string, string>, variant?: string) {
|
||||
return {
|
||||
headers,
|
||||
variant,
|
||||
}
|
||||
}
|
||||
|
||||
const decode = Schema.decodeUnknownSync(Config.Info)
|
||||
|
||||
describe("ConfigProviderPlugin.Plugin", () => {
|
||||
it.effect("merges flat provider and model overlays", () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
const providerID = ProviderV2.ID.make("custom")
|
||||
const modelID = ModelV2.ID.make("chat")
|
||||
const config = Config.Service.of({
|
||||
entries: () =>
|
||||
Effect.succeed([
|
||||
new Config.Document({
|
||||
type: "document",
|
||||
info: decode({ providers: { custom: { aiSDK: true } } }),
|
||||
}),
|
||||
new Config.Document({
|
||||
type: "document",
|
||||
info: decode({
|
||||
providers: {
|
||||
custom: {
|
||||
package: "custom-provider",
|
||||
settings: { auth: { type: "token", region: "us-east-1" } },
|
||||
headers: { "X-Test": "provider" },
|
||||
body: { reasoning: { type: "enabled", budget: 8_000 }, tags: ["provider"] },
|
||||
models: {
|
||||
chat: {
|
||||
package: "custom-model-provider",
|
||||
settings: { auth: { region: "us-west-2" } },
|
||||
headers: { "x-test": "model" },
|
||||
body: { reasoning: { budget: 32_000 }, tags: ["model"] },
|
||||
},
|
||||
inherit: {
|
||||
settings: { auth: { region: "eu-west-1" }, baseURL: "https://model.example/v1" },
|
||||
},
|
||||
clear: { settings: { baseURL: "https://old.example/v1" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
new Config.Document({
|
||||
type: "document",
|
||||
info: decode({
|
||||
providers: {
|
||||
custom: { models: { clear: { package: "custom-provider", settings: { baseURL: null } } } },
|
||||
},
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
})
|
||||
|
||||
yield* addPlugin(config)
|
||||
|
||||
const model = required(yield* catalog.model.get(providerID, modelID))
|
||||
expect(model.api).toEqual({
|
||||
id: modelID,
|
||||
type: "aisdk",
|
||||
package: "custom-model-provider",
|
||||
settings: { auth: { type: "token", region: "us-west-2" } },
|
||||
})
|
||||
expect(model.request.headers).toEqual({ "x-test": "model" })
|
||||
expect(model.request.body).toEqual({
|
||||
reasoning: { type: "enabled", budget: 32_000 },
|
||||
tags: ["model"],
|
||||
})
|
||||
expect(required(yield* catalog.model.get(providerID, ModelV2.ID.make("inherit"))).api).toEqual({
|
||||
id: ModelV2.ID.make("inherit"),
|
||||
type: "aisdk",
|
||||
package: "custom-provider",
|
||||
url: "https://model.example/v1",
|
||||
settings: {
|
||||
auth: { type: "token", region: "eu-west-1" },
|
||||
baseURL: "https://model.example/v1",
|
||||
},
|
||||
})
|
||||
expect(required(yield* catalog.model.get(providerID, ModelV2.ID.make("clear"))).api).toEqual({
|
||||
id: ModelV2.ID.make("clear"),
|
||||
type: "aisdk",
|
||||
package: "custom-provider",
|
||||
settings: { auth: { type: "token", region: "us-east-1" }, baseURL: null },
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("keeps configured model variant bodies unchanged", () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
|
|
@ -142,9 +68,7 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||
info: decode({
|
||||
providers: {
|
||||
opencode: {
|
||||
package: "@ai-sdk/openai",
|
||||
aiSDK: true,
|
||||
settings: { baseURL: "https://opencode.test/v1" },
|
||||
api: { type: "aisdk", package: "@ai-sdk/openai", url: "https://opencode.test/v1" },
|
||||
models: {
|
||||
"alpha-gpt-next": {
|
||||
variants: [
|
||||
|
|
@ -195,9 +119,7 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||
info: decode({
|
||||
providers: {
|
||||
opencode: {
|
||||
package: "@ai-sdk/openai",
|
||||
aiSDK: true,
|
||||
settings: { baseURL: "https://opencode.test/v1" },
|
||||
api: { type: "aisdk", package: "@ai-sdk/openai", url: "https://opencode.test/v1" },
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
|
@ -247,8 +169,8 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||
custom: {
|
||||
name: "Configured",
|
||||
env: ["CUSTOM_API_KEY"],
|
||||
package: "custom-native",
|
||||
headers: { first: "first", shared: "first" },
|
||||
api: { type: "native", settings: {} },
|
||||
request: request({ first: "first", shared: "first" }),
|
||||
models: {
|
||||
chat: {
|
||||
name: "First",
|
||||
|
|
@ -256,8 +178,7 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||
disabled: true,
|
||||
limit: { context: 100, output: 50 },
|
||||
cost: { input: 1, output: 2 },
|
||||
headers: { first: "first", shared: "first" },
|
||||
variant: "retained",
|
||||
request: request({ first: "first", shared: "first" }, "retained"),
|
||||
variants: [
|
||||
{
|
||||
id: "fast",
|
||||
|
|
@ -276,19 +197,17 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||
model: "custom/default",
|
||||
providers: {
|
||||
custom: {
|
||||
package: "custom-sdk",
|
||||
aiSDK: true,
|
||||
settings: { baseURL: "https://example.test" },
|
||||
headers: { last: "last", shared: "last" },
|
||||
api: { type: "aisdk", package: "custom-sdk", url: "https://example.test" },
|
||||
request: request({ last: "last", shared: "last" }),
|
||||
models: {
|
||||
default: {
|
||||
name: "Default",
|
||||
},
|
||||
chat: {
|
||||
id: "api-chat",
|
||||
api: { id: "api-chat" },
|
||||
name: "Last",
|
||||
limit: { output: 75 },
|
||||
headers: { last: "last", shared: "last" },
|
||||
request: request({ last: "last", shared: "last" }),
|
||||
variants: [
|
||||
{
|
||||
id: "fast",
|
||||
|
|
@ -328,12 +247,7 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||
})
|
||||
expect((yield* integrations.get(Integration.ID.make("custom")))?.name).toBe("Renamed")
|
||||
expect(provider.disabled).toBeUndefined()
|
||||
expect(provider.api).toEqual({
|
||||
type: "aisdk",
|
||||
package: "custom-sdk",
|
||||
url: "https://example.test",
|
||||
settings: { baseURL: "https://example.test" },
|
||||
})
|
||||
expect(provider.api).toEqual({ type: "aisdk", package: "custom-sdk", url: "https://example.test" })
|
||||
expect(provider.request.headers).toEqual({ first: "first", shared: "last", last: "last" })
|
||||
expect(model.api.id).toBe(ModelV2.ID.make("api-chat"))
|
||||
expect(model.name).toBe("Last")
|
||||
|
|
|
|||
|
|
@ -141,35 +141,6 @@ describe("SessionRunnerModel", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("applies a selected variant base URL", () =>
|
||||
Effect.gen(function* () {
|
||||
const base = model(
|
||||
{ type: "aisdk", package: "@ai-sdk/openai", url: "https://default.example/v1" },
|
||||
[
|
||||
{
|
||||
id: ModelV2.VariantID.make("regional"),
|
||||
settings: { baseURL: "https://regional.example/v1" },
|
||||
headers: {},
|
||||
body: {},
|
||||
},
|
||||
],
|
||||
)
|
||||
const session = SessionV2.Info.make({
|
||||
id: SessionV2.ID.make("ses_regional_variant"),
|
||||
projectID: ProjectV2.ID.global,
|
||||
title: "test",
|
||||
model: { id: base.id, providerID: base.providerID, variant: ModelV2.VariantID.make("regional") },
|
||||
cost: 0,
|
||||
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||
time: { created: DateTime.makeUnsafe(0), updated: DateTime.makeUnsafe(0) },
|
||||
location: { directory: AbsolutePath.make("/project") },
|
||||
})
|
||||
const resolved = yield* SessionRunnerModel.resolve(session, base)
|
||||
|
||||
expect(resolved.route.endpoint.baseURL).toBe("https://regional.example/v1")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("overlays selected OpenAI-compatible Session variant bodies", () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = model(
|
||||
|
|
|
|||
|
|
@ -96,20 +96,12 @@ test("Core reuses the canonical shared schemas", async () => {
|
|||
[coreLLM.ToolContent, LLM.ToolContent],
|
||||
[ModelV2.ID, Model.ID],
|
||||
[ModelV2.VariantID, Model.VariantID],
|
||||
[ModelV2.Package, Model.Package],
|
||||
[ModelV2.Ref, Model.Ref],
|
||||
[ModelV2.Family, Model.Family],
|
||||
[ModelV2.Capabilities, Model.Capabilities],
|
||||
[ModelV2.Cost, Model.Cost],
|
||||
[ModelV2.Api, Model.Api],
|
||||
[ModelV2.Info, Model.Info],
|
||||
[ProviderV2.ID, Provider.ID],
|
||||
[ProviderV2.Package, Provider.Package],
|
||||
[ProviderV2.AISDK, Provider.AISDK],
|
||||
[ProviderV2.Native, Provider.Native],
|
||||
[ProviderV2.Api, Provider.Api],
|
||||
[ProviderV2.Request, Provider.Request],
|
||||
[ProviderV2.Info, Provider.Info],
|
||||
[corePermission.Effect, Permission.Effect],
|
||||
[corePermission.Rule, Permission.Rule],
|
||||
[corePermission.Ruleset, Permission.Ruleset],
|
||||
|
|
@ -157,15 +149,50 @@ test("Core reuses the canonical shared schemas", async () => {
|
|||
for (const [core, shared] of schemas) expect(core).toBe(shared)
|
||||
|
||||
expect(Agent.Info.empty(Agent.ID.make("test"))).toEqual(AgentV2.Info.empty(AgentV2.ID.make("test")))
|
||||
expect(Model.Info.empty(Provider.ID.make("test"), Model.ID.make("model"))).toEqual(
|
||||
ModelV2.Info.empty(ProviderV2.ID.make("test"), ModelV2.ID.make("model")),
|
||||
)
|
||||
expect(Provider.Info.empty(Provider.ID.make("test"))).toEqual(ProviderV2.Info.empty(ProviderV2.ID.make("test")))
|
||||
expect(Skill.Source.key(Skill.DirectorySource.make({ type: "directory", path: AbsolutePath.make("/tmp") }))).toBe(
|
||||
"directory:/tmp",
|
||||
)
|
||||
})
|
||||
|
||||
test("shared provider schemas use flat package identity", () => {
|
||||
expect(
|
||||
Schema.decodeUnknownSync(Provider.Info)({
|
||||
id: "openai",
|
||||
name: "OpenAI",
|
||||
package: "@opencode-ai/llm/providers/openai",
|
||||
settings: { baseURL: "https://api.openai.com/v1" },
|
||||
headers: { "x-application": "opencode" },
|
||||
body: { service_tier: "priority" },
|
||||
}),
|
||||
).toMatchObject({ package: "@opencode-ai/llm/providers/openai" })
|
||||
expect(
|
||||
Schema.decodeUnknownSync(Provider.Info)({
|
||||
id: "legacy",
|
||||
name: "Legacy",
|
||||
package: "aisdk:@ai-sdk/openai",
|
||||
}),
|
||||
).toMatchObject({ package: "aisdk:@ai-sdk/openai" })
|
||||
expect(
|
||||
Schema.decodeUnknownSync(Model.Info)({
|
||||
id: "claude-sonnet",
|
||||
modelID: "us.anthropic.claude-sonnet-4-6",
|
||||
providerID: "amazon-bedrock",
|
||||
name: "Claude Sonnet",
|
||||
package: "@opencode-ai/llm/providers/amazon-bedrock",
|
||||
settings: { region: "us-west-2" },
|
||||
headers: {},
|
||||
body: { additionalModelRequestFields: { reasoning_config: { type: "enabled" } } },
|
||||
capabilities: { tools: true, input: ["text"], output: ["text"] },
|
||||
variants: [{ id: "max", settings: {}, body: { budget_tokens: 31999 } }],
|
||||
time: { released: 0 },
|
||||
cost: [],
|
||||
status: "active",
|
||||
enabled: true,
|
||||
limit: { context: 200000, output: 64000 },
|
||||
}),
|
||||
).toMatchObject({ id: "claude-sonnet", modelID: "us.anthropic.claude-sonnet-4-6" })
|
||||
})
|
||||
|
||||
test("shared record schemas construct and decode plain objects", () => {
|
||||
const made = Prompt.make({ text: "hello" })
|
||||
const decoded = Schema.decodeUnknownSync(Prompt)({ text: "hello" })
|
||||
|
|
|
|||
|
|
@ -10,14 +10,6 @@ export type ID = typeof ID.Type
|
|||
export const VariantID = Schema.String.pipe(Schema.brand("VariantID"))
|
||||
export type VariantID = typeof VariantID.Type
|
||||
|
||||
export const Package = Schema.Struct({
|
||||
modelID: ID,
|
||||
package: Schema.String.pipe(Schema.optional),
|
||||
aiSDK: Schema.Boolean.pipe(Schema.optional),
|
||||
...Provider.Overlays,
|
||||
})
|
||||
export type Package = typeof Package.Type
|
||||
|
||||
export const Ref = Schema.Struct({
|
||||
id: ID,
|
||||
providerID: Provider.ID,
|
||||
|
|
@ -49,35 +41,23 @@ export const Cost = Schema.Struct({
|
|||
}),
|
||||
})
|
||||
|
||||
export const Api = Schema.Union([
|
||||
Schema.Struct({
|
||||
id: ID,
|
||||
...Provider.AISDK.fields,
|
||||
}),
|
||||
Schema.Struct({
|
||||
id: ID,
|
||||
...Provider.Native.fields,
|
||||
}),
|
||||
]).pipe(Schema.toTaggedUnion("type"))
|
||||
export type Api = typeof Api.Type
|
||||
export interface Variant extends Schema.Schema.Type<typeof Variant> {}
|
||||
export const Variant = Schema.Struct({
|
||||
id: VariantID,
|
||||
...Provider.Overlays,
|
||||
})
|
||||
|
||||
export interface Info extends Schema.Schema.Type<typeof Info> {}
|
||||
export const Info = Schema.Struct({
|
||||
id: ID,
|
||||
modelID: ID.pipe(Schema.optional),
|
||||
providerID: Provider.ID,
|
||||
family: Family.pipe(Schema.optional),
|
||||
name: Schema.String,
|
||||
api: Api,
|
||||
package: Provider.Package.pipe(Schema.optional),
|
||||
...Provider.Overlays,
|
||||
capabilities: Capabilities,
|
||||
request: Schema.Struct({
|
||||
...Provider.Request.fields,
|
||||
variant: Schema.String.pipe(Schema.optional),
|
||||
}),
|
||||
variants: Schema.Struct({
|
||||
id: VariantID,
|
||||
settings: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||
...Provider.Request.fields,
|
||||
}).pipe(Schema.Array, Schema.mutable),
|
||||
variants: Variant.pipe(Schema.Array, Schema.mutable, Schema.optional),
|
||||
time: Schema.Struct({
|
||||
released: Schema.Finite,
|
||||
}),
|
||||
|
|
@ -93,15 +73,12 @@ export const Info = Schema.Struct({
|
|||
.annotate({ identifier: "ModelV2.Info" })
|
||||
.pipe(
|
||||
withStatics((schema) => ({
|
||||
empty: (providerID: Provider.ID, modelID: ID) =>
|
||||
empty: (providerID: Provider.ID, id: ID) =>
|
||||
schema.make({
|
||||
id: modelID,
|
||||
id,
|
||||
providerID,
|
||||
name: modelID,
|
||||
api: { id: modelID, type: "native", settings: {} },
|
||||
name: id,
|
||||
capabilities: { tools: false, input: [], output: [] },
|
||||
request: { headers: {}, body: {} },
|
||||
variants: [],
|
||||
time: { released: 0 },
|
||||
cost: [],
|
||||
status: "active",
|
||||
|
|
|
|||
|
|
@ -22,38 +22,15 @@ export const ID = Schema.String.pipe(
|
|||
)
|
||||
export type ID = typeof ID.Type
|
||||
|
||||
export const Package = Schema.String
|
||||
export type Package = typeof Package.Type
|
||||
|
||||
export const Overlays = {
|
||||
settings: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||
headers: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
|
||||
body: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||
}
|
||||
|
||||
export const Package = Schema.Struct({
|
||||
package: Schema.String,
|
||||
aiSDK: Schema.Boolean.pipe(Schema.optional),
|
||||
...Overlays,
|
||||
})
|
||||
export type Package = typeof Package.Type
|
||||
|
||||
export interface AISDK extends Schema.Schema.Type<typeof AISDK> {}
|
||||
export const AISDK = Schema.Struct({
|
||||
type: Schema.Literal("aisdk"),
|
||||
package: Schema.String,
|
||||
url: Schema.String.pipe(Schema.optional),
|
||||
settings: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||
})
|
||||
|
||||
export interface Native extends Schema.Schema.Type<typeof Native> {}
|
||||
export const Native = Schema.Struct({
|
||||
type: Schema.Literal("native"),
|
||||
package: Schema.String.pipe(Schema.optional),
|
||||
url: Schema.String.pipe(Schema.optional),
|
||||
settings: Schema.Record(Schema.String, Schema.Unknown),
|
||||
})
|
||||
|
||||
export const Api = Schema.Union([AISDK, Native]).pipe(Schema.toTaggedUnion("type"))
|
||||
export type Api = typeof Api.Type
|
||||
|
||||
export interface Request extends Schema.Schema.Type<typeof Request> {}
|
||||
export const Request = Schema.Struct({
|
||||
headers: Schema.Record(Schema.String, Schema.String),
|
||||
|
|
@ -66,18 +43,12 @@ export const Info = Schema.Struct({
|
|||
integrationID: Integration.ID.pipe(Schema.optional),
|
||||
name: Schema.String,
|
||||
disabled: Schema.Boolean.pipe(Schema.optional),
|
||||
api: Api,
|
||||
request: Request,
|
||||
package: Package,
|
||||
...Overlays,
|
||||
})
|
||||
.annotate({ identifier: "ProviderV2.Info" })
|
||||
.pipe(
|
||||
withStatics((schema) => ({
|
||||
empty: (id: ID) =>
|
||||
schema.make({
|
||||
id,
|
||||
name: id,
|
||||
api: { type: "native", settings: {} },
|
||||
request: { headers: {}, body: {} },
|
||||
}),
|
||||
empty: (id: ID) => schema.make({ id, name: id, package: "" }),
|
||||
})),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4002,7 +4002,6 @@ export type ModelV2Info = {
|
|||
| {
|
||||
id: string
|
||||
type: "native"
|
||||
package?: string
|
||||
url?: string
|
||||
settings: {
|
||||
[key: string]: unknown
|
||||
|
|
@ -4024,9 +4023,6 @@ export type ModelV2Info = {
|
|||
}
|
||||
variants: Array<{
|
||||
id: string
|
||||
settings?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
headers: {
|
||||
[key: string]: string
|
||||
}
|
||||
|
|
@ -4074,7 +4070,6 @@ export type ProviderV2Info = {
|
|||
}
|
||||
| {
|
||||
type: "native"
|
||||
package?: string
|
||||
url?: string
|
||||
settings: {
|
||||
[key: string]: unknown
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue