Преглед на файлове

feat(core): generate model variants

Dax Raad преди 2 месеца
родител
ревизия
42bb793574
променени са 3 файла, в които са добавени 129 реда и са изтрити 1 реда
  1. 3 1
      packages/core/src/plugin/internal.ts
  2. 39 0
      packages/core/src/plugin/variant.ts
  3. 87 0
      packages/core/test/plugin/variant.test.ts

+ 3 - 1
packages/core/src/plugin/internal.ts

@@ -29,6 +29,7 @@ import { CommandPlugin } from "./command"
 import { ModelsDevPlugin } from "./models-dev"
 import { ProviderPlugins } from "./provider"
 import { SkillPlugin } from "./skill"
+import { VariantPlugin } from "./variant"
 
 export type Requirements =
   | AgentV2.Service
@@ -107,12 +108,13 @@ export const locationLayer = Layer.effectDiscard(
       yield* add(CommandPlugin.Plugin)
       yield* add(SkillPlugin.Plugin)
       yield* add(ModelsDevPlugin)
-      yield* add(ConfigProviderPlugin.Plugin)
       yield* add(ConfigAgentPlugin.Plugin)
       yield* add(ConfigCommandPlugin.Plugin)
       yield* add(ConfigSkillPlugin.Plugin)
       for (const item of ProviderPlugins) yield* add(item)
       yield* add(ConfigExternalPlugin.Plugin)
+      yield* add(ConfigProviderPlugin.Plugin)
+      yield* add(VariantPlugin.Plugin)
     }).pipe(Effect.withSpan("PluginInternal.boot"), Effect.forkScoped({ startImmediately: true }))
   }),
 ).pipe(

+ 39 - 0
packages/core/src/plugin/variant.ts

@@ -0,0 +1,39 @@
+export * as VariantPlugin from "./variant"
+
+import type { ModelV2Info } from "@opencode-ai/sdk/v2/types"
+import { Effect } from "effect"
+import { define } from "./internal"
+
+export const Plugin = define({
+  id: "variant",
+  effect: Effect.fn(function* (ctx) {
+    yield* ctx.catalog.transform((catalog) => {
+      for (const record of catalog.provider.list()) {
+        for (const model of record.models.values()) {
+          catalog.model.update(model.providerID, model.id, (draft) => {
+            const generated = generate(draft)
+            if (generated.length === 0) return
+
+            const explicit = new Map(draft.variants.map((variant) => [variant.id, variant]))
+            const generatedIDs = new Set(generated.map((variant) => variant.id))
+            draft.variants = [
+              ...generated.map((variant) => explicit.get(variant.id) ?? variant),
+              ...draft.variants.filter((variant) => !generatedIDs.has(variant.id)),
+            ]
+          })
+        }
+      }
+    })
+  }),
+})
+
+export function generate(model: ModelV2Info): ModelV2Info["variants"] {
+  if (model.api.type !== "aisdk" || model.api.package !== "@ai-sdk/openai-compatible") return []
+  const ids = `${model.id} ${model.api.id}`.toLowerCase()
+  if (!["glm-5.2", "glm-5-2", "glm-5p2"].some((name) => ids.includes(name))) return []
+  return ["high", "max"].map((id) => ({
+    id,
+    headers: {},
+    body: { reasoning_effort: id },
+  }))
+}

+ 87 - 0
packages/core/test/plugin/variant.test.ts

@@ -0,0 +1,87 @@
+import { describe, expect } from "bun:test"
+import { Catalog } from "@opencode-ai/core/catalog"
+import { Credential } from "@opencode-ai/core/credential"
+import { EventV2 } from "@opencode-ai/core/event"
+import { Integration } from "@opencode-ai/core/integration"
+import { Location } from "@opencode-ai/core/location"
+import { ModelV2 } from "@opencode-ai/core/model"
+import { Policy } from "@opencode-ai/core/policy"
+import { VariantPlugin } from "@opencode-ai/core/plugin/variant"
+import { ProviderV2 } from "@opencode-ai/core/provider"
+import { AbsolutePath } from "@opencode-ai/core/schema"
+import { Effect, Layer } from "effect"
+import { location } from "../fixture/location"
+import { testEffect } from "../lib/effect"
+import { catalogHost, host } from "./host"
+
+const events = EventV2.defaultLayer
+const locationLayer = Layer.succeed(
+  Location.Service,
+  Location.Service.of(location({ directory: AbsolutePath.make(import.meta.dir) })),
+)
+const connections = Credential.defaultLayer.pipe(Layer.fresh)
+const integrations = Integration.locationLayer.pipe(Layer.provide(events), Layer.provide(connections))
+const catalog = Catalog.layer.pipe(
+  Layer.provide(
+    Layer.mergeAll(
+      events,
+      locationLayer,
+      Policy.layer.pipe(Layer.provide(locationLayer)),
+      connections,
+      integrations,
+    ),
+  ),
+)
+const it = testEffect(
+  Layer.mergeAll(catalog.pipe(Layer.provide(connections)), integrations, connections, events, locationLayer),
+)
+
+describe("VariantPlugin", () => {
+  it.effect("adds GLM 5.2 variants after catalog sources", () =>
+    Effect.gen(function* () {
+      const service = yield* Catalog.Service
+      yield* service.transform((catalog) => {
+        catalog.provider.update(ProviderV2.ID.opencode, (provider) => {
+          provider.api = { type: "aisdk", package: "@ai-sdk/openai-compatible" }
+        })
+        catalog.model.update(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2"), (model) => {
+          model.api = {
+            id: ModelV2.ID.make("glm-5.2"),
+            type: "aisdk",
+            package: "@ai-sdk/openai-compatible",
+          }
+        })
+      })
+      yield* VariantPlugin.Plugin.effect(host({ catalog: catalogHost(service) }))
+
+      expect((yield* service.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2")))?.variants).toEqual([
+        expect.objectContaining({ id: "high", body: { reasoning_effort: "high" } }),
+        expect.objectContaining({ id: "max", body: { reasoning_effort: "max" } }),
+      ])
+    }),
+  )
+
+  it.effect("keeps explicit variants over generated defaults", () =>
+    Effect.gen(function* () {
+      const service = yield* Catalog.Service
+      yield* service.transform((catalog) => {
+        catalog.model.update(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2"), (model) => {
+          model.api = {
+            id: ModelV2.ID.make("glm-5.2"),
+            type: "aisdk",
+            package: "@ai-sdk/openai-compatible",
+          }
+          model.variants = [
+            { id: ModelV2.VariantID.make("high"), headers: { custom: "true" }, body: {} },
+          ]
+        })
+      })
+      yield* VariantPlugin.Plugin.effect(host({ catalog: catalogHost(service) }))
+
+      expect((yield* service.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2")))?.variants).toEqual([
+        expect.objectContaining({ id: "high", headers: { custom: "true" } }),
+        expect.objectContaining({ id: "max", body: { reasoning_effort: "max" } }),
+      ])
+    }),
+  )
+})