refactor(schema): flatten provider identity
This commit is contained in:
parent
315ba3720e
commit
1a9bd8c1fd
16 changed files with 262 additions and 436 deletions
|
|
@ -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" })
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue