feat(core): flatten provider config and load native packages (#35563)

Co-authored-by: Dax Raad <d@ironbay.co>
This commit is contained in:
Shoubhit Dash 2026-07-07 01:48:56 +05:30 committed by GitHub
commit e57d9ca390
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
107 changed files with 2593 additions and 1984 deletions

View file

@ -62,7 +62,7 @@ describe("ConfigAgentPlugin.Plugin", () => {
info: decode({
permissions: [{ action: "read", resource: "*", effect: "allow" }],
agents: {
reviewer: { variant: "high", hidden: true },
reviewer: { model: "openrouter/openai/gpt-5#high", hidden: true },
removed: { disabled: true },
late: {
permissions: [{ action: "edit", resource: "*", effect: "allow" }],
@ -170,7 +170,7 @@ describe("ConfigAgentPlugin.Plugin", () => {
hidden: true,
color: "warning",
steps: 12,
model: { providerID: "anthropic", id: "claude-sonnet", variant: undefined },
model: { providerID: "anthropic", id: "claude-sonnet" },
})
expect(reviewer.request).toEqual({
settings: {},

View file

@ -44,8 +44,7 @@ describe("ConfigCommandPlugin.Plugin", () => {
`---
description: File review
agent: reviewer
model: anthropic/claude
variant: high
model: anthropic/claude#high
subtask: true
---
Review files`,

View file

@ -4,6 +4,7 @@ import { describe, expect } from "bun:test"
import { Effect, Fiber, Layer, PubSub, Schema, Stream } from "effect"
import { FastCheck } from "effect/testing"
import { Config } from "@opencode-ai/core/config"
import { ConfigModel } from "@opencode-ai/core/config/model"
import { Config as ConfigSchema } from "@opencode-ai/schema/config"
import { ConfigProvider } from "@opencode-ai/core/config/provider"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
@ -16,12 +17,14 @@ import { EventV2 } from "@opencode-ai/core/event"
import { Global } from "@opencode-ai/core/global"
import { Location } from "@opencode-ai/core/location"
import { Project } from "@opencode-ai/core/project"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { location } from "../fixture/location"
import { tmpdir } from "../fixture/tmpdir"
import { testEffect } from "../lib/effect"
const it = testEffect(Layer.empty)
const selection = Schema.decodeUnknownSync(ConfigModel.Selection)
function testLayer(
directory: string,
@ -47,11 +50,10 @@ function testLayer(
}
const provider = {
api: { type: "native", settings: {} },
request: {
headers: {},
body: {},
},
package: "native",
settings: {},
headers: {},
body: {},
models: {},
}
@ -105,13 +107,19 @@ describe("Config", () => {
it.effect("returns the latest defined scalar from priority-ordered documents", () =>
Effect.sync(() => {
const entries = [
new Config.Document({ type: "document", info: new Config.Info({ model: "openrouter/openai/gpt-5" }) }),
new Config.Document({
type: "document",
info: new Config.Info({ model: selection("openrouter/openai/gpt-5") }),
}),
new Config.Directory({ type: "directory", path: AbsolutePath.make("/skills") }),
new Config.Document({ type: "document", info: new Config.Info({}) }),
new Config.Document({ type: "document", info: new Config.Info({ model: "openrouter/openai/gpt-5.5" }) }),
new Config.Document({
type: "document",
info: new Config.Info({ model: selection("openrouter/openai/gpt-5.5") }),
}),
]
expect(Config.latest(entries, "model")).toBe("openrouter/openai/gpt-5.5")
expect(Config.latest(entries, "model")).toEqual(selection("openrouter/openai/gpt-5.5"))
expect(Config.latest(entries, "default_agent")).toBeUndefined()
}),
)
@ -142,7 +150,12 @@ describe("Config", () => {
Effect.sync(() => {
FastCheck.assert(
FastCheck.property(Schema.toArbitrary(ConfigV1.Info), (info) => {
Schema.decodeUnknownSync(Config.Info)(ConfigMigrateV1.migrate(info), { errors: "all" })
const parsed = Schema.decodeUnknownSync(ConfigV1.Info)(
Schema.decodeUnknownSync(Schema.UnknownFromJsonString)(
Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(info),
),
)
Schema.decodeUnknownSync(Config.Info)(ConfigMigrateV1.migrate(parsed), { errors: "all" })
}),
{ numRuns: 100 },
)
@ -165,12 +178,9 @@ describe("Config", () => {
},
})
expect(migrated.providers?.bedrock?.api).toEqual({
type: "aisdk",
package: "@ai-sdk/amazon-bedrock",
expect(migrated.providers?.bedrock).toMatchObject({
package: ProviderV2.aisdk("@ai-sdk/amazon-bedrock"),
settings: { region: "us-east-1", profile: "dev" },
})
expect(migrated.providers?.bedrock?.request).toEqual({
headers: { "x-test": "1" },
body: { trace: true },
})
@ -197,8 +207,7 @@ describe("Config", () => {
template: "Review changes",
description: "Review code",
agent: "reviewer",
model: "anthropic/claude",
variant: "high",
model: { providerID: "anthropic", model: "claude", variant: "high" },
subtask: true,
},
})
@ -431,8 +440,7 @@ describe("Config", () => {
],
agents: {
reviewer: {
model: "openrouter/openai/gpt-5",
variant: "high",
model: "openrouter/openai/gpt-5#high",
request: {
headers: { "x-agent": "reviewer" },
body: { reasoningEffort: "high" },
@ -505,7 +513,7 @@ describe("Config", () => {
expect(documents).toHaveLength(1)
expect(documents[0]?.info.shell).toBe("/bin/bash")
expect(documents[0]?.info.model).toBe("anthropic/claude")
expect(documents[0]?.info.model).toEqual(selection("anthropic/claude"))
expect(documents[0]?.info.default_agent).toBe("reviewer")
expect(documents[0]?.info.autoupdate).toBe("notify")
expect(documents[0]?.info.share).toBe("disabled")
@ -516,8 +524,7 @@ describe("Config", () => {
{ action: "bash", resource: "git status", effect: "allow" },
])
const reviewer = documents[0]?.info.agents?.reviewer
expect(reviewer?.model).toBe("openrouter/openai/gpt-5")
expect(reviewer?.variant).toBe("high")
expect(reviewer?.model).toEqual(selection("openrouter/openai/gpt-5#high"))
expect(reviewer?.request).toEqual({
headers: { "x-agent": "reviewer" },
body: { reasoningEffort: "high" },
@ -748,34 +755,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: ProviderV2.aisdk("@ai-sdk/openai"),
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", 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({
package: ProviderV2.aisdk("@ai-sdk/anthropic"),
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

@ -0,0 +1,28 @@
import { describe, expect, test } from "bun:test"
import { ConfigModel } from "@opencode-ai/core/config/model"
import { Model } from "@opencode-ai/schema/model"
import { Provider } from "@opencode-ai/schema/provider"
import { Schema } from "effect"
const decode = Schema.decodeUnknownSync(ConfigModel.Selection)
describe("ConfigModel.Selection", () => {
test("normalizes short and explicit model selections", () => {
expect(decode("openrouter/openai/gpt-5#high")).toEqual({
providerID: Provider.ID.make("openrouter"),
model: Model.ID.make("openai/gpt-5"),
variant: Model.VariantID.make("high"),
})
expect(decode({ providerID: "anthropic", model: "claude-sonnet", variant: "high" })).toEqual({
providerID: Provider.ID.make("anthropic"),
model: Model.ID.make("claude-sonnet"),
variant: Model.VariantID.make("high"),
})
})
test("rejects malformed selections and reserved fragments", () => {
expect(() => decode("gpt-5")).toThrow()
expect(() => decode("openai/gpt-5#")).toThrow()
expect(() => decode({ providerID: "openai", model: "gpt-5#high" })).toThrow()
})
})

View file

@ -2,223 +2,47 @@ 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", () => {
const lowerer = ConfigProviderOptionsV1.get("@ai-sdk/openai")
test("splits provider overlays without changing package settings", () => {
expect(
lowerer.provider({
ConfigProviderOptionsV1.provider({
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 },
})
})
test("keeps model and variant options unchanged", () => {
expect(
lowerer.request({
ConfigProviderOptionsV1.model({
reasoningEffort: "high",
reasoningSummary: "auto",
reasoning: { encryptedContent: true },
textVerbosity: "low",
text: { outputFormat: "plain" },
nestedValue: { camelCase: true },
}),
).toEqual({
reasoning: { encrypted_content: true, effort: "high", summary: "auto" },
text: { output_format: "plain", verbosity: "low" },
nested_value: { camel_case: true },
})
})
test("lowers Anthropic provider and request options", () => {
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",
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", reasoningSummary: "auto", textVerbosity: "low" })).toEqual({
reasoning: { effort: "high", summary: "auto" },
text: { verbosity: "low" },
})
})
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 mechanical lowering for custom provider options", () => {
expect(ConfigProviderOptionsV1.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

@ -45,13 +45,6 @@ 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", () => {
@ -68,7 +61,8 @@ describe("ConfigProviderPlugin.Plugin", () => {
info: decode({
providers: {
opencode: {
api: { type: "aisdk", package: "@ai-sdk/openai", url: "https://opencode.test/v1" },
package: "aisdk:@ai-sdk/openai",
settings: { baseURL: "https://opencode.test/v1" },
models: {
"alpha-gpt-next": {
variants: [
@ -119,7 +113,8 @@ describe("ConfigProviderPlugin.Plugin", () => {
info: decode({
providers: {
opencode: {
api: { type: "aisdk", package: "@ai-sdk/openai", url: "https://opencode.test/v1" },
package: "aisdk:@ai-sdk/openai",
settings: { baseURL: "https://opencode.test/v1" },
},
},
}),
@ -144,7 +139,7 @@ describe("ConfigProviderPlugin.Plugin", () => {
yield* addPlugin(config)
const model = required(yield* catalog.model.get(providerID, modelID))
expect(model.variants[0]).toMatchObject({
expect(model.variants?.[0]).toMatchObject({
id: "high",
body: { reasoningEffort: "high" },
})
@ -169,8 +164,8 @@ describe("ConfigProviderPlugin.Plugin", () => {
custom: {
name: "Configured",
env: ["CUSTOM_API_KEY"],
api: { type: "native", settings: {} },
request: request({ first: "first", shared: "first" }),
package: "native",
headers: { first: "first", shared: "first" },
models: {
chat: {
name: "First",
@ -178,7 +173,8 @@ describe("ConfigProviderPlugin.Plugin", () => {
disabled: true,
limit: { context: 100, output: 50 },
cost: { input: 1, output: 2 },
request: request({ first: "first", shared: "first" }, "retained"),
settings: { retained: true },
headers: { first: "first", shared: "first" },
variants: [
{
id: "fast",
@ -197,17 +193,18 @@ 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: "aisdk:custom-sdk",
settings: { baseURL: "https://example.test" },
headers: { last: "last", shared: "last" },
models: {
default: {
name: "Default",
},
chat: {
api: { id: "api-chat" },
modelID: "api-chat",
name: "Last",
limit: { output: 75 },
request: request({ last: "last", shared: "last" }),
headers: { last: "last", shared: "last" },
variants: [
{
id: "fast",
@ -247,22 +244,24 @@ 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.request.headers).toEqual({ first: "first", shared: "last", last: "last" })
expect(model.api.id).toBe(ModelV2.ID.make("api-chat"))
expect(provider.package).toBe("aisdk:custom-sdk")
expect(provider.settings).toEqual({ baseURL: "https://example.test" })
expect(provider.headers).toEqual({ first: "first", shared: "last", last: "last" })
expect(model.id).toBe(modelID)
expect(model.modelID).toBe(ModelV2.ID.make("api-chat"))
expect(model.name).toBe("Last")
expect(model.capabilities).toEqual({ tools: true, input: ["text"], output: ["text"] })
expect(model.enabled).toBe(false)
expect(model.limit).toEqual({ context: 100, output: 75 })
expect(model.cost).toEqual([{ input: 1, output: 2, cache: { read: 0, write: 0 }, tier: undefined }])
expect(model.request.headers).toEqual({ first: "first", shared: "last", last: "last" })
expect(model.request.variant).toBe("retained")
expect(model.variants.map((variant) => variant.id)).toEqual([
expect(model.settings).toEqual({ baseURL: "https://example.test", retained: true })
expect(model.headers).toEqual({ first: "first", shared: "last", last: "last" })
expect(model.variants?.map((variant) => variant.id)).toEqual([
ModelV2.VariantID.make("fast"),
ModelV2.VariantID.make("slow"),
])
expect(model.variants[0]?.headers).toEqual({ first: "first", shared: "last", last: "last" })
expect(model.variants[1]?.headers).toEqual({ slow: "slow" })
expect(model.variants?.[0]?.headers).toEqual({ first: "first", shared: "last", last: "last" })
expect(model.variants?.[1]?.headers).toEqual({ slow: "slow" })
}),
),
)