refactor(core): add replayable catalog transforms

This commit is contained in:
Dax Raad 2026-05-28 23:32:59 -04:00
commit 506a090a2e
44 changed files with 700 additions and 312 deletions

View file

@ -19,12 +19,11 @@ const it = testEffect(PluginV2.defaultLayer)
function context(
records: { provider: ProviderV2.Info; models: Map<ModelV2.ID, ModelV2.Info> }[],
updates: Array<{ id: ProviderV2.ID; enabled: ProviderV2.Info["enabled"]; apiKey?: string }>,
): Catalog.Context {
): Catalog.Editor {
return {
data: records,
updateProvider: (providerID, fn) => context(records, updates).provider.update(providerID, fn),
updateModel: (providerID, modelID, fn) => context(records, updates).model.update(providerID, modelID, fn),
provider: {
list: () => records,
get: (providerID) => records.find((item) => item.provider.id === providerID),
update: (providerID, fn) => {
const record = records.find((item) => item.provider.id === providerID)
const provider = produce(record?.provider ?? ProviderV2.Info.empty(providerID), fn)
@ -45,8 +44,13 @@ function context(
},
},
model: {
get: () => undefined,
update: () => {},
remove: () => {},
default: {
get: () => undefined,
set: () => {},
},
},
}
}
@ -192,7 +196,7 @@ describe("AccountV2", () => {
]
const updates: Array<{ id: ProviderV2.ID; enabled: ProviderV2.Info["enabled"]; apiKey?: string }> = []
const catalog = Catalog.Service.of({
loader: () => Effect.die("unexpected catalog.loader"),
transform: () => Effect.die("unexpected catalog.transform"),
provider: {
get: () => Effect.die("unexpected provider.get"),
all: () => Effect.succeed([]),
@ -203,7 +207,6 @@ describe("AccountV2", () => {
all: () => Effect.succeed([]),
available: () => Effect.succeed([]),
default: () => Effect.succeed(Option.none<ModelV2.Info>()),
setDefault: () => Effect.die("unexpected model.setDefault"),
small: () => Effect.succeed(Option.none<ModelV2.Info>()),
},
})

View file

@ -29,9 +29,9 @@ describe("CatalogV2", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
const providerID = ProviderV2.ID.make("test")
const load = yield* catalog.loader()
const transform = yield* catalog.transform()
yield* load((catalog) =>
yield* transform((catalog) =>
catalog.provider.update(providerID, (provider) => {
provider.endpoint = {
type: "aisdk",
@ -55,9 +55,9 @@ describe("CatalogV2", () => {
const catalog = yield* Catalog.Service
const providerID = ProviderV2.ID.make("test")
const modelID = ModelV2.ID.make("model")
const load = yield* catalog.loader()
const transform = yield* catalog.transform()
yield* load((catalog) => {
yield* transform((catalog) => {
catalog.provider.update(providerID, (provider) => {
provider.endpoint = {
type: "aisdk",
@ -84,9 +84,9 @@ describe("CatalogV2", () => {
const catalog = yield* Catalog.Service
const providerID = ProviderV2.ID.make("test")
const modelID = ModelV2.ID.make("model")
const load = yield* catalog.loader()
const transform = yield* catalog.transform()
yield* load((catalog) => {
yield* transform((catalog) => {
catalog.provider.update(providerID, (provider) => {
provider.endpoint = {
type: "aisdk",
@ -111,14 +111,14 @@ describe("CatalogV2", () => {
const plugin = yield* PluginV2.Service
const providerID = ProviderV2.ID.make("test")
const seen: unknown[] = []
const load = yield* catalog.loader()
const transform = yield* catalog.transform()
yield* plugin.add({
id: PluginV2.ID.make("test"),
effect: Effect.succeed({
"catalog.transform": (evt) =>
Effect.sync(() => {
const item = evt.data.find((record) => record.provider.id === providerID)
const item = evt.provider.get(providerID)
if (!item) return
seen.push(item.provider.endpoint.type)
if (item?.provider.endpoint.type === "aisdk") seen.push(item.provider.endpoint.url)
@ -126,7 +126,7 @@ describe("CatalogV2", () => {
}),
}),
})
yield* load((catalog) =>
yield* transform((catalog) =>
catalog.provider.update(providerID, (provider) => {
provider.endpoint = { type: "aisdk", package: "@ai-sdk/openai-compatible" }
provider.options.aisdk.provider.baseURL = "https://provider.example.com"
@ -142,9 +142,9 @@ describe("CatalogV2", () => {
const catalog = yield* Catalog.Service
const plugin = yield* PluginV2.Service
const providerID = ProviderV2.ID.make("test")
const load = yield* catalog.loader()
const transform = yield* catalog.transform()
yield* load((catalog) =>
yield* transform((catalog) =>
catalog.provider.update(providerID, (provider) => {
provider.name = "Before"
}),
@ -171,9 +171,9 @@ describe("CatalogV2", () => {
const catalog = yield* Catalog.Service
const providerID = ProviderV2.ID.make("test")
const modelID = ModelV2.ID.make("model")
const load = yield* catalog.loader()
const transform = yield* catalog.transform()
yield* load((catalog) => {
yield* transform((catalog) => {
catalog.provider.update(providerID, (provider) => {
provider.options.headers.provider = "provider"
provider.options.headers.shared = "provider"
@ -201,9 +201,9 @@ describe("CatalogV2", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
const providerID = ProviderV2.ID.make("test")
const load = yield* catalog.loader()
const transform = yield* catalog.transform()
yield* load((catalog) => {
yield* transform((catalog) => {
catalog.provider.update(providerID, (provider) => {
provider.enabled = { via: "custom", data: {} }
})
@ -219,13 +219,44 @@ describe("CatalogV2", () => {
}),
)
it.effect("uses a transform-provided default model until that transform is replaced", () =>
Effect.gen(function* () {
const catalog = yield* Catalog.Service
const providerID = ProviderV2.ID.make("test")
const old = ModelV2.ID.make("old")
const newest = ModelV2.ID.make("new")
const transform = yield* catalog.transform()
const models = (catalog: Catalog.Editor) => {
catalog.provider.update(providerID, (provider) => {
provider.enabled = { via: "custom", data: {} }
})
catalog.model.update(providerID, old, (model) => {
model.time.released = DateTime.makeUnsafe(1000)
})
catalog.model.update(providerID, newest, (model) => {
model.time.released = DateTime.makeUnsafe(2000)
})
}
yield* transform((catalog) => {
models(catalog)
catalog.model.default.set(providerID, old)
})
expect(Option.getOrUndefined(yield* catalog.model.default())?.id).toBe(old)
yield* transform(models)
expect(Option.getOrUndefined(yield* catalog.model.default())?.id).toBe(newest)
}),
)
it.effect("small model prefers small keyword candidates before cost scoring", () =>
Effect.gen(function* () {
const catalog = yield* Catalog.Service
const providerID = ProviderV2.ID.make("test")
const load = yield* catalog.loader()
const transform = yield* catalog.transform()
yield* load((catalog) => {
yield* transform((catalog) => {
catalog.provider.update(providerID, () => {})
catalog.model.update(providerID, ModelV2.ID.make("cheap-large"), (model) => {
model.capabilities.input = ["text"]
@ -250,10 +281,10 @@ describe("CatalogV2", () => {
const catalog = yield* Catalog.Service
const policy = yield* Policy.Service
const providerID = ProviderV2.ID.make("blocked")
const load = yield* catalog.loader()
const transform = yield* catalog.transform()
yield* policy.load([new Policy.Info({ effect: "deny", action: "provider.use", resource: "blocked" })])
yield* load((catalog) => {
yield* transform((catalog) => {
catalog.provider.update(providerID, () => {})
catalog.model.update(providerID, ModelV2.ID.make("model"), () => {})
})

View file

@ -24,8 +24,8 @@ describe("AmazonBedrockPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(AmazonBedrockPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const bedrock = provider("amazon-bedrock", {
endpoint: { type: "aisdk", package: "@ai-sdk/amazon-bedrock" },
options: {

View file

@ -12,8 +12,8 @@ describe("AnthropicPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(AnthropicPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("anthropic", {
endpoint: { type: "aisdk", package: "@ai-sdk/anthropic" },
options: { headers: { Existing: "1" }, body: {}, aisdk: { provider: {}, request: {} } },
@ -35,8 +35,8 @@ describe("AnthropicPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(AnthropicPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => catalog.provider.update(provider("openai").id, () => {}))
const transform = yield* catalog.transform()
yield* transform((catalog) => catalog.provider.update(provider("openai").id, () => {}))
expect((yield* catalog.provider.get(ProviderV2.ID.openai)).options.headers["anthropic-beta"]).toBeUndefined()
}),
)

View file

@ -13,8 +13,8 @@ describe("AzureCognitiveServicesPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(AzureCognitiveServicesPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("azure-cognitive-services"), (item) => {
item.endpoint = { type: "aisdk", package: "@ai-sdk/openai-compatible" }
})
@ -37,8 +37,8 @@ describe("AzureCognitiveServicesPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(AzureCognitiveServicesPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const azure = provider("azure-cognitive-services", {
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible" },
})

View file

@ -34,8 +34,8 @@ describe("AzurePlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(AzurePlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
catalog.provider.update(ProviderV2.ID.azure, (item) => {
item.endpoint = { type: "aisdk", package: "@ai-sdk/azure" }
})
@ -51,8 +51,8 @@ describe("AzurePlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(AzurePlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const azure = provider("azure", {
endpoint: { type: "aisdk", package: "@ai-sdk/azure" },
options: { headers: {}, body: {}, aisdk: { provider: { resourceName: "from-config" }, request: {} } },
@ -100,8 +100,8 @@ describe("AzurePlugin", () => {
),
})
yield* plugin.add(AzurePlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
catalog.provider.update(ProviderV2.ID.azure, (item) => {
item.endpoint = { type: "aisdk", package: "@ai-sdk/azure" }
})
@ -119,8 +119,8 @@ describe("AzurePlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(AzurePlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const azure = provider("azure", {
endpoint: { type: "aisdk", package: "@ai-sdk/azure" },
options: { headers: {}, body: {}, aisdk: { provider: { resourceName: "" }, request: {} } },
@ -141,8 +141,8 @@ describe("AzurePlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(AzurePlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const azure = provider("azure", {
endpoint: { type: "aisdk", package: "@ai-sdk/azure" },
options: { headers: {}, body: {}, aisdk: { provider: { resourceName: " " }, request: {} } },

View file

@ -24,8 +24,8 @@ describe("CerebrasPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(CerebrasPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("cerebras"), (item) => {
item.endpoint = { type: "aisdk", package: "@ai-sdk/cerebras" }
item.options.headers.Existing = "1"
@ -43,8 +43,8 @@ describe("CerebrasPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(CerebrasPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => catalog.provider.update(ProviderV2.ID.make("groq"), () => {}))
const transform = yield* catalog.transform()
yield* transform((catalog) => catalog.provider.update(ProviderV2.ID.make("groq"), () => {}))
expect((yield* catalog.provider.get(ProviderV2.ID.make("groq"))).options.headers).toEqual({})
}),
)

View file

@ -54,8 +54,8 @@ describe("CloudflareWorkersAIPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(CloudflareWorkersAIPlugin)
const load = yield* catalog.loader()
yield* load((catalog) =>
const transform = yield* catalog.transform()
yield* transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => {
provider.endpoint = { type: "aisdk", package: "test-provider" }
}),
@ -86,8 +86,8 @@ describe("CloudflareWorkersAIPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(CloudflareWorkersAIPlugin)
const load = yield* catalog.loader()
yield* load((catalog) =>
const transform = yield* catalog.transform()
yield* transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => {
provider.endpoint = { type: "aisdk", package: "test-provider", url: "https://proxy.example/v1" }
}),
@ -152,8 +152,8 @@ describe("CloudflareWorkersAIPlugin", () => {
),
})
yield* plugin.add(CloudflareWorkersAIPlugin)
const load = yield* catalog.loader()
yield* load((catalog) =>
const transform = yield* catalog.transform()
yield* transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => {
provider.endpoint = { type: "aisdk", package: "test-provider" }
}),
@ -173,8 +173,8 @@ describe("CloudflareWorkersAIPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(CloudflareWorkersAIPlugin)
const load = yield* catalog.loader()
yield* load((catalog) =>
const transform = yield* catalog.transform()
yield* transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => {
provider.endpoint = { type: "aisdk", package: "test-provider" }
provider.options.aisdk.provider.accountId = "configured-acct"

View file

@ -152,8 +152,8 @@ describe("GithubCopilotPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(GithubCopilotPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("github-copilot"), () => {})
catalog.model.update(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5-chat-latest"), () => {})
})
@ -168,8 +168,8 @@ describe("GithubCopilotPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(GithubCopilotPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("custom-copilot"), () => {})
catalog.model.update(ProviderV2.ID.make("custom-copilot"), ModelV2.ID.make("gpt-5-chat-latest"), () => {})
})

View file

@ -184,8 +184,8 @@ describe("GitLabPlugin", () => {
),
})
yield* plugin.add(GitLabPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => catalog.provider.update(ProviderV2.ID.make("gitlab"), () => {}))
const transform = yield* catalog.transform()
yield* transform((catalog) => catalog.provider.update(ProviderV2.ID.make("gitlab"), () => {}))
const provider = yield* catalog.provider.get(ProviderV2.ID.make("gitlab"))
yield* plugin.trigger(
"aisdk.sdk",
@ -232,8 +232,8 @@ describe("GitLabPlugin", () => {
),
})
yield* plugin.add(GitLabPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => catalog.provider.update(ProviderV2.ID.make("gitlab"), () => {}))
const transform = yield* catalog.transform()
yield* transform((catalog) => catalog.provider.update(ProviderV2.ID.make("gitlab"), () => {}))
const provider = yield* catalog.provider.get(ProviderV2.ID.make("gitlab"))
yield* plugin.trigger(
"aisdk.sdk",

View file

@ -22,8 +22,8 @@ describe("GoogleVertexAnthropicPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(GoogleVertexAnthropicPlugin)
const load = yield* catalog.loader()
yield* load((catalog) =>
const transform = yield* catalog.transform()
yield* transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("google-vertex-anthropic"), (provider) => {
provider.endpoint = { type: "aisdk", package: "@ai-sdk/google-vertex/anthropic" }
}),
@ -41,8 +41,8 @@ describe("GoogleVertexAnthropicPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(GoogleVertexAnthropicPlugin)
const load = yield* catalog.loader()
yield* load((catalog) =>
const transform = yield* catalog.transform()
yield* transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("google-vertex-anthropic"), (provider) => {
provider.endpoint = { type: "aisdk", package: "@ai-sdk/google-vertex/anthropic" }
provider.options.aisdk.provider.project = "configured-project"

View file

@ -47,8 +47,8 @@ describe("GoogleVertexPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(GoogleVertexPlugin)
const load = yield* catalog.loader()
yield* load((catalog) =>
const transform = yield* catalog.transform()
yield* transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
provider.endpoint = {
type: "aisdk",
@ -86,8 +86,8 @@ describe("GoogleVertexPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(GoogleVertexPlugin)
const load = yield* catalog.loader()
yield* load((catalog) =>
const transform = yield* catalog.transform()
yield* transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
provider.endpoint = {
type: "aisdk",
@ -136,8 +136,8 @@ describe("GoogleVertexPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(GoogleVertexPlugin)
const load = yield* catalog.loader()
yield* load((catalog) =>
const transform = yield* catalog.transform()
yield* transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
provider.endpoint = {
type: "aisdk",
@ -165,8 +165,8 @@ describe("GoogleVertexPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(GoogleVertexPlugin)
const load = yield* catalog.loader()
yield* load((catalog) =>
const transform = yield* catalog.transform()
yield* transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
provider.endpoint = {
type: "aisdk",
@ -201,8 +201,8 @@ describe("GoogleVertexPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(GoogleVertexPlugin)
const load = yield* catalog.loader()
yield* load((catalog) =>
const transform = yield* catalog.transform()
yield* transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
provider.endpoint = { type: "aisdk", package: "@ai-sdk/google-vertex" }
provider.options.aisdk.provider.project = "config-project"

View file

@ -31,7 +31,7 @@ export const npmLayer = Layer.succeed(
export const catalogLayer = Layer.succeed(
Catalog.Service,
Catalog.Service.of({
loader: () => Effect.die("unexpected catalog.loader"),
transform: () => Effect.die("unexpected catalog.transform"),
provider: {
get: () => Effect.die("unexpected provider.get"),
all: () => Effect.succeed([]),
@ -42,7 +42,6 @@ export const catalogLayer = Layer.succeed(
all: () => Effect.succeed([]),
available: () => Effect.succeed([]),
default: () => Effect.succeed(Option.none<ModelV2.Info>()),
setDefault: () => Effect.die("unexpected model.setDefault"),
small: () => Effect.succeed(Option.none<ModelV2.Info>()),
},
}),

View file

@ -22,8 +22,8 @@ describe("KiloPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(KiloPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const kilo = provider("kilo", {
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.kilo.ai/api/gateway" },
options: { headers: { Existing: "value" }, body: {}, aisdk: { provider: {}, request: {} } },
@ -48,8 +48,8 @@ describe("KiloPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(KiloPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("kilo", {
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.kilo.ai/api/gateway" },
})
@ -74,8 +74,8 @@ describe("KiloPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(KiloPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const kilo = provider("kilo", {
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.kilo.ai/api/gateway" },
})

View file

@ -22,8 +22,8 @@ describe("LLMGatewayPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(LLMGatewayPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const llmgateway = provider("llmgateway", {
enabled: { via: "env", name: "LLMGATEWAY_API_KEY" },
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.llmgateway.io/v1" },
@ -56,8 +56,8 @@ describe("LLMGatewayPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(LLMGatewayPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("llmgateway", {
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.llmgateway.io/v1" },
})

View file

@ -22,8 +22,8 @@ describe("NvidiaPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(NvidiaPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const nvidia = provider("nvidia", {
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://integrate.api.nvidia.com/v1" },
options: { headers: { Existing: "value" }, body: {}, aisdk: { provider: {}, request: {} } },
@ -49,8 +49,8 @@ describe("NvidiaPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(NvidiaPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("nvidia", {
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://integrate.api.nvidia.com/v1" },
options: { headers: {}, body: {}, aisdk: { provider: {}, request: {} } },
@ -74,8 +74,8 @@ describe("NvidiaPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(NvidiaPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("nvidia", {
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://integrate.api.nvidia.com/v1" },
options: {

View file

@ -77,8 +77,8 @@ describe("OpenAIPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(OpenAIPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("openai", { endpoint: { type: "aisdk", package: "@ai-sdk/openai" } })
catalog.provider.update(item.id, (draft) => {
draft.endpoint = item.endpoint
@ -96,8 +96,8 @@ describe("OpenAIPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(OpenAIPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("custom-openai")
catalog.provider.update(item.id, () => {})
catalog.model.update(item.id, ModelV2.ID.make("gpt-5-chat-latest"), () => {})

View file

@ -24,8 +24,8 @@ describe("OpencodePlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(OpencodePlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("opencode")
catalog.provider.update(item.id, () => {})
const paid = model("opencode", "paid", { cost: cost(1) })
@ -45,8 +45,8 @@ describe("OpencodePlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(OpencodePlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("opencode")
catalog.provider.update(item.id, () => {})
const free = model("opencode", "free", { cost: cost(0) })
@ -66,8 +66,8 @@ describe("OpencodePlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(OpencodePlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("opencode")
catalog.provider.update(item.id, () => {})
const outputOnly = model("opencode", "output-only", { cost: cost(0, 1) })
@ -87,8 +87,8 @@ describe("OpencodePlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(OpencodePlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("opencode")
catalog.provider.update(item.id, () => {})
const paid = model("opencode", "paid", { cost: cost(1) })
@ -108,8 +108,8 @@ describe("OpencodePlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(OpencodePlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("opencode", { env: ["CUSTOM_OPENCODE_API_KEY"] })
catalog.provider.update(item.id, (draft) => {
draft.env = [...item.env]
@ -131,8 +131,8 @@ describe("OpencodePlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(OpencodePlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("opencode", {
options: {
headers: {},
@ -163,8 +163,8 @@ describe("OpencodePlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(OpencodePlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("opencode", { enabled: { via: "account", service: "opencode" } })
catalog.provider.update(item.id, (draft) => {
draft.enabled = item.enabled
@ -186,8 +186,8 @@ describe("OpencodePlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(OpencodePlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("openai")
catalog.provider.update(item.id, () => {})
const paid = model("openai", "paid", { cost: cost(1) })
@ -206,8 +206,8 @@ describe("OpencodePlugin", () => {
const catalog = yield* Catalog.Service
const providerID = ProviderV2.ID.opencode
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
catalog.provider.update(providerID, () => {})
catalog.model.update(providerID, ModelV2.ID.make("cheap-mini"), (model) => {
model.capabilities.input = ["text"]

View file

@ -23,8 +23,8 @@ describe("OpenRouterPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(OpenRouterPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const openrouter = provider("openrouter", {
endpoint: { type: "aisdk", package: "@openrouter/ai-sdk-provider" },
options: { headers: { Existing: "value" }, body: {}, aisdk: { provider: {}, request: {} } },
@ -75,8 +75,8 @@ describe("OpenRouterPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(OpenRouterPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const openrouter = provider("openrouter", {
endpoint: { type: "aisdk", package: "@openrouter/ai-sdk-provider" },
})
@ -108,8 +108,8 @@ describe("OpenRouterPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(OpenRouterPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("custom-openrouter"), () => {})
catalog.model.update(ProviderV2.ID.make("custom-openrouter"), ModelV2.ID.make("gpt-5-chat-latest"), () => {})
})

View file

@ -12,8 +12,8 @@ describe("VercelPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(VercelPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("vercel", {
endpoint: { type: "aisdk", package: "@ai-sdk/vercel" },
options: { headers: { Existing: "1" }, body: {}, aisdk: { provider: {}, request: {} } },
@ -36,8 +36,8 @@ describe("VercelPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(VercelPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("vercel", { endpoint: { type: "aisdk", package: "@ai-sdk/vercel" } })
catalog.provider.update(item.id, (draft) => {
draft.endpoint = item.endpoint
@ -69,8 +69,8 @@ describe("VercelPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(VercelPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => catalog.provider.update(provider("gateway").id, () => {}))
const transform = yield* catalog.transform()
yield* transform((catalog) => catalog.provider.update(provider("gateway").id, () => {}))
expect((yield* catalog.provider.get(ProviderV2.ID.make("gateway"))).options.headers).toEqual({})
}),
)

View file

@ -22,8 +22,8 @@ describe("ZenmuxPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(ZenmuxPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("zenmux", {
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://zenmux.ai/api/v1" },
})
@ -42,8 +42,8 @@ describe("ZenmuxPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(ZenmuxPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("zenmux", {
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://zenmux.ai/api/v1" },
options: { headers: { Existing: "value" }, body: {}, aisdk: { provider: {}, request: {} } },
@ -67,8 +67,8 @@ describe("ZenmuxPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(ZenmuxPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("zenmux", {
endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://zenmux.ai/api/v1" },
options: {
@ -95,8 +95,8 @@ describe("ZenmuxPlugin", () => {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* plugin.add(ZenmuxPlugin)
const load = yield* catalog.loader()
yield* load((catalog) => {
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("openrouter", {
options: {
headers: { "HTTP-Referer": "https://example.com/", "X-Title": "custom-title" },