refactor(core): flatten provider config

This commit is contained in:
Dax Raad 2026-06-24 12:51:59 -04:00
commit 98d48b60be
8 changed files with 93 additions and 482 deletions

View file

@ -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<Request>("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<Request>("ConfigV2.Provider.Request")({
headers: Overlays.headers,
body: Overlays.body,
}) {}
class Cache extends Schema.Class<Cache>("ConfigV2.Model.Cost.Cache")({
@ -30,32 +35,17 @@ 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),
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<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),
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),
}) {}

View file

@ -144,12 +144,12 @@ export const OpencodePlugin = define<HttpClient.HttpClient | EventV2.Service | S
const packageName = config.provider?.npm ?? item.npm
const lowerer = ConfigProviderOptionsV1.get(packageName)
Object.assign(model.request.headers, config.headers)
Object.assign(model.request.body, lowerer.request(withoutCredentials(config.options)))
Object.assign(model.request.body, lowerer.model(withoutCredentials(config.options)))
if (config.variants !== undefined) {
model.variants = Object.entries(config.variants).map(([id, options]) => ({
id: ModelV2.VariantID.make(id),
headers: { ...(options.headers ?? {}) },
body: lowerer.request(withoutCredentials(options)),
body: lowerer.model(withoutCredentials(options)),
}))
}
if (config.release_date !== undefined) {

View file

@ -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,

View file

@ -3,209 +3,32 @@ export * as ConfigProviderOptionsV1 from "./provider-options"
type Options = Readonly<Record<string, unknown>>
export interface ProviderResult {
readonly settings: Record<string, unknown>
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>
readonly model: (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 = {
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<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)
function record<T>(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])))
}

View file

@ -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" },
},
},
},

View file

@ -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" },
})
},
)
})

View file

@ -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<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({
@ -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",

View file

@ -22,23 +22,11 @@ export const ID = Schema.String.pipe(
)
export type ID = typeof ID.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),
export const Overlays = {
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"),
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<typeof Request> {}
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: "",
}),
})),
)