feat(core): migrate native provider config
This commit is contained in:
parent
9848587052
commit
62e44a80e7
16 changed files with 358 additions and 99 deletions
|
|
@ -213,6 +213,7 @@ describe("CatalogV2", () => {
|
|||
type: "aisdk",
|
||||
package: "@ai-sdk/openai-compatible",
|
||||
url: "https://provider.example.com",
|
||||
settings: {},
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -103,13 +103,10 @@ describe("Config", () => {
|
|||
},
|
||||
})
|
||||
|
||||
expect(migrated.providers?.bedrock?.api).toEqual({
|
||||
type: "aisdk",
|
||||
expect(migrated.providers?.bedrock).toMatchObject({
|
||||
package: "@ai-sdk/amazon-bedrock",
|
||||
url: undefined,
|
||||
aiSDK: true,
|
||||
settings: { region: "us-east-1", profile: "dev" },
|
||||
})
|
||||
expect(migrated.providers?.bedrock?.request).toEqual({
|
||||
headers: { "x-test": "1" },
|
||||
body: { trace: true },
|
||||
})
|
||||
|
|
@ -585,34 +582,34 @@ 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" } },
|
||||
body: { apiKey: "secret" },
|
||||
models: {
|
||||
model: {
|
||||
request: { body: { reasoningEffort: "high" } },
|
||||
body: { reasoningEffort: "high" },
|
||||
variants: [{ id: "fast", body: { temperature: 0.2 } }],
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(documents[0]?.info.providers?.openai).toMatchObject({
|
||||
api: { settings: {} },
|
||||
request: { headers: { Authorization: "Bearer secret", "OpenAI-Organization": "org" } },
|
||||
package: "@ai-sdk/openai",
|
||||
aiSDK: true,
|
||||
settings: {},
|
||||
headers: { Authorization: "Bearer secret", "OpenAI-Organization": "org" },
|
||||
models: {
|
||||
model: {
|
||||
request: {
|
||||
body: { temperature: 0.3, reasoningEffort: "high", serviceTier: "priority" },
|
||||
},
|
||||
body: { temperature: 0.3, reasoningEffort: "high", serviceTier: "priority" },
|
||||
variants: [{ id: "high", body: { reasoningEffort: "high", reasoningSummary: "auto" } }],
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(documents[0]?.info.providers?.anthropic).toMatchObject({
|
||||
package: "@ai-sdk/anthropic",
|
||||
aiSDK: true,
|
||||
models: {
|
||||
model: {
|
||||
request: {
|
||||
body: {
|
||||
output_config: { effort: "high", task_budget: 4096 },
|
||||
metadata: { user_id: "user-1" },
|
||||
},
|
||||
body: {
|
||||
output_config: { effort: "high", task_budget: 4096 },
|
||||
metadata: { user_id: "user-1" },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -45,16 +45,90 @@ 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("partitions existing model variant bodies without changing config shape", () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
|
|
@ -68,7 +142,9 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||
info: decode({
|
||||
providers: {
|
||||
opencode: {
|
||||
api: { type: "aisdk", package: "@ai-sdk/openai", url: "https://opencode.test/v1" },
|
||||
package: "@ai-sdk/openai",
|
||||
aiSDK: true,
|
||||
settings: { baseURL: "https://opencode.test/v1" },
|
||||
models: {
|
||||
"alpha-gpt-next": {
|
||||
variants: [
|
||||
|
|
@ -120,7 +196,9 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||
info: decode({
|
||||
providers: {
|
||||
opencode: {
|
||||
api: { type: "aisdk", package: "@ai-sdk/openai", url: "https://opencode.test/v1" },
|
||||
package: "@ai-sdk/openai",
|
||||
aiSDK: true,
|
||||
settings: { baseURL: "https://opencode.test/v1" },
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
|
@ -171,8 +249,8 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||
custom: {
|
||||
name: "Configured",
|
||||
env: ["CUSTOM_API_KEY"],
|
||||
api: { type: "native", settings: {} },
|
||||
request: request({ first: "first", shared: "first" }),
|
||||
package: "custom-native",
|
||||
headers: { first: "first", shared: "first" },
|
||||
models: {
|
||||
chat: {
|
||||
name: "First",
|
||||
|
|
@ -180,7 +258,8 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||
disabled: true,
|
||||
limit: { context: 100, output: 50 },
|
||||
cost: { input: 1, output: 2 },
|
||||
request: request({ first: "first", shared: "first" }, "retained"),
|
||||
headers: { first: "first", shared: "first" },
|
||||
variant: "retained",
|
||||
variants: [
|
||||
{
|
||||
id: "fast",
|
||||
|
|
@ -199,17 +278,19 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||
model: "custom/default",
|
||||
providers: {
|
||||
custom: {
|
||||
api: { type: "aisdk", package: "custom-sdk", url: "https://example.test" },
|
||||
request: request({ last: "last", shared: "last" }),
|
||||
package: "custom-sdk",
|
||||
aiSDK: true,
|
||||
settings: { baseURL: "https://example.test" },
|
||||
headers: { last: "last", shared: "last" },
|
||||
models: {
|
||||
default: {
|
||||
name: "Default",
|
||||
},
|
||||
chat: {
|
||||
api: { id: "api-chat" },
|
||||
id: "api-chat",
|
||||
name: "Last",
|
||||
limit: { output: 75 },
|
||||
request: request({ last: "last", shared: "last" }),
|
||||
headers: { last: "last", shared: "last" },
|
||||
variants: [
|
||||
{
|
||||
id: "fast",
|
||||
|
|
@ -249,7 +330,12 @@ 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" })
|
||||
expect(provider.api).toEqual({
|
||||
type: "aisdk",
|
||||
package: "custom-sdk",
|
||||
url: "https://example.test",
|
||||
settings: { baseURL: "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")
|
||||
|
|
|
|||
|
|
@ -148,6 +148,40 @@ 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: {},
|
||||
generation: {},
|
||||
options: {},
|
||||
},
|
||||
],
|
||||
)
|
||||
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("lowers selected OpenAI-compatible Session variants into Chat options", () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = model(
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ 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],
|
||||
|
|
@ -106,6 +107,7 @@ test("Core reuses the canonical shared schemas", async () => {
|
|||
[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],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue