fix(core): preserve aisdk provider compatibility
This commit is contained in:
parent
864f16c703
commit
78880f3288
9 changed files with 495 additions and 172 deletions
|
|
@ -3,10 +3,8 @@ import { ConfigProviderOptionsV1 } from "@opencode-ai/core/v1/config/provider-op
|
|||
|
||||
describe("ConfigProviderOptionsV1", () => {
|
||||
test("splits provider overlays without changing package settings", () => {
|
||||
const lowerer = ConfigProviderOptionsV1.get("@ai-sdk/openai")
|
||||
|
||||
expect(
|
||||
lowerer.provider({
|
||||
ConfigProviderOptionsV1.provider({
|
||||
apiKey: "secret",
|
||||
baseURL: "https://openai.example/v1",
|
||||
organization: "org",
|
||||
|
|
@ -27,10 +25,8 @@ describe("ConfigProviderOptionsV1", () => {
|
|||
})
|
||||
|
||||
test("keeps model and variant options unchanged", () => {
|
||||
const lowerer = ConfigProviderOptionsV1.get("@ai-sdk/anthropic")
|
||||
|
||||
expect(
|
||||
lowerer.model({
|
||||
ConfigProviderOptionsV1.model({
|
||||
reasoningEffort: "high",
|
||||
taskBudget: 1024,
|
||||
metadata: { userId: "user" },
|
||||
|
|
@ -42,8 +38,8 @@ describe("ConfigProviderOptionsV1", () => {
|
|||
})
|
||||
})
|
||||
|
||||
test("uses the same mechanical lowering for every package", () => {
|
||||
expect(ConfigProviderOptionsV1.get("custom-provider").provider({ enabled: true })).toEqual({
|
||||
test("uses mechanical lowering for custom provider options", () => {
|
||||
expect(ConfigProviderOptionsV1.provider({ enabled: true })).toEqual({
|
||||
settings: { enabled: true },
|
||||
headers: undefined,
|
||||
body: undefined,
|
||||
|
|
|
|||
|
|
@ -80,4 +80,24 @@ describe("GooglePlugin", () => {
|
|||
expect(language.provider).toBe("custom-google")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("wraps AI SDK language models for the native runner", () =>
|
||||
Effect.gen(function* () {
|
||||
const aisdk = yield* AISDK.Service
|
||||
yield* addPlugin()
|
||||
|
||||
const resolved = yield* aisdk.model(
|
||||
ModelV2.Info.make({
|
||||
...ModelV2.Info.empty(ProviderV2.ID.make("custom-google"), ModelV2.ID.make("alias")),
|
||||
modelID: ModelV2.ID.make("gemini-api"),
|
||||
package: "aisdk:@ai-sdk/google",
|
||||
settings: { apiKey: "test" },
|
||||
}),
|
||||
)
|
||||
|
||||
expect(String(resolved.id)).toBe("gemini-api")
|
||||
expect(String(resolved.provider)).toBe("custom-google")
|
||||
expect(resolved.route.id).toBe("ai-sdk:@ai-sdk/google")
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { describe, expect } from "bun:test"
|
||||
import { LLM } from "@opencode-ai/llm"
|
||||
import { OpenAI } from "@opencode-ai/llm/providers"
|
||||
import { LLMClient } from "@opencode-ai/llm/route"
|
||||
import { DateTime, Effect } from "effect"
|
||||
import { Headers } from "effect/unstable/http"
|
||||
|
|
@ -36,6 +37,18 @@ const model = (transport: Transport, variants?: NonNullable<ModelV2.Info["varian
|
|||
limit: { context: 100, output: 20 },
|
||||
})
|
||||
|
||||
const aisdkDependencies = {
|
||||
loadAISDK: (input: ModelV2.Info) =>
|
||||
Effect.succeed(
|
||||
OpenAI.model(input.modelID ?? input.id, {
|
||||
...input.settings,
|
||||
headers: input.headers,
|
||||
body: input.body,
|
||||
limits: { context: input.limit.context, output: input.limit.output },
|
||||
}),
|
||||
),
|
||||
}
|
||||
|
||||
describe("SessionRunnerModel", () => {
|
||||
it.effect("constructs native provider package models mechanically", () =>
|
||||
Effect.gen(function* () {
|
||||
|
|
@ -63,10 +76,10 @@ describe("SessionRunnerModel", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("maps catalog OpenAI AI SDK models into native Responses routes", () =>
|
||||
it.effect("maps catalog OpenAI native provider packages into Responses routes", () =>
|
||||
Effect.gen(function* () {
|
||||
const resolved = yield* SessionRunnerModel.fromCatalogModel(
|
||||
model({ package: "aisdk:@ai-sdk/openai", settings: { baseURL: "https://openai.example/v1" } }),
|
||||
model({ package: "@opencode-ai/llm/providers/openai", settings: { baseURL: "https://openai.example/v1" } }),
|
||||
)
|
||||
|
||||
expect(resolved).toMatchObject({ id: "api-test-model", provider: "test-provider" })
|
||||
|
|
@ -85,7 +98,7 @@ describe("SessionRunnerModel", () => {
|
|||
it.effect("keeps catalog apiKey credentials out of provider JSON", () =>
|
||||
Effect.gen(function* () {
|
||||
const resolved = yield* SessionRunnerModel.fromCatalogModel(
|
||||
model({ package: "aisdk:@ai-sdk/openai", settings: { baseURL: "https://openai.example/v1" } }),
|
||||
model({ package: "@opencode-ai/llm/providers/openai", settings: { baseURL: "https://openai.example/v1" } }),
|
||||
)
|
||||
const prepared = yield* LLMClient.prepare(LLM.request({ model: resolved, prompt: "Hello" }))
|
||||
|
||||
|
|
@ -99,7 +112,7 @@ describe("SessionRunnerModel", () => {
|
|||
const resolved = yield* SessionRunnerModel.fromCatalogModel(
|
||||
ModelV2.Info.make({
|
||||
...model({
|
||||
package: "aisdk:@ai-sdk/openai-compatible",
|
||||
package: "@opencode-ai/llm/providers/openai-compatible",
|
||||
settings: {
|
||||
apiKey: "settings-secret",
|
||||
baseURL: "https://compatible.example/v1",
|
||||
|
|
@ -126,7 +139,7 @@ describe("SessionRunnerModel", () => {
|
|||
|
||||
it.effect("overlays selected OpenAI Session variant bodies", () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = model({ package: "aisdk:@ai-sdk/openai", settings: { baseURL: "https://openai.example/v1" } }, [
|
||||
const catalog = model({ package: "@opencode-ai/llm/providers/openai", settings: { baseURL: "https://openai.example/v1" } }, [
|
||||
{
|
||||
id: ModelV2.VariantID.make("high"),
|
||||
headers: { "x-variant": "high" },
|
||||
|
|
@ -170,7 +183,7 @@ describe("SessionRunnerModel", () => {
|
|||
Effect.gen(function* () {
|
||||
const catalog = model(
|
||||
{
|
||||
package: "aisdk:@ai-sdk/openai-compatible",
|
||||
package: "@opencode-ai/llm/providers/openai-compatible",
|
||||
settings: { baseURL: "https://compatible.example/v1" },
|
||||
},
|
||||
[
|
||||
|
|
@ -204,7 +217,7 @@ describe("SessionRunnerModel", () => {
|
|||
|
||||
it.effect("rejects an explicit unavailable Session variant during model resolution", () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = model({ package: "aisdk:@ai-sdk/openai", settings: { baseURL: "https://openai.example/v1" } })
|
||||
const catalog = model({ package: "@opencode-ai/llm/providers/openai", settings: { baseURL: "https://openai.example/v1" } })
|
||||
const session = SessionV2.Info.make({
|
||||
id: SessionV2.ID.make("ses_model_variant_unavailable"),
|
||||
projectID: ProjectV2.ID.global,
|
||||
|
|
@ -235,7 +248,7 @@ describe("SessionRunnerModel", () => {
|
|||
it.effect("overlays selected Anthropic Session variant bodies", () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = model(
|
||||
{ package: "aisdk:@ai-sdk/anthropic", settings: { baseURL: "https://anthropic.example/v1" } },
|
||||
{ package: "@opencode-ai/llm/providers/anthropic", settings: { baseURL: "https://anthropic.example/v1" } },
|
||||
[
|
||||
{
|
||||
id: ModelV2.VariantID.make("high"),
|
||||
|
|
@ -264,10 +277,10 @@ describe("SessionRunnerModel", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("maps catalog Anthropic AI SDK models into native routes", () =>
|
||||
it.effect("maps catalog Anthropic native provider packages into native routes", () =>
|
||||
Effect.gen(function* () {
|
||||
const resolved = yield* SessionRunnerModel.fromCatalogModel(
|
||||
model({ package: "aisdk:@ai-sdk/anthropic", settings: { baseURL: "https://anthropic.example/v1" } }),
|
||||
model({ package: "@opencode-ai/llm/providers/anthropic", settings: { baseURL: "https://anthropic.example/v1" } }),
|
||||
)
|
||||
|
||||
expect(resolved.route).toMatchObject({
|
||||
|
|
@ -281,7 +294,7 @@ describe("SessionRunnerModel", () => {
|
|||
Effect.gen(function* () {
|
||||
const resolved = yield* SessionRunnerModel.fromCatalogModel(
|
||||
ModelV2.Info.make({
|
||||
...model({ package: "aisdk:@ai-sdk/openai", settings: { baseURL: "https://openai.example/v1" } }),
|
||||
...model({ package: "@opencode-ai/llm/providers/openai", settings: { baseURL: "https://openai.example/v1" } }),
|
||||
headers: {},
|
||||
body: {},
|
||||
}),
|
||||
|
|
@ -305,7 +318,7 @@ describe("SessionRunnerModel", () => {
|
|||
const credential = Credential.Key.make({ type: "key", key: "stored-secret", metadata: { tenant: "work" } })
|
||||
const resolved = yield* SessionRunnerModel.fromCatalogModel(
|
||||
ModelV2.Info.make({
|
||||
...model({ package: "aisdk:@ai-sdk/openai", settings: { baseURL: "https://openai.example/v1" } }),
|
||||
...model({ package: "@opencode-ai/llm/providers/openai", settings: { baseURL: "https://openai.example/v1" } }),
|
||||
settings: { apiKey: "configured-secret", baseURL: "https://openai.example/v1" },
|
||||
headers: {},
|
||||
body: {},
|
||||
|
|
@ -325,34 +338,26 @@ describe("SessionRunnerModel", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("rejects catalog APIs without a native route", () =>
|
||||
it.effect("delegates aisdk-prefixed packages to the compatibility resolver", () =>
|
||||
Effect.gen(function* () {
|
||||
const failure = yield* SessionRunnerModel.fromCatalogModel(
|
||||
const resolved = yield* SessionRunnerModel.fromCatalogModel(
|
||||
model({ package: "aisdk:@ai-sdk/google", settings: { baseURL: "https://google.example/v1" } }),
|
||||
).pipe(Effect.flip)
|
||||
undefined,
|
||||
aisdkDependencies,
|
||||
)
|
||||
|
||||
expect(failure).toMatchObject({
|
||||
_tag: "SessionRunnerModel.UnsupportedPackageError",
|
||||
providerID: "test-provider",
|
||||
modelID: "test-model",
|
||||
package: "aisdk:@ai-sdk/google",
|
||||
})
|
||||
expect(failure.message).toBe("Unsupported package for test-provider/test-model: aisdk:@ai-sdk/google")
|
||||
expect(resolved.route.id).toBe("openai-responses")
|
||||
expect(resolved.route.endpoint).toMatchObject({ baseURL: "https://google.example/v1" })
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("reports whether a catalog model has a supported native route", () =>
|
||||
it.effect("reports whether a catalog model has a supported package", () =>
|
||||
Effect.sync(() => {
|
||||
expect(
|
||||
SessionRunnerModel.supported(
|
||||
model({ package: "aisdk:@ai-sdk/openai", settings: { baseURL: "https://openai.example/v1" } }),
|
||||
),
|
||||
).toBe(true)
|
||||
expect(
|
||||
SessionRunnerModel.supported(
|
||||
model({ package: "aisdk:@ai-sdk/google", settings: { baseURL: "https://google.example/v1" } }),
|
||||
),
|
||||
).toBe(false)
|
||||
).toBe(true)
|
||||
expect(SessionRunnerModel.supported(model({ package: "native-provider-package" }))).toBe(true)
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue