|
|
@@ -1,18 +1,17 @@
|
|
|
import { describe, expect } from "bun:test"
|
|
|
-import { DateTime, Effect, Fiber, Layer, Option, Stream } from "effect"
|
|
|
+import { Effect, Fiber, Layer, Stream } from "effect"
|
|
|
import { Catalog } from "@opencode-ai/core/catalog"
|
|
|
import { Integration } from "@opencode-ai/core/integration"
|
|
|
import { Credential } from "@opencode-ai/core/credential"
|
|
|
import { EventV2 } from "@opencode-ai/core/event"
|
|
|
import { Location } from "@opencode-ai/core/location"
|
|
|
import { ModelV2 } from "@opencode-ai/core/model"
|
|
|
-import { PluginV2 } from "@opencode-ai/core/plugin"
|
|
|
import { Policy } from "@opencode-ai/core/policy"
|
|
|
-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 { testEffect } from "./lib/effect"
|
|
|
+import { required } from "./plugin/provider-helper"
|
|
|
|
|
|
const locationLayer = Layer.succeed(
|
|
|
Location.Service,
|
|
|
@@ -41,7 +40,7 @@ describe("CatalogV2", () => {
|
|
|
.pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped)
|
|
|
yield* Effect.yieldNow
|
|
|
|
|
|
- yield* (yield* catalog.transform())((editor) => editor.provider.update(ProviderV2.ID.make("test"), () => {}))
|
|
|
+ yield* catalog.transform((editor) => editor.provider.update(ProviderV2.ID.make("test"), () => {}))
|
|
|
|
|
|
expect((yield* Fiber.join(updated)).length).toBe(1)
|
|
|
}),
|
|
|
@@ -76,14 +75,13 @@ describe("CatalogV2", () => {
|
|
|
|
|
|
return Effect.gen(function* () {
|
|
|
const catalog = yield* Catalog.Service
|
|
|
- const transform = yield* catalog.transform()
|
|
|
- yield* transform((editor) => editor.provider.update(ProviderV2.ID.make("test"), () => {}))
|
|
|
+ yield* catalog.transform((editor) => editor.provider.update(ProviderV2.ID.make("test"), () => {}))
|
|
|
|
|
|
expect((yield* catalog.provider.available()).map((provider) => provider.id)).toEqual([ProviderV2.ID.make("test")])
|
|
|
- expect((yield* catalog.provider.get(ProviderV2.ID.make("test"))).request.body).toEqual({})
|
|
|
+ expect(required(yield* catalog.provider.get(ProviderV2.ID.make("test"))).request.body).toEqual({})
|
|
|
active = second
|
|
|
expect((yield* catalog.provider.available()).map((provider) => provider.id)).toEqual([ProviderV2.ID.make("test")])
|
|
|
- expect((yield* catalog.provider.get(ProviderV2.ID.make("test"))).request.body).toEqual({})
|
|
|
+ expect(required(yield* catalog.provider.get(ProviderV2.ID.make("test"))).request.body).toEqual({})
|
|
|
}).pipe(Effect.provide(layer))
|
|
|
})
|
|
|
|
|
|
@@ -99,13 +97,13 @@ describe("CatalogV2", () => {
|
|
|
const catalog = yield* Catalog.Service
|
|
|
const integrations = yield* Integration.Service
|
|
|
const providerID = ProviderV2.ID.make("test")
|
|
|
- yield* integrations.update((editor) =>
|
|
|
+ yield* integrations.transform((editor) =>
|
|
|
editor.method.update({
|
|
|
integrationID: Integration.ID.make(providerID),
|
|
|
method: { type: "env", names: ["CATALOG_TEST_API_KEY"] },
|
|
|
}),
|
|
|
)
|
|
|
- yield* (yield* catalog.transform())((editor) => editor.provider.update(providerID, () => {}))
|
|
|
+ yield* catalog.transform((editor) => editor.provider.update(providerID, () => {}))
|
|
|
|
|
|
expect((yield* catalog.provider.available()).map((provider) => provider.id)).toContain(providerID)
|
|
|
}),
|
|
|
@@ -121,9 +119,7 @@ describe("CatalogV2", () => {
|
|
|
Effect.gen(function* () {
|
|
|
const catalog = yield* Catalog.Service
|
|
|
const providerID = ProviderV2.ID.make("test")
|
|
|
- const transform = yield* catalog.transform()
|
|
|
-
|
|
|
- yield* transform((catalog) =>
|
|
|
+ yield* catalog.transform((catalog) =>
|
|
|
catalog.provider.update(providerID, (provider) => {
|
|
|
provider.api = {
|
|
|
type: "aisdk",
|
|
|
@@ -134,7 +130,7 @@ describe("CatalogV2", () => {
|
|
|
}),
|
|
|
)
|
|
|
|
|
|
- expect((yield* catalog.provider.get(providerID)).api).toEqual({
|
|
|
+ expect(required(yield* catalog.provider.get(providerID)).api).toEqual({
|
|
|
type: "aisdk",
|
|
|
package: "@ai-sdk/openai-compatible",
|
|
|
url: "https://override.example.com",
|
|
|
@@ -147,9 +143,7 @@ describe("CatalogV2", () => {
|
|
|
const catalog = yield* Catalog.Service
|
|
|
const providerID = ProviderV2.ID.make("test")
|
|
|
const modelID = ModelV2.ID.make("model")
|
|
|
- const transform = yield* catalog.transform()
|
|
|
-
|
|
|
- yield* transform((catalog) => {
|
|
|
+ yield* catalog.transform((catalog) => {
|
|
|
catalog.provider.update(providerID, (provider) => {
|
|
|
provider.api = {
|
|
|
type: "aisdk",
|
|
|
@@ -168,7 +162,7 @@ describe("CatalogV2", () => {
|
|
|
})
|
|
|
})
|
|
|
|
|
|
- expect((yield* catalog.model.get(providerID, modelID)).api).toEqual({
|
|
|
+ expect(required(yield* catalog.model.get(providerID, modelID)).api).toEqual({
|
|
|
id: modelID,
|
|
|
type: "aisdk",
|
|
|
package: "@ai-sdk/openai-compatible",
|
|
|
@@ -183,9 +177,7 @@ describe("CatalogV2", () => {
|
|
|
const catalog = yield* Catalog.Service
|
|
|
const providerID = ProviderV2.ID.make("test")
|
|
|
const modelID = ModelV2.ID.make("model")
|
|
|
- const transform = yield* catalog.transform()
|
|
|
-
|
|
|
- yield* transform((catalog) => {
|
|
|
+ yield* catalog.transform((catalog) => {
|
|
|
catalog.provider.update(providerID, (provider) => {
|
|
|
provider.api = {
|
|
|
type: "aisdk",
|
|
|
@@ -196,7 +188,7 @@ describe("CatalogV2", () => {
|
|
|
catalog.model.update(providerID, modelID, () => {})
|
|
|
})
|
|
|
|
|
|
- expect((yield* catalog.model.get(providerID, modelID)).api).toEqual({
|
|
|
+ expect(required(yield* catalog.model.get(providerID, modelID)).api).toEqual({
|
|
|
id: modelID,
|
|
|
type: "aisdk",
|
|
|
package: "@ai-sdk/openai-compatible",
|
|
|
@@ -205,106 +197,12 @@ describe("CatalogV2", () => {
|
|
|
}),
|
|
|
)
|
|
|
|
|
|
- it.effect("runs catalog transform hooks after baseURL is normalized", () =>
|
|
|
- Effect.gen(function* () {
|
|
|
- const catalog = yield* Catalog.Service
|
|
|
- const plugin = yield* PluginV2.Service
|
|
|
- const providerID = ProviderV2.ID.make("test")
|
|
|
- const seen: unknown[] = []
|
|
|
- const transform = yield* catalog.transform()
|
|
|
-
|
|
|
- yield* plugin.add({
|
|
|
- id: PluginV2.ID.make("test"),
|
|
|
- effect: Effect.succeed({
|
|
|
- "catalog.transform": (evt) =>
|
|
|
- Effect.sync(() => {
|
|
|
- const item = evt.provider.get(providerID)
|
|
|
- if (!item) return
|
|
|
- seen.push(item.provider.api.type)
|
|
|
- if (item?.provider.api.type === "aisdk") seen.push(item.provider.api.url)
|
|
|
- seen.push(item?.provider.request.body.baseURL)
|
|
|
- }),
|
|
|
- }),
|
|
|
- })
|
|
|
- yield* transform((catalog) =>
|
|
|
- catalog.provider.update(providerID, (provider) => {
|
|
|
- provider.api = { type: "aisdk", package: "@ai-sdk/openai-compatible" }
|
|
|
- provider.request.body.baseURL = "https://provider.example.com"
|
|
|
- }),
|
|
|
- )
|
|
|
-
|
|
|
- expect(seen).toEqual(["aisdk", "https://provider.example.com", undefined])
|
|
|
- }),
|
|
|
- )
|
|
|
-
|
|
|
- it.effect("runs catalog transform when a plugin is added", () =>
|
|
|
- Effect.gen(function* () {
|
|
|
- const catalog = yield* Catalog.Service
|
|
|
- const plugin = yield* PluginV2.Service
|
|
|
- const providerID = ProviderV2.ID.make("test")
|
|
|
- const transform = yield* catalog.transform()
|
|
|
-
|
|
|
- yield* transform((catalog) =>
|
|
|
- catalog.provider.update(providerID, (provider) => {
|
|
|
- provider.name = "Before"
|
|
|
- }),
|
|
|
- )
|
|
|
- yield* plugin.add({
|
|
|
- id: PluginV2.ID.make("test-transform"),
|
|
|
- effect: Effect.succeed({
|
|
|
- "catalog.transform": (evt) =>
|
|
|
- Effect.sync(() =>
|
|
|
- evt.provider.update(providerID, (provider) => {
|
|
|
- provider.name = "After"
|
|
|
- }),
|
|
|
- ),
|
|
|
- }),
|
|
|
- })
|
|
|
- yield* Effect.yieldNow
|
|
|
-
|
|
|
- expect((yield* catalog.provider.get(providerID)).name).toBe("After")
|
|
|
- }),
|
|
|
- )
|
|
|
-
|
|
|
- it.effect("ignores plugin additions from another location", () =>
|
|
|
- Effect.gen(function* () {
|
|
|
- const events = yield* EventV2.Service
|
|
|
- const plugin = yield* PluginV2.Service
|
|
|
- let invoked = 0
|
|
|
-
|
|
|
- yield* plugin.add({
|
|
|
- id: PluginV2.ID.make("test-transform"),
|
|
|
- effect: Effect.succeed({
|
|
|
- "catalog.transform": () => Effect.sync(() => invoked++),
|
|
|
- }),
|
|
|
- })
|
|
|
- yield* Effect.yieldNow
|
|
|
- expect(invoked).toBe(1)
|
|
|
-
|
|
|
- yield* events.publish(
|
|
|
- PluginV2.Event.Added,
|
|
|
- { id: PluginV2.ID.make("test-transform") },
|
|
|
- {
|
|
|
- location: new Location.Info({
|
|
|
- directory: AbsolutePath.make("other"),
|
|
|
- project: { id: Project.ID.global, directory: AbsolutePath.make("other") },
|
|
|
- }),
|
|
|
- },
|
|
|
- )
|
|
|
- yield* Effect.yieldNow
|
|
|
-
|
|
|
- expect(invoked).toBe(1)
|
|
|
- }),
|
|
|
- )
|
|
|
-
|
|
|
it.effect("resolves provider and model request merges", () =>
|
|
|
Effect.gen(function* () {
|
|
|
const catalog = yield* Catalog.Service
|
|
|
const providerID = ProviderV2.ID.make("test")
|
|
|
const modelID = ModelV2.ID.make("model")
|
|
|
- const transform = yield* catalog.transform()
|
|
|
-
|
|
|
- yield* transform((catalog) => {
|
|
|
+ yield* catalog.transform((catalog) => {
|
|
|
catalog.provider.update(providerID, (provider) => {
|
|
|
provider.request.headers.provider = "provider"
|
|
|
provider.request.headers.shared = "provider"
|
|
|
@@ -321,7 +219,7 @@ describe("CatalogV2", () => {
|
|
|
})
|
|
|
})
|
|
|
|
|
|
- const model = yield* catalog.model.get(providerID, modelID)
|
|
|
+ const model = required(yield* catalog.model.get(providerID, modelID))
|
|
|
expect(model.request.headers).toEqual({ provider: "provider", shared: "model", model: "model" })
|
|
|
expect(model.request.body).toEqual({ provider: true, model: true, request: true })
|
|
|
expect(model.request.options).toEqual({ shared: "model", model: true })
|
|
|
@@ -332,19 +230,17 @@ describe("CatalogV2", () => {
|
|
|
Effect.gen(function* () {
|
|
|
const catalog = yield* Catalog.Service
|
|
|
const providerID = ProviderV2.ID.make("test")
|
|
|
- const transform = yield* catalog.transform()
|
|
|
-
|
|
|
- yield* transform((catalog) => {
|
|
|
+ yield* catalog.transform((catalog) => {
|
|
|
catalog.provider.update(providerID, () => {})
|
|
|
catalog.model.update(providerID, ModelV2.ID.make("old"), (model) => {
|
|
|
- model.time.released = DateTime.makeUnsafe(1000)
|
|
|
+ model.time.released = 1000
|
|
|
})
|
|
|
catalog.model.update(providerID, ModelV2.ID.make("new"), (model) => {
|
|
|
- model.time.released = DateTime.makeUnsafe(2000)
|
|
|
+ model.time.released = 2000
|
|
|
})
|
|
|
})
|
|
|
|
|
|
- expect(Option.getOrUndefined(yield* catalog.model.default())?.id).toMatch("new")
|
|
|
+ expect((yield* catalog.model.default())?.id).toMatch("new")
|
|
|
}),
|
|
|
)
|
|
|
|
|
|
@@ -354,26 +250,26 @@ describe("CatalogV2", () => {
|
|
|
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) => {
|
|
|
+ const models = (catalog: Catalog.Draft) => {
|
|
|
catalog.provider.update(providerID, () => {})
|
|
|
catalog.model.update(providerID, old, (model) => {
|
|
|
- model.time.released = DateTime.makeUnsafe(1000)
|
|
|
+ model.time.released = 1000
|
|
|
})
|
|
|
catalog.model.update(providerID, newest, (model) => {
|
|
|
- model.time.released = DateTime.makeUnsafe(2000)
|
|
|
+ model.time.released = 2000
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- yield* transform((catalog) => {
|
|
|
+ let configured = true
|
|
|
+ yield* catalog.transform((catalog) => {
|
|
|
models(catalog)
|
|
|
- catalog.model.default.set(providerID, old)
|
|
|
+ if (configured) catalog.model.default.set(providerID, old)
|
|
|
})
|
|
|
- expect(Option.getOrUndefined(yield* catalog.model.default())?.id).toBe(old)
|
|
|
+ expect((yield* catalog.model.default())?.id).toBe(old)
|
|
|
|
|
|
- yield* transform(models)
|
|
|
- expect(Option.getOrUndefined(yield* catalog.model.default())?.id).toBe(newest)
|
|
|
+ configured = false
|
|
|
+ yield* catalog.rebuild()
|
|
|
+ expect((yield* catalog.model.default())?.id).toBe(newest)
|
|
|
}),
|
|
|
)
|
|
|
|
|
|
@@ -384,9 +280,7 @@ describe("CatalogV2", () => {
|
|
|
const enabledProvider = ProviderV2.ID.make("enabled")
|
|
|
const disabledModel = ModelV2.ID.make("configured")
|
|
|
const fallbackModel = ModelV2.ID.make("fallback")
|
|
|
- const transform = yield* catalog.transform()
|
|
|
-
|
|
|
- yield* transform((catalog) => {
|
|
|
+ yield* catalog.transform((catalog) => {
|
|
|
catalog.provider.update(disabledProvider, (provider) => {
|
|
|
provider.disabled = true
|
|
|
})
|
|
|
@@ -396,7 +290,7 @@ describe("CatalogV2", () => {
|
|
|
catalog.model.default.set(disabledProvider, disabledModel)
|
|
|
})
|
|
|
|
|
|
- expect(Option.getOrUndefined(yield* catalog.model.default())).toMatchObject({
|
|
|
+ expect(yield* catalog.model.default()).toMatchObject({
|
|
|
providerID: enabledProvider,
|
|
|
id: fallbackModel,
|
|
|
})
|
|
|
@@ -407,25 +301,23 @@ describe("CatalogV2", () => {
|
|
|
Effect.gen(function* () {
|
|
|
const catalog = yield* Catalog.Service
|
|
|
const providerID = ProviderV2.ID.make("test")
|
|
|
- const transform = yield* catalog.transform()
|
|
|
-
|
|
|
- yield* transform((catalog) => {
|
|
|
+ yield* catalog.transform((catalog) => {
|
|
|
catalog.provider.update(providerID, () => {})
|
|
|
catalog.model.update(providerID, ModelV2.ID.make("cheap-large"), (model) => {
|
|
|
model.capabilities.input = ["text"]
|
|
|
model.capabilities.output = ["text"]
|
|
|
model.cost = [{ input: 1, output: 1, cache: { read: 0, write: 0 } }]
|
|
|
- model.time.released = DateTime.makeUnsafe(Date.now())
|
|
|
+ model.time.released = Date.now()
|
|
|
})
|
|
|
catalog.model.update(providerID, ModelV2.ID.make("expensive-mini"), (model) => {
|
|
|
model.capabilities.input = ["text"]
|
|
|
model.capabilities.output = ["text"]
|
|
|
model.cost = [{ input: 10, output: 10, cache: { read: 0, write: 0 } }]
|
|
|
- model.time.released = DateTime.makeUnsafe(Date.now())
|
|
|
+ model.time.released = Date.now()
|
|
|
})
|
|
|
})
|
|
|
|
|
|
- expect(Option.getOrUndefined(yield* catalog.model.small(providerID))?.id).toMatch("expensive-mini")
|
|
|
+ expect((yield* catalog.model.small(providerID))?.id).toMatch("expensive-mini")
|
|
|
}),
|
|
|
)
|
|
|
|
|
|
@@ -434,17 +326,15 @@ describe("CatalogV2", () => {
|
|
|
const catalog = yield* Catalog.Service
|
|
|
const policy = yield* Policy.Service
|
|
|
const providerID = ProviderV2.ID.make("blocked")
|
|
|
- const transform = yield* catalog.transform()
|
|
|
-
|
|
|
yield* policy.load([new Policy.Info({ effect: "deny", action: "provider.use", resource: "blocked" })])
|
|
|
- yield* transform((catalog) => {
|
|
|
+ yield* catalog.transform((catalog) => {
|
|
|
catalog.provider.update(providerID, () => {})
|
|
|
catalog.model.update(providerID, ModelV2.ID.make("model"), () => {})
|
|
|
})
|
|
|
|
|
|
expect(yield* catalog.provider.all()).toEqual([])
|
|
|
expect(yield* catalog.model.all()).toEqual([])
|
|
|
- expect(yield* catalog.provider.get(providerID).pipe(Effect.option)).toEqual(Option.none())
|
|
|
+ expect(yield* catalog.provider.get(providerID)).toBeUndefined()
|
|
|
}),
|
|
|
)
|
|
|
})
|