From 98d48b60be4f40f0c670f5f1626661ce1afb7660 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Wed, 24 Jun 2026 12:51:59 -0400 Subject: [PATCH] refactor(core): flatten provider config --- packages/core/src/config/provider.ts | 39 ++-- packages/core/src/plugin/provider/opencode.ts | 4 +- packages/core/src/v1/config/migrate.ts | 38 ++-- .../core/src/v1/config/provider-options.ts | 201 ++---------------- packages/core/test/config/config.test.ts | 36 ++-- .../core/test/config/provider-options.test.ts | 197 ++--------------- packages/schema/src/model.ts | 32 +-- packages/schema/src/provider.ts | 28 +-- 8 files changed, 93 insertions(+), 482 deletions(-) diff --git a/packages/core/src/config/provider.ts b/packages/core/src/config/provider.ts index 1b54757078..2dc038962c 100644 --- a/packages/core/src/config/provider.ts +++ b/packages/core/src/config/provider.ts @@ -1,12 +1,17 @@ export * as ConfigProvider from "./provider" import { Schema } from "effect" -import { ProviderV2 } from "../provider" import { ModelV2 } from "../model" -export class Request extends Schema.Class("ConfigV2.Provider.Request")({ +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 class Request extends Schema.Class("ConfigV2.Provider.Request")({ + headers: Overlays.headers, + body: Overlays.body, }) {} class Cache extends Schema.Class("ConfigV2.Model.Cost.Cache")({ @@ -30,32 +35,17 @@ class Limit extends Schema.Class("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("ConfigV2.Model")({ + id: ModelV2.ID.pipe(Schema.optional), family: ModelV2.Family.pipe(Schema.optional), name: Schema.String.pipe(Schema.optional), - api: ModelApi.pipe(Schema.optional), + package: Schema.String.pipe(Schema.optional), + aisdk: Schema.Literal(true).pipe(Schema.optional), + ...Overlays, capabilities: ModelV2.Capabilities.pipe(Schema.optional), - request: Schema.Struct({ - ...Request.fields, - variant: Schema.String.pipe(Schema.optional), - }).pipe(Schema.optional), variants: Schema.Struct({ id: ModelV2.VariantID, - ...Request.fields, + ...Overlays, }).pipe(Schema.Array, Schema.optional), cost: Schema.Union([Cost, Cost.pipe(Schema.Array)]).pipe(Schema.optional), disabled: Schema.Boolean.pipe(Schema.optional), @@ -65,7 +55,8 @@ class Model extends Schema.Class("ConfigV2.Model")({ export class Info extends Schema.Class("ConfigV2.Provider")({ name: Schema.String.pipe(Schema.optional), env: Schema.String.pipe(Schema.Array, Schema.optional), - api: ProviderV2.Api.pipe(Schema.optional), - request: Request.pipe(Schema.optional), + package: Schema.String.pipe(Schema.optional), + aisdk: Schema.Literal(true).pipe(Schema.optional), + ...Overlays, models: Schema.Record(Schema.String, Model).pipe(Schema.optional), }) {} diff --git a/packages/core/src/plugin/provider/opencode.ts b/packages/core/src/plugin/provider/opencode.ts index 87a02d328a..8ef365e49c 100644 --- a/packages/core/src/plugin/provider/opencode.ts +++ b/packages/core/src/plugin/provider/opencode.ts @@ -144,12 +144,12 @@ export const OpencodePlugin = define ({ id: ModelV2.VariantID.make(id), headers: { ...(options.headers ?? {}) }, - body: lowerer.request(withoutCredentials(options)), + body: lowerer.model(withoutCredentials(options)), })) } if (config.release_date !== undefined) { diff --git a/packages/core/src/v1/config/migrate.ts b/packages/core/src/v1/config/migrate.ts index 1583c6c1d5..7914be66d5 100644 --- a/packages/core/src/v1/config/migrate.ts +++ b/packages/core/src/v1/config/migrate.ts @@ -173,15 +173,11 @@ function migrateProvider(info: ConfigProviderV1.Info) { return { name: info.name, env: info.env, - 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 }, + package: info.npm, + aisdk: info.npm ? (true as const) : undefined, + settings: info.api ? { ...options.settings, baseURL: info.api } : options.settings, + headers: info.options && options.headers, + body: info.options && options.body, models: info.models && Object.fromEntries(Object.entries(info.models).map(([name, model]) => [name, migrateModel(model, info.npm)])), @@ -191,7 +187,7 @@ function migrateProvider(info: ConfigProviderV1.Info) { function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: string) { const packageID = info.provider?.npm ?? packageName const lowerer = ConfigProviderOptionsV1.get(packageID) - const request = info.options && lowerer.request(info.options) + const settings = info.options && lowerer.model(info.options) const costs = info.cost && [ { input: info.cost.input, @@ -214,29 +210,19 @@ function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: st ? { tools: info.tool_call ?? false, input: info.modalities?.input ?? [], output: info.modalities?.output ?? [] } : undefined return { + id: info.id, family: info.family, name: info.name, - 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 }, + package: info.provider?.npm, + aisdk: info.provider?.npm ? (true as const) : undefined, + settings: info.provider?.api ? { ...settings, baseURL: info.provider.api } : settings, capabilities, - request: (info.headers || request) && { - headers: info.headers, - body: request, - }, + headers: info.headers, variants: info.variants && Object.entries(info.variants).map(([id, options]) => ({ id, - body: lowerer.request(options), + settings: lowerer.model(options), })), cost: costs, disabled: info.status === "deprecated" ? true : undefined, diff --git a/packages/core/src/v1/config/provider-options.ts b/packages/core/src/v1/config/provider-options.ts index a441a1a211..01f813197f 100644 --- a/packages/core/src/v1/config/provider-options.ts +++ b/packages/core/src/v1/config/provider-options.ts @@ -3,209 +3,32 @@ export * as ConfigProviderOptionsV1 from "./provider-options" type Options = Readonly> export interface ProviderResult { + readonly settings: Record readonly headers?: Record readonly body?: Record - readonly url?: string - readonly settings?: Record } export interface Lowerer { readonly provider: (options: Options) => ProviderResult - readonly request: (options: Options) => Record + readonly model: (options: Options) => Record } -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 = { +const lowerer: 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"]), + settings: Object.fromEntries(Object.entries(options).filter(([key]) => key !== "headers" && key !== "body")), + headers: record(options.headers, (value): value is string => typeof value === "string"), + body: record(options.body, () => true), } }, - request: snake, + model: (options) => ({ ...options }), } -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 - }, +export function get(_packageName?: string): Lowerer { + return lowerer } -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> = { - "@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 = []): 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) { - return Object.fromEntries(Object.entries(options).filter(([key]) => !keys.includes(key))) -} - -function pick(options: Options, keys: ReadonlyArray) { - 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) { - 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) { - 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 { - return typeof input === "object" && input !== null && !Array.isArray(input) +function record(input: unknown, guard: (value: unknown) => value is T) { + if (typeof input !== "object" || input === null || Array.isArray(input)) return undefined + return Object.fromEntries(Object.entries(input).filter((entry): entry is [string, T] => guard(entry[1]))) } diff --git a/packages/core/test/config/config.test.ts b/packages/core/test/config/config.test.ts index b092bb5582..328b5a3b82 100644 --- a/packages/core/test/config/config.test.ts +++ b/packages/core/test/config/config.test.ts @@ -93,6 +93,7 @@ describe("Config", () => { provider: { bedrock: { npm: "@ai-sdk/amazon-bedrock", + api: "https://bedrock.example.com", options: { headers: { "x-test": "1" }, body: { trace: true }, @@ -103,13 +104,10 @@ describe("Config", () => { }, }) - expect(migrated.providers?.bedrock?.api).toEqual({ - type: "aisdk", + expect(migrated.providers?.bedrock).toMatchObject({ package: "@ai-sdk/amazon-bedrock", - url: undefined, - settings: { region: "us-east-1", profile: "dev" }, - }) - expect(migrated.providers?.bedrock?.request).toEqual({ + aisdk: true, + settings: { baseURL: "https://bedrock.example.com", region: "us-east-1", profile: "dev" }, headers: { "x-test": "1" }, body: { trace: true }, }) @@ -585,34 +583,32 @@ describe("Config", () => { }) expect(documents[0]?.info.attachments).toEqual({ image: { auto_resize: false, max_width: 1200 } }) expect(documents[0]?.info.providers?.custom).toMatchObject({ - request: { body: { apiKey: "secret" } }, + settings: { apiKey: "secret" }, models: { model: { - request: { body: { reasoningEffort: "high" } }, - variants: [{ id: "fast", body: { temperature: 0.2 } }], + settings: { reasoningEffort: "high" }, + variants: [{ id: "fast", settings: { temperature: 0.2 } }], }, }, }) expect(documents[0]?.info.providers?.openai).toMatchObject({ - api: { settings: {} }, - request: { headers: { Authorization: "Bearer secret", "OpenAI-Organization": "org" } }, + package: "@ai-sdk/openai", + aisdk: true, + settings: { apiKey: "secret", organization: "org" }, models: { model: { - request: { - body: { temperature: 0.3, reasoning_effort: "high", service_tier: "priority" }, - }, - variants: [{ id: "high", body: { reasoning_effort: "high", reasoning_summary: "auto" } }], + settings: { temperature: 0.3, reasoningEffort: "high", serviceTier: "priority" }, + variants: [{ id: "high", settings: { reasoningEffort: "high", reasoningSummary: "auto" } }], }, }, }) expect(documents[0]?.info.providers?.anthropic).toMatchObject({ models: { model: { - request: { - body: { - output_config: { effort: "high", task_budget: 4096 }, - metadata: { user_id: "user-1" }, - }, + settings: { + effort: "high", + taskBudget: 4096, + metadata: { userId: "user-1" }, }, }, }, diff --git a/packages/core/test/config/provider-options.test.ts b/packages/core/test/config/provider-options.test.ts index a407353d09..d9fbe2dfd7 100644 --- a/packages/core/test/config/provider-options.test.ts +++ b/packages/core/test/config/provider-options.test.ts @@ -2,20 +2,7 @@ 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", () => { + test("splits provider overlays without changing package settings", () => { const lowerer = ConfigProviderOptionsV1.get("@ai-sdk/openai") expect( @@ -23,189 +10,43 @@ describe("ConfigProviderOptionsV1", () => { apiKey: "secret", baseURL: "https://openai.example/v1", organization: "org", - project: "project", - headers: { "x-test": "1" }, + headers: { "x-test": "1", invalid: true }, body: { store: true }, - timeout: 1000, + nested: { camelCase: true }, }), ).toEqual({ - url: "https://openai.example/v1", - headers: { - Authorization: "Bearer secret", - "OpenAI-Organization": "org", - "OpenAI-Project": "project", - "x-test": "1", + settings: { + apiKey: "secret", + baseURL: "https://openai.example/v1", + organization: "org", + nested: { camelCase: true }, }, + headers: { "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", () => { + test("keeps model and variant options unchanged", () => { 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", + lowerer.model({ + reasoningEffort: "high", taskBudget: 1024, - metadata: { userId: "user", traceId: "trace" }, - nestedValue: { camelCase: true }, + metadata: { userId: "user" }, }), ).toEqual({ - output_config: { effort: "high", task_budget: 1024 }, - metadata: { user_id: "user", trace_id: "trace" }, - nested_value: { camel_case: true }, + reasoningEffort: "high", + taskBudget: 1024, + metadata: { userId: "user" }, }) }) - 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", + test("uses the same mechanical lowering for every package", () => { + expect(ConfigProviderOptionsV1.get("custom-provider").provider({ enabled: true })).toEqual({ + settings: { enabled: true }, 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" }, - }) - }, - ) }) diff --git a/packages/schema/src/model.ts b/packages/schema/src/model.ts index a41b540b4d..59ea7876a6 100644 --- a/packages/schema/src/model.ts +++ b/packages/schema/src/model.ts @@ -41,17 +41,11 @@ 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 {} +export const Variant = Schema.Struct({ + id: VariantID, + ...Provider.Overlays, +}) export interface Info extends Schema.Schema.Type {} export const Info = Schema.Struct({ @@ -59,16 +53,11 @@ export const Info = Schema.Struct({ providerID: Provider.ID, family: Family.pipe(Schema.optional), name: Schema.String, - api: Api, + package: Schema.String.pipe(Schema.optional), + aisdk: Schema.Literal(true).pipe(Schema.optional), + ...Provider.Overlays, capabilities: Capabilities, - request: Schema.Struct({ - ...Provider.Request.fields, - variant: Schema.String.pipe(Schema.optional), - }), - variants: Schema.Struct({ - id: VariantID, - ...Provider.Request.fields, - }).pipe(Schema.Array, Schema.mutable), + variants: Variant.pipe(Schema.Array, Schema.mutable, Schema.optional), time: Schema.Struct({ released: Schema.Finite, }), @@ -89,10 +78,7 @@ export const Info = Schema.Struct({ 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", diff --git a/packages/schema/src/provider.ts b/packages/schema/src/provider.ts index d3d5109608..6493635364 100644 --- a/packages/schema/src/provider.ts +++ b/packages/schema/src/provider.ts @@ -22,23 +22,11 @@ export const ID = Schema.String.pipe( ) export type ID = typeof ID.Type -export interface AISDK extends Schema.Schema.Type {} -export const AISDK = Schema.Struct({ - type: Schema.Literal("aisdk"), - package: Schema.String, - url: Schema.String.pipe(Schema.optional), +export const Overlays = { settings: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), -}) - -export interface Native extends Schema.Schema.Type {} -export const Native = Schema.Struct({ - type: Schema.Literal("native"), - 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 + headers: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional), + body: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), +} export interface Request extends Schema.Schema.Type {} export const Request = Schema.Struct({ @@ -52,8 +40,9 @@ 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: Schema.String, + aisdk: Schema.Literal(true).pipe(Schema.optional), + ...Overlays, }) .annotate({ identifier: "ProviderV2.Info" }) .pipe( @@ -62,8 +51,7 @@ export const Info = Schema.Struct({ schema.make({ id, name: id, - api: { type: "native", settings: {} }, - request: { headers: {}, body: {} }, + package: "", }), })), )