瀏覽代碼

refactor(core): move models.dev into core (#27347)

Dax 3 月之前
父節點
當前提交
16c457e712
共有 68 個文件被更改,包括 344 次插入153 次删除
  1. 2 0
      packages/core/src/catalog.ts
  2. 12 0
      packages/core/src/instance-layer.ts
  3. 10 0
      packages/core/src/instance.ts
  4. 2 0
      packages/core/src/models-snapshot.d.ts
  5. 2 0
      packages/core/src/models-snapshot.js
  6. 22 14
      packages/core/src/models.ts
  7. 66 0
      packages/core/src/plugin/boot.ts
  8. 5 5
      packages/core/src/plugin/models-dev.ts
  9. 4 2
      packages/core/test/catalog.test.ts
  10. 6 8
      packages/core/test/models.test.ts
  11. 0 0
      packages/core/test/plugin/fixtures/provider-factory.ts
  12. 0 0
      packages/core/test/plugin/provider-alibaba.test.ts
  13. 0 0
      packages/core/test/plugin/provider-amazon-bedrock.test.ts
  14. 0 0
      packages/core/test/plugin/provider-anthropic.test.ts
  15. 0 0
      packages/core/test/plugin/provider-azure-cognitive-services.test.ts
  16. 1 1
      packages/core/test/plugin/provider-azure.test.ts
  17. 0 0
      packages/core/test/plugin/provider-cerebras.test.ts
  18. 0 0
      packages/core/test/plugin/provider-cloudflare-ai-gateway.test.ts
  19. 1 1
      packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts
  20. 0 0
      packages/core/test/plugin/provider-cohere.test.ts
  21. 1 1
      packages/core/test/plugin/provider-deepinfra.test.ts
  22. 1 1
      packages/core/test/plugin/provider-dynamic.test.ts
  23. 0 0
      packages/core/test/plugin/provider-gateway.test.ts
  24. 1 1
      packages/core/test/plugin/provider-github-copilot.test.ts
  25. 1 1
      packages/core/test/plugin/provider-gitlab.test.ts
  26. 0 0
      packages/core/test/plugin/provider-google-vertex-anthropic.test.ts
  27. 0 0
      packages/core/test/plugin/provider-google-vertex.test.ts
  28. 1 1
      packages/core/test/plugin/provider-google.test.ts
  29. 1 1
      packages/core/test/plugin/provider-groq.test.ts
  30. 1 1
      packages/core/test/plugin/provider-helper.ts
  31. 0 0
      packages/core/test/plugin/provider-kilo.test.ts
  32. 0 0
      packages/core/test/plugin/provider-llmgateway.test.ts
  33. 0 0
      packages/core/test/plugin/provider-mistral.test.ts
  34. 0 0
      packages/core/test/plugin/provider-nvidia.test.ts
  35. 0 0
      packages/core/test/plugin/provider-openai-compatible.test.ts
  36. 0 0
      packages/core/test/plugin/provider-openai.test.ts
  37. 4 2
      packages/core/test/plugin/provider-opencode.test.ts
  38. 0 0
      packages/core/test/plugin/provider-openrouter.test.ts
  39. 0 0
      packages/core/test/plugin/provider-perplexity.test.ts
  40. 0 0
      packages/core/test/plugin/provider-sap-ai-core.test.ts
  41. 0 0
      packages/core/test/plugin/provider-togetherai.test.ts
  42. 0 0
      packages/core/test/plugin/provider-venice.test.ts
  43. 0 0
      packages/core/test/plugin/provider-vercel.test.ts
  44. 1 1
      packages/core/test/plugin/provider-xai.test.ts
  45. 0 0
      packages/core/test/plugin/provider-zenmux.test.ts
  46. 2 2
      packages/opencode/script/generate.ts
  47. 16 10
      packages/opencode/src/cli/cmd/debug/v2.ts
  48. 1 1
      packages/opencode/src/cli/cmd/github.ts
  49. 1 1
      packages/opencode/src/cli/cmd/models.ts
  50. 1 1
      packages/opencode/src/cli/cmd/providers.ts
  51. 1 1
      packages/opencode/src/effect/app-runtime.ts
  52. 1 2
      packages/opencode/src/provider/model-status.ts
  53. 1 1
      packages/opencode/src/provider/provider.ts
  54. 1 1
      packages/opencode/src/provider/transform.ts
  55. 59 0
      packages/opencode/src/server/routes/instance/httpapi/groups/v2/instance.ts
  56. 4 1
      packages/opencode/src/server/routes/instance/httpapi/groups/v2/model.ts
  57. 6 2
      packages/opencode/src/server/routes/instance/httpapi/groups/v2/provider.ts
  58. 1 1
      packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts
  59. 2 2
      packages/opencode/src/server/routes/instance/httpapi/handlers/v2.ts
  60. 10 3
      packages/opencode/src/server/routes/instance/httpapi/handlers/v2/model.ts
  61. 13 3
      packages/opencode/src/server/routes/instance/httpapi/handlers/v2/provider.ts
  62. 1 1
      packages/opencode/src/server/routes/instance/httpapi/server.ts
  63. 0 50
      packages/opencode/src/v2/plugin-boot.ts
  64. 1 1
      packages/opencode/test/provider/model-status.test.ts
  65. 1 1
      packages/opencode/test/provider/provider.test.ts
  66. 1 1
      packages/opencode/test/session/llm.test.ts
  67. 37 3
      packages/sdk/js/src/v2/gen/sdk.gen.ts
  68. 38 23
      packages/sdk/js/src/v2/gen/types.gen.ts

+ 2 - 0
packages/core/src/catalog.ts

@@ -5,6 +5,7 @@ import { produce, type Draft } from "immer"
 import { ModelV2 } from "./model"
 import { PluginV2 } from "./plugin"
 import { ProviderV2 } from "./provider"
+import { Instance } from "./instance"
 
 type ProviderRecord = {
   provider: ProviderV2.Info
@@ -56,6 +57,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/v2
 export const layer = Layer.effect(
   Service,
   Effect.gen(function* () {
+    yield* Instance.Service
     let records = HashMap.empty<ProviderV2.ID, ProviderRecord>()
     let defaultModel: { providerID: ProviderV2.ID; modelID: ModelV2.ID } | undefined
     const plugin = yield* PluginV2.Service

+ 12 - 0
packages/core/src/instance-layer.ts

@@ -0,0 +1,12 @@
+import { Layer, LayerMap } from "effect"
+import { Instance } from "./instance"
+import { Catalog } from "./catalog"
+import { PluginBoot } from "./plugin/boot"
+
+export class InstanceServiceMap extends LayerMap.Service<InstanceServiceMap>()("@opencode/example/InstanceServiceMap", {
+  lookup: (ref: Instance.Ref) => {
+    const instance = Layer.succeed(Instance.Service, Instance.Service.of(ref))
+    return Layer.mergeAll(Catalog.defaultLayer, PluginBoot.defaultLayer).pipe(Layer.provide(instance))
+  },
+  idleTimeToLive: "5 minutes",
+}) {}

+ 10 - 0
packages/core/src/instance.ts

@@ -0,0 +1,10 @@
+import { Context } from "effect"
+
+export * as Instance from "./instance"
+
+export type Ref = {
+  readonly directory: string
+  readonly workspaceID?: string
+}
+
+export class Service extends Context.Service<Service, Ref>()("@opencode/Instance") {}

+ 2 - 0
packages/core/src/models-snapshot.d.ts

@@ -0,0 +1,2 @@
+// Auto-generated by build.ts - do not edit
+export declare const snapshot: Record<string, unknown>

文件差異過大導致無法顯示
+ 2 - 0
packages/core/src/models-snapshot.js


+ 22 - 14
packages/opencode/src/provider/models.ts → packages/core/src/models.ts

@@ -1,15 +1,17 @@
-import { Global } from "@opencode-ai/core/global"
 import path from "path"
 import { Context, Duration, Effect, Layer, Option, Schedule, Schema } from "effect"
 import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http"
-import { Installation } from "../installation"
-import { Flag } from "@opencode-ai/core/flag/flag"
-import { Flock } from "@opencode-ai/core/util/flock"
-import { Hash } from "@opencode-ai/core/util/hash"
-import { AppFileSystem } from "@opencode-ai/core/filesystem"
-import { withTransientReadRetry } from "@/util/effect-http-client"
-import { CatalogModelStatus } from "./model-status"
-import { RuntimeFlags } from "@/effect/runtime-flags"
+import { Global } from "./global"
+import { Flag } from "./flag/flag"
+import { Flock } from "./util/flock"
+import { Hash } from "./util/hash"
+import { AppFileSystem } from "./filesystem"
+import { InstallationChannel, InstallationVersion } from "./installation/version"
+
+export const CatalogModelStatus = Schema.Literals(["alpha", "beta", "deprecated"])
+export type CatalogModelStatus = typeof CatalogModelStatus.Type
+
+const USER_AGENT = `opencode/${InstallationChannel}/${InstallationVersion}/${Flag.OPENCODE_CLIENT}`
 
 const CostTier = Schema.Struct({
   input: Schema.Finite,
@@ -110,14 +112,21 @@ export interface Interface {
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/ModelsDev") {}
 
-type Requirements = AppFileSystem.Service | HttpClient.HttpClient | RuntimeFlags.Service
+type Requirements = AppFileSystem.Service | HttpClient.HttpClient
 
 export const layer: Layer.Layer<Service, never, Requirements> = Layer.effect(
   Service,
   Effect.gen(function* () {
     const fs = yield* AppFileSystem.Service
-    const http = HttpClient.filterStatusOk(withTransientReadRetry(yield* HttpClient.HttpClient))
-    const flags = yield* RuntimeFlags.Service
+    const http = HttpClient.filterStatusOk(
+      (yield* HttpClient.HttpClient).pipe(
+        HttpClient.retryTransient({
+          retryOn: "errors-and-responses",
+          times: 2,
+          schedule: Schedule.exponential(200).pipe(Schedule.jittered),
+        }),
+      ),
+    )
 
     const source = Flag.OPENCODE_MODELS_URL || "https://models.dev"
     const filepath = path.join(
@@ -136,7 +145,7 @@ export const layer: Layer.Layer<Service, never, Requirements> = Layer.effect(
 
     const fetchApi = Effect.fn("ModelsDev.fetchApi")(function* () {
       return yield* HttpClientRequest.get(`${source}/api.json`).pipe(
-        HttpClientRequest.setHeader("User-Agent", Installation.userAgent(flags.client)),
+        HttpClientRequest.setHeader("User-Agent", USER_AGENT),
         http.execute,
         Effect.flatMap((res) => res.text),
         Effect.timeout("10 seconds"),
@@ -212,7 +221,6 @@ export const layer: Layer.Layer<Service, never, Requirements> = Layer.effect(
 export const defaultLayer: Layer.Layer<Service> = layer.pipe(
   Layer.provide(FetchHttpClient.layer),
   Layer.provide(AppFileSystem.defaultLayer),
-  Layer.provide(RuntimeFlags.defaultLayer),
 )
 
 export * as ModelsDev from "./models"

+ 66 - 0
packages/core/src/plugin/boot.ts

@@ -0,0 +1,66 @@
+export * as PluginBoot from "./boot"
+
+import { Context, Deferred, Effect, Layer } from "effect"
+import { AuthV2 } from "../auth"
+import { Catalog } from "../catalog"
+import { Npm } from "../npm"
+import { PluginV2 } from "../plugin"
+import { AuthPlugin } from "./auth"
+import { EnvPlugin } from "./env"
+import { ModelsDevPlugin } from "./models-dev"
+import { ProviderPlugins } from "./provider"
+
+type Plugin = {
+  id: PluginV2.ID
+  effect: Effect.Effect<PluginV2.HookFunctions | void, never, Catalog.Service | AuthV2.Service | Npm.Service>
+}
+
+export interface Interface {
+  readonly wait: () => Effect.Effect<void>
+}
+
+export class Service extends Context.Service<Service, Interface>()("@opencode/v2/PluginBoot") {}
+
+export const layer: Layer.Layer<Service, never, Catalog.Service | PluginV2.Service | AuthV2.Service | Npm.Service> = Layer.effect(
+  Service,
+  Effect.gen(function* () {
+    const catalog = yield* Catalog.Service
+    const plugin = yield* PluginV2.Service
+    const auth = yield* AuthV2.Service
+    const npm = yield* Npm.Service
+    const done = yield* Deferred.make<void>()
+
+    const add = Effect.fn("PluginBoot.add")(function* (input: Plugin) {
+      yield* plugin.add({
+        id: input.id,
+        effect: input.effect.pipe(
+          Effect.provideService(Catalog.Service, catalog),
+          Effect.provideService(AuthV2.Service, auth),
+          Effect.provideService(Npm.Service, npm),
+        ),
+      })
+    })
+
+    const boot = Effect.gen(function* () {
+      yield* add(EnvPlugin)
+      yield* add(AuthPlugin)
+      for (const item of ProviderPlugins) {
+        yield* add(item)
+      }
+      yield* add(ModelsDevPlugin)
+    }).pipe(Effect.withSpan("PluginBoot.boot"))
+
+    yield* boot.pipe(Effect.exit, Effect.flatMap((exit) => Deferred.done(done, exit)), Effect.forkScoped)
+
+    return Service.of({
+      wait: () => Deferred.await(done),
+    })
+  }),
+)
+
+export const defaultLayer = layer.pipe(
+  Layer.provide(Catalog.defaultLayer),
+  Layer.provide(PluginV2.defaultLayer),
+  Layer.provide(Layer.orDie(AuthV2.defaultLayer)),
+  Layer.provide(Npm.defaultLayer),
+)

+ 5 - 5
packages/opencode/src/v2/plugin/models-dev.ts → packages/core/src/plugin/models-dev.ts

@@ -1,9 +1,9 @@
 import { DateTime, Effect } from "effect"
-import { Catalog } from "@opencode-ai/core/catalog"
-import { ModelV2 } from "@opencode-ai/core/model"
-import { ProviderV2 } from "@opencode-ai/core/provider"
-import { ModelsDev } from "@/provider/models"
-import { PluginV2 } from "@opencode-ai/core/plugin"
+import { Catalog } from "../catalog"
+import { ModelV2 } from "../model"
+import { ModelsDev } from "../models"
+import { PluginV2 } from "../plugin"
+import { ProviderV2 } from "../provider"
 
 function released(date: string) {
   const time = Date.parse(date)

+ 4 - 2
packages/core/test/v2/catalog.test.ts → packages/core/test/catalog.test.ts

@@ -1,12 +1,14 @@
 import { describe, expect } from "bun:test"
 import { DateTime, Effect, Layer, Option } from "effect"
 import { Catalog } from "@opencode-ai/core/catalog"
+import { Instance } from "@opencode-ai/core/instance"
 import { ModelV2 } from "@opencode-ai/core/model"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { ProviderV2 } from "@opencode-ai/core/provider"
-import { testEffect } from "../lib/effect"
+import { testEffect } from "./lib/effect"
 
-const it = testEffect(Catalog.layer.pipe(Layer.provideMerge(PluginV2.defaultLayer)))
+const instanceLayer = Layer.succeed(Instance.Service, Instance.Service.of({ directory: "test" }))
+const it = testEffect(Catalog.layer.pipe(Layer.provideMerge(PluginV2.defaultLayer), Layer.provide(instanceLayer)))
 
 describe("CatalogV2", () => {
   it.effect("normalizes provider baseURL into endpoint url", () =>

+ 6 - 8
packages/opencode/test/provider/models.test.ts → packages/core/test/models.test.ts

@@ -4,11 +4,10 @@ import { HttpClient, HttpClientResponse } from "effect/unstable/http"
 import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import { Flag } from "@opencode-ai/core/flag/flag"
 import { Global } from "@opencode-ai/core/global"
-import { ModelsDev } from "../../src/provider/models"
-import { it } from "../lib/effect"
+import { ModelsDev } from "@opencode-ai/core/models"
+import { it } from "./lib/effect"
 import { rm, writeFile, utimes, mkdir } from "fs/promises"
 import path from "path"
-import { RuntimeFlags } from "@/effect/runtime-flags"
 
 // test/preload.ts pins OPENCODE_MODELS_PATH to a fixture so other tests can
 // resolve providers without network. These tests need to drive the on-disk
@@ -93,7 +92,6 @@ const buildLayer = (state: Ref.Ref<MockState>) =>
   Layer.fresh(ModelsDev.layer).pipe(
     Layer.provide(Layer.succeed(HttpClient.HttpClient, makeMockClient(state))),
     Layer.provide(AppFileSystem.defaultLayer),
-    Layer.provide(RuntimeFlags.layer({ client: "test-client" })),
   )
 
 const writeCache = (data: object, mtimeMs?: number) =>
@@ -138,14 +136,14 @@ describe("ModelsDev Service", () => {
     }),
   )
 
-  it.live("get() returns {} when disk empty and fetch disabled", () =>
+  it.live("get() returns bundled snapshot when disk empty and fetch disabled", () =>
     Effect.gen(function* () {
       const state = yield* Ref.make(initialState)
       const result = yield* provided(
         state,
         ModelsDev.Service.use((s) => s.get()),
       )
-      expect(result).toEqual({})
+      expect(Object.keys(result).length).toBeGreaterThan(0)
       const final = yield* Ref.get(state)
       expect(final.calls).toEqual([])
     }),
@@ -207,7 +205,7 @@ describe("ModelsDev Service", () => {
       const final = yield* Ref.get(state)
       expect(final.calls.length).toBe(1)
       expect(final.calls[0].url).toContain("/api.json")
-      expect(final.calls[0].userAgent).toContain("/test-client")
+      expect(final.calls[0].userAgent).toContain("/cli")
     }),
   )
 
@@ -257,7 +255,7 @@ describe("ModelsDev Service", () => {
         }),
       )
       expect(result).toEqual(fixture)
-      // withTransientReadRetry retries 5xx, so calls may be > 1.
+      // retryTransient retries 5xx, so calls may be > 1.
       const final = yield* Ref.get(state)
       expect(final.calls.length).toBeGreaterThanOrEqual(1)
     }),

+ 0 - 0
packages/core/test/v2/plugin/fixtures/provider-factory.ts → packages/core/test/plugin/fixtures/provider-factory.ts


+ 0 - 0
packages/core/test/v2/plugin/provider-alibaba.test.ts → packages/core/test/plugin/provider-alibaba.test.ts


+ 0 - 0
packages/core/test/v2/plugin/provider-amazon-bedrock.test.ts → packages/core/test/plugin/provider-amazon-bedrock.test.ts


+ 0 - 0
packages/core/test/v2/plugin/provider-anthropic.test.ts → packages/core/test/plugin/provider-anthropic.test.ts


+ 0 - 0
packages/core/test/v2/plugin/provider-azure-cognitive-services.test.ts → packages/core/test/plugin/provider-azure-cognitive-services.test.ts


+ 1 - 1
packages/core/test/v2/plugin/provider-azure.test.ts → packages/core/test/plugin/provider-azure.test.ts

@@ -4,7 +4,7 @@ import { AuthV2 } from "@opencode-ai/core/auth"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { AuthPlugin } from "@opencode-ai/core/plugin/auth"
 import { AzurePlugin } from "@opencode-ai/core/plugin/provider/azure"
-import { testEffect } from "../../lib/effect"
+import { testEffect } from "../lib/effect"
 import { fakeSelectorSdk, it, model, npmLayer, provider, withEnv } from "./provider-helper"
 
 const itWithAuth = testEffect(Layer.mergeAll(PluginV2.defaultLayer, AuthV2.defaultLayer, npmLayer))

+ 0 - 0
packages/core/test/v2/plugin/provider-cerebras.test.ts → packages/core/test/plugin/provider-cerebras.test.ts


+ 0 - 0
packages/core/test/v2/plugin/provider-cloudflare-ai-gateway.test.ts → packages/core/test/plugin/provider-cloudflare-ai-gateway.test.ts


+ 1 - 1
packages/core/test/v2/plugin/provider-cloudflare-workers-ai.test.ts → packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts

@@ -5,7 +5,7 @@ import { ModelV2 } from "@opencode-ai/core/model"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { AuthPlugin } from "@opencode-ai/core/plugin/auth"
 import { CloudflareWorkersAIPlugin } from "@opencode-ai/core/plugin/provider/cloudflare-workers-ai"
-import { testEffect } from "../../lib/effect"
+import { testEffect } from "../lib/effect"
 import { fakeSelectorSdk, it, model, npmLayer, provider, withEnv } from "./provider-helper"
 
 const itWithAuth = testEffect(Layer.mergeAll(PluginV2.defaultLayer, AuthV2.defaultLayer, npmLayer))

+ 0 - 0
packages/core/test/v2/plugin/provider-cohere.test.ts → packages/core/test/plugin/provider-cohere.test.ts


+ 1 - 1
packages/core/test/v2/plugin/provider-deepinfra.test.ts → packages/core/test/plugin/provider-deepinfra.test.ts

@@ -3,7 +3,7 @@ import { Effect, Layer } from "effect"
 import { AISDK } from "@opencode-ai/core/aisdk"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { DeepInfraPlugin } from "@opencode-ai/core/plugin/provider/deepinfra"
-import { testEffect } from "../../lib/effect"
+import { testEffect } from "../lib/effect"
 import { it, model } from "./provider-helper"
 
 const itAISDK = testEffect(Layer.provideMerge(AISDK.layer, PluginV2.defaultLayer))

+ 1 - 1
packages/core/test/v2/plugin/provider-dynamic.test.ts → packages/core/test/plugin/provider-dynamic.test.ts

@@ -9,7 +9,7 @@ import { AISDK } from "@opencode-ai/core/aisdk"
 import { ModelV2 } from "@opencode-ai/core/model"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { DynamicProviderPlugin } from "@opencode-ai/core/plugin/provider/dynamic"
-import { testEffect } from "../../lib/effect"
+import { testEffect } from "../lib/effect"
 import { fixtureProvider, it, model, npmLayer } from "./provider-helper"
 
 const fixtureProviderPath = fileURLToPath(fixtureProvider)

+ 0 - 0
packages/core/test/v2/plugin/provider-gateway.test.ts → packages/core/test/plugin/provider-gateway.test.ts


+ 1 - 1
packages/core/test/plugin/provider-github-copilot.test.ts

@@ -3,7 +3,7 @@ import { Effect } from "effect"
 import { ModelV2 } from "@opencode-ai/core/model"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { GithubCopilotPlugin } from "@opencode-ai/core/plugin/provider/github-copilot"
-import { fakeSelectorSdk, it, model } from "../v2/plugin/provider-helper"
+import { fakeSelectorSdk, it, model } from "./provider-helper"
 
 describe("GithubCopilotPlugin", () => {
   it.effect("creates the bundled Copilot SDK for the GitHub Copilot package", () =>

+ 1 - 1
packages/core/test/v2/plugin/provider-gitlab.test.ts → packages/core/test/plugin/provider-gitlab.test.ts

@@ -4,7 +4,7 @@ import { AuthV2 } from "@opencode-ai/core/auth"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { AuthPlugin } from "@opencode-ai/core/plugin/auth"
 import { GitLabPlugin } from "@opencode-ai/core/plugin/provider/gitlab"
-import { testEffect } from "../../lib/effect"
+import { testEffect } from "../lib/effect"
 import { it, model, npmLayer, provider, withEnv } from "./provider-helper"
 
 const gitlabSDKOptions: Record<string, unknown>[] = []

+ 0 - 0
packages/core/test/v2/plugin/provider-google-vertex-anthropic.test.ts → packages/core/test/plugin/provider-google-vertex-anthropic.test.ts


+ 0 - 0
packages/core/test/v2/plugin/provider-google-vertex.test.ts → packages/core/test/plugin/provider-google-vertex.test.ts


+ 1 - 1
packages/core/test/v2/plugin/provider-google.test.ts → packages/core/test/plugin/provider-google.test.ts

@@ -4,7 +4,7 @@ import { AISDK } from "@opencode-ai/core/aisdk"
 import { ModelV2 } from "@opencode-ai/core/model"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { GooglePlugin } from "@opencode-ai/core/plugin/provider/google"
-import { testEffect } from "../../lib/effect"
+import { testEffect } from "../lib/effect"
 import { it, model } from "./provider-helper"
 
 const itWithAISDK = testEffect(AISDK.layer.pipe(Layer.provideMerge(PluginV2.defaultLayer)))

+ 1 - 1
packages/core/test/v2/plugin/provider-groq.test.ts → packages/core/test/plugin/provider-groq.test.ts

@@ -6,7 +6,7 @@ import { ModelV2 } from "@opencode-ai/core/model"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { GroqPlugin } from "@opencode-ai/core/plugin/provider/groq"
 import { it, model } from "./provider-helper"
-import { testEffect } from "../../lib/effect"
+import { testEffect } from "../lib/effect"
 
 const aisdkIt = testEffect(AISDK.layer.pipe(Layer.provideMerge(PluginV2.defaultLayer)))
 

+ 1 - 1
packages/core/test/v2/plugin/provider-helper.ts → packages/core/test/plugin/provider-helper.ts

@@ -5,7 +5,7 @@ import { Effect, Layer, Option } from "effect"
 import { ModelV2 } from "@opencode-ai/core/model"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { ProviderV2 } from "@opencode-ai/core/provider"
-import { testEffect } from "../../lib/effect"
+import { testEffect } from "../lib/effect"
 
 export const fixtureProvider = new URL("./fixtures/provider-factory.ts", import.meta.url).href
 

+ 0 - 0
packages/core/test/v2/plugin/provider-kilo.test.ts → packages/core/test/plugin/provider-kilo.test.ts


+ 0 - 0
packages/core/test/v2/plugin/provider-llmgateway.test.ts → packages/core/test/plugin/provider-llmgateway.test.ts


+ 0 - 0
packages/core/test/v2/plugin/provider-mistral.test.ts → packages/core/test/plugin/provider-mistral.test.ts


+ 0 - 0
packages/core/test/v2/plugin/provider-nvidia.test.ts → packages/core/test/plugin/provider-nvidia.test.ts


+ 0 - 0
packages/core/test/v2/plugin/provider-openai-compatible.test.ts → packages/core/test/plugin/provider-openai-compatible.test.ts


+ 0 - 0
packages/core/test/v2/plugin/provider-openai.test.ts → packages/core/test/plugin/provider-openai.test.ts


+ 4 - 2
packages/core/test/v2/plugin/provider-opencode.test.ts → packages/core/test/plugin/provider-opencode.test.ts

@@ -1,6 +1,7 @@
 import { describe, expect } from "bun:test"
-import { DateTime, Effect, Option } from "effect"
+import { DateTime, Effect, Layer, Option } from "effect"
 import { Catalog } from "@opencode-ai/core/catalog"
+import { Instance } from "@opencode-ai/core/instance"
 import { ModelV2 } from "@opencode-ai/core/model"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { OpencodePlugin } from "@opencode-ai/core/plugin/provider/opencode"
@@ -8,6 +9,7 @@ import { ProviderV2 } from "@opencode-ai/core/provider"
 import { it, model, provider, withEnv } from "./provider-helper"
 
 const cost = (input: number, output = 0) => [{ input, output, cache: { read: 0, write: 0 } }]
+const instanceLayer = Layer.succeed(Instance.Service, Instance.Service.of({ directory: "test" }))
 
 describe("OpencodePlugin", () => {
   it.effect("uses a public key and cancels paid models without credentials", () =>
@@ -190,6 +192,6 @@ describe("OpencodePlugin", () => {
       const selected = yield* catalog.model.small(providerID)
 
       expect(Option.getOrUndefined(selected)?.id).toBe(ModelV2.ID.make("gpt-5-nano"))
-    }).pipe(Effect.provide(Catalog.defaultLayer)),
+    }).pipe(Effect.provide(Catalog.defaultLayer.pipe(Layer.provide(instanceLayer)))),
   )
 })

+ 0 - 0
packages/core/test/v2/plugin/provider-openrouter.test.ts → packages/core/test/plugin/provider-openrouter.test.ts


+ 0 - 0
packages/core/test/v2/plugin/provider-perplexity.test.ts → packages/core/test/plugin/provider-perplexity.test.ts


+ 0 - 0
packages/core/test/v2/plugin/provider-sap-ai-core.test.ts → packages/core/test/plugin/provider-sap-ai-core.test.ts


+ 0 - 0
packages/core/test/v2/plugin/provider-togetherai.test.ts → packages/core/test/plugin/provider-togetherai.test.ts


+ 0 - 0
packages/core/test/v2/plugin/provider-venice.test.ts → packages/core/test/plugin/provider-venice.test.ts


+ 0 - 0
packages/core/test/v2/plugin/provider-vercel.test.ts → packages/core/test/plugin/provider-vercel.test.ts


+ 1 - 1
packages/core/test/v2/plugin/provider-xai.test.ts → packages/core/test/plugin/provider-xai.test.ts

@@ -4,7 +4,7 @@ import { ModelV2 } from "@opencode-ai/core/model"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { XAIPlugin } from "@opencode-ai/core/plugin/provider/xai"
 import { ProviderV2 } from "@opencode-ai/core/provider"
-import { testEffect } from "../../lib/effect"
+import { testEffect } from "../lib/effect"
 import { fakeSelectorSdk } from "./provider-helper"
 
 const it = testEffect(PluginV2.defaultLayer)

+ 0 - 0
packages/core/test/v2/plugin/provider-zenmux.test.ts → packages/core/test/plugin/provider-zenmux.test.ts


+ 2 - 2
packages/opencode/script/generate.ts

@@ -13,11 +13,11 @@ const modelsData = process.env.MODELS_DEV_API_JSON
   ? await Bun.file(process.env.MODELS_DEV_API_JSON).text()
   : await fetch(`${modelsUrl}/api.json`).then((x) => x.text())
 await Bun.write(
-  path.join(dir, "src/provider/models-snapshot.js"),
+  path.join(dir, "../core/src/models-snapshot.js"),
   `// @ts-nocheck\n// Auto-generated by build.ts - do not edit\nexport const snapshot = ${modelsData}\n`,
 )
 await Bun.write(
-  path.join(dir, "src/provider/models-snapshot.d.ts"),
+  path.join(dir, "../core/src/models-snapshot.d.ts"),
   `// Auto-generated by build.ts - do not edit\nexport declare const snapshot: Record<string, unknown>\n`,
 )
 console.log("Generated models-snapshot.js")

+ 16 - 10
packages/opencode/src/cli/cmd/debug/v2.ts

@@ -1,22 +1,23 @@
 import { EOL } from "os"
 import { Effect, Layer, Option } from "effect"
 import { Catalog } from "@opencode-ai/core/catalog"
+import { InstanceServiceMap } from "@opencode-ai/core/instance-layer"
+import { PluginBoot } from "@opencode-ai/core/plugin/boot"
 import { effectCmd } from "../../effect-cmd"
-import { PluginBoot } from "@/v2/plugin-boot"
 
-const layer = Catalog.defaultLayer.pipe(Layer.provide(PluginBoot.defaultLayer))
+const Runtime = Layer.mergeAll(InstanceServiceMap.layer)
 
 export const V2Command = effectCmd({
   command: "v2",
   describe: "debug v2 catalog and built-in plugins",
   instance: false,
-  handler: Effect.fn("Cli.debug.v2")(function* () {
-    const result = yield* Effect.gen(function* () {
+  handler: Effect.fn("Cli.debug.v2")(
+    function* () {
+      yield* PluginBoot.Service.use((service) => service.wait())
       const catalog = yield* Catalog.Service
-
       const providers = (yield* catalog.provider.available()).sort((a, b) => a.id.localeCompare(b.id))
       const all = (yield* catalog.provider.all()).sort((a, b) => a.id.localeCompare(b.id))
-      return {
+      const result = {
         providers,
         default: catalog.model
           .default()
@@ -33,8 +34,13 @@ export const V2Command = effectCmd({
           ),
         ),
       }
-    }).pipe(Effect.provide(layer), Effect.orDie)
-
-    process.stdout.write(JSON.stringify(result, null, 2) + EOL)
-  }),
+      process.stdout.write(JSON.stringify(result, null, 2) + EOL)
+    },
+    Effect.provide(
+      InstanceServiceMap.get({
+        directory: process.cwd(),
+      }),
+    ),
+    Effect.provide(Runtime),
+  ),
 })

+ 1 - 1
packages/opencode/src/cli/cmd/github.ts

@@ -19,7 +19,7 @@ import type {
 import { UI } from "../ui"
 import { cmd } from "./cmd"
 import { effectCmd } from "../effect-cmd"
-import { ModelsDev } from "@/provider/models"
+import { ModelsDev } from "@opencode-ai/core/models"
 import { InstanceRef } from "@/effect/instance-ref"
 import { SessionShare } from "@/share/session"
 import { Session } from "@/session/session"

+ 1 - 1
packages/opencode/src/cli/cmd/models.ts

@@ -2,7 +2,7 @@ import { EOL } from "os"
 import { Effect } from "effect"
 import { Provider } from "@/provider/provider"
 import { ProviderID } from "../../provider/schema"
-import { ModelsDev } from "@/provider/models"
+import { ModelsDev } from "@opencode-ai/core/models"
 import { effectCmd, fail } from "../effect-cmd"
 import { UI } from "../ui"
 

+ 1 - 1
packages/opencode/src/cli/cmd/providers.ts

@@ -3,7 +3,7 @@ import { cmd } from "./cmd"
 import { CliError, effectCmd, fail } from "../effect-cmd"
 import { UI } from "../ui"
 import * as Prompt from "../effect/prompt"
-import { ModelsDev } from "@/provider/models"
+import { ModelsDev } from "@opencode-ai/core/models"
 
 import { map, pipe, sortBy, values } from "remeda"
 import path from "path"

+ 1 - 1
packages/opencode/src/effect/app-runtime.ts

@@ -14,7 +14,7 @@ import { FileWatcher } from "@/file/watcher"
 import { Storage } from "@/storage/storage"
 import { Snapshot } from "@/snapshot"
 import { Plugin } from "@/plugin"
-import { ModelsDev } from "@/provider/models"
+import { ModelsDev } from "@opencode-ai/core/models"
 import { Provider } from "@/provider/provider"
 import { ProviderAuth } from "@/provider/auth"
 import { Agent } from "@/agent/agent"

+ 1 - 2
packages/opencode/src/provider/model-status.ts

@@ -1,7 +1,6 @@
 import { Schema } from "effect"
 
-export const CatalogModelStatus = Schema.Literals(["alpha", "beta", "deprecated"])
-export type CatalogModelStatus = typeof CatalogModelStatus.Type
+export { CatalogModelStatus } from "@opencode-ai/core/models"
 
 export const ModelStatus = Schema.Literals(["alpha", "beta", "deprecated", "active"])
 export type ModelStatus = typeof ModelStatus.Type

+ 1 - 1
packages/opencode/src/provider/provider.ts

@@ -8,7 +8,7 @@ import { Npm } from "@opencode-ai/core/npm"
 import { Hash } from "@opencode-ai/core/util/hash"
 import { Plugin } from "../plugin"
 import { type LanguageModelV3 } from "@ai-sdk/provider"
-import * as ModelsDev from "./models"
+import * as ModelsDev from "@opencode-ai/core/models"
 import { Auth } from "../auth"
 import { Env } from "../env"
 import { InstallationVersion } from "@opencode-ai/core/installation/version"

+ 1 - 1
packages/opencode/src/provider/transform.ts

@@ -2,7 +2,7 @@ import type { ModelMessage, ToolResultPart } from "ai"
 import { mergeDeep, unique } from "remeda"
 import type { JSONSchema7 } from "@ai-sdk/provider"
 import type * as Provider from "./provider"
-import type * as ModelsDev from "./models"
+import type * as ModelsDev from "@opencode-ai/core/models"
 import { iife } from "@/util/iife"
 import { Flag } from "@opencode-ai/core/flag/flag"
 

+ 59 - 0
packages/opencode/src/server/routes/instance/httpapi/groups/v2/instance.ts

@@ -0,0 +1,59 @@
+import { Catalog } from "@opencode-ai/core/catalog"
+import { Instance } from "@opencode-ai/core/instance"
+import { InstanceServiceMap } from "@opencode-ai/core/instance-layer"
+import { PluginBoot } from "@opencode-ai/core/plugin/boot"
+import { Effect, Layer, Schema } from "effect"
+import { HttpServerRequest } from "effect/unstable/http"
+import { HttpApiMiddleware, OpenApi } from "effect/unstable/httpapi"
+
+export const InstanceQuery = Schema.Struct({
+  instance: Schema.optional(
+    Schema.Struct({
+      directory: Schema.optional(Schema.String),
+      workspace: Schema.optional(Schema.String),
+    }),
+  ),
+}).annotate({ identifier: "V2InstanceQuery" })
+
+export const instanceQueryOpenApi = OpenApi.annotations({
+  transform: (operation) => {
+    const parameters = operation.parameters
+    if (!Array.isArray(parameters)) return operation
+    return {
+      ...operation,
+      parameters: parameters.map((parameter) =>
+        parameter?.name === "instance" && parameter?.in === "query"
+          ? { ...parameter, style: "deepObject", explode: true }
+          : parameter,
+      ),
+    }
+  },
+})
+
+export class V2InstanceMiddleware extends HttpApiMiddleware.Service<
+  V2InstanceMiddleware,
+  {
+    provides: Catalog.Service | PluginBoot.Service
+  }
+>()("@opencode/ExperimentalHttpApiV2Instance") {}
+
+function ref(request: HttpServerRequest.HttpServerRequest): Instance.Ref {
+  const query = new URL(request.url, "http://localhost").searchParams
+  return {
+    directory: query.get("instance[directory]") || request.headers["x-opencode-directory"] || process.cwd(),
+    workspaceID: query.get("instance[workspace]") || request.headers["x-opencode-workspace"],
+  }
+}
+
+export const layer = Layer.effect(
+  V2InstanceMiddleware,
+  Effect.gen(function* () {
+    const instances = yield* InstanceServiceMap
+    return V2InstanceMiddleware.of((effect) =>
+      Effect.gen(function* () {
+        const request = yield* HttpServerRequest.HttpServerRequest
+        return yield* effect.pipe(Effect.provide(instances.get(ref(request))))
+      }),
+    )
+  }),
+).pipe(Layer.provide(InstanceServiceMap.layer))

+ 4 - 1
packages/opencode/src/server/routes/instance/httpapi/groups/v2/model.ts

@@ -2,12 +2,14 @@ import { ModelV2 } from "@opencode-ai/core/model"
 import { Schema } from "effect"
 import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
 import { Authorization } from "../../middleware/authorization"
+import { InstanceQuery, instanceQueryOpenApi, V2InstanceMiddleware } from "./instance"
 
 export const ModelGroup = HttpApiGroup.make("v2.model")
   .add(
     HttpApiEndpoint.get("models", "/api/model", {
+      query: InstanceQuery,
       success: Schema.Array(ModelV2.Info),
-    }).annotateMerge(
+    }).annotateMerge(instanceQueryOpenApi).annotateMerge(
       OpenApi.annotations({
         identifier: "v2.model.list",
         summary: "List v2 models",
@@ -21,4 +23,5 @@ export const ModelGroup = HttpApiGroup.make("v2.model")
       description: "Experimental v2 model routes.",
     }),
   )
+  .middleware(V2InstanceMiddleware)
   .middleware(Authorization)

+ 6 - 2
packages/opencode/src/server/routes/instance/httpapi/groups/v2/provider.ts

@@ -3,12 +3,14 @@ import { Schema } from "effect"
 import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
 import { ApiNotFoundError } from "../../errors"
 import { Authorization } from "../../middleware/authorization"
+import { InstanceQuery, instanceQueryOpenApi, V2InstanceMiddleware } from "./instance"
 
 export const ProviderGroup = HttpApiGroup.make("v2.provider")
   .add(
     HttpApiEndpoint.get("providers", "/api/provider", {
+      query: InstanceQuery,
       success: Schema.Array(ProviderV2.Info),
-    }).annotateMerge(
+    }).annotateMerge(instanceQueryOpenApi).annotateMerge(
       OpenApi.annotations({
         identifier: "v2.provider.list",
         summary: "List v2 providers",
@@ -19,9 +21,10 @@ export const ProviderGroup = HttpApiGroup.make("v2.provider")
   .add(
     HttpApiEndpoint.get("provider", "/api/provider/:providerID", {
       params: { providerID: ProviderV2.ID },
+      query: InstanceQuery,
       success: ProviderV2.Info,
       error: ApiNotFoundError,
-    }).annotateMerge(
+    }).annotateMerge(instanceQueryOpenApi).annotateMerge(
       OpenApi.annotations({
         identifier: "v2.provider.get",
         summary: "Get v2 provider",
@@ -35,4 +38,5 @@ export const ProviderGroup = HttpApiGroup.make("v2.provider")
       description: "Experimental v2 provider routes.",
     }),
   )
+  .middleware(V2InstanceMiddleware)
   .middleware(Authorization)

+ 1 - 1
packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts

@@ -1,6 +1,6 @@
 import { ProviderAuth } from "@/provider/auth"
 import { Config } from "@/config/config"
-import { ModelsDev } from "@/provider/models"
+import { ModelsDev } from "@opencode-ai/core/models"
 import { Provider } from "@/provider/provider"
 import { ProviderID } from "@/provider/schema"
 import { mapValues } from "remeda"

+ 2 - 2
packages/opencode/src/server/routes/instance/httpapi/handlers/v2.ts

@@ -1,12 +1,12 @@
-import { Catalog } from "@opencode-ai/core/catalog"
 import { SessionV2 } from "@/v2/session"
 import { Layer } from "effect"
+import { layer as v2InstanceLayer } from "../groups/v2/instance"
 import { messageHandlers } from "./v2/message"
 import { modelHandlers } from "./v2/model"
 import { providerHandlers } from "./v2/provider"
 import { sessionHandlers } from "./v2/session"
 
 export const v2Handlers = Layer.mergeAll(sessionHandlers, messageHandlers, modelHandlers, providerHandlers).pipe(
-  Layer.provide(Catalog.defaultLayer),
+  Layer.provide(v2InstanceLayer),
   Layer.provide(SessionV2.defaultLayer),
 )

+ 10 - 3
packages/opencode/src/server/routes/instance/httpapi/handlers/v2/model.ts

@@ -1,12 +1,19 @@
 import { Catalog } from "@opencode-ai/core/catalog"
+import { PluginBoot } from "@opencode-ai/core/plugin/boot"
 import { Effect } from "effect"
 import { HttpApiBuilder } from "effect/unstable/httpapi"
 import { InstanceHttpApi } from "../../api"
 
 export const modelHandlers = HttpApiBuilder.group(InstanceHttpApi, "v2.model", (handlers) =>
   Effect.gen(function* () {
-    const catalog = yield* Catalog.Service
-
-    return handlers.handle("models", () => catalog.model.available())
+    return handlers.handle(
+      "models",
+      Effect.fn(function* () {
+        const catalog = yield* Catalog.Service
+        const pluginBoot = yield* PluginBoot.Service
+        yield* pluginBoot.wait()
+        return yield* catalog.model.available()
+      }),
+    )
   }),
 )

+ 13 - 3
packages/opencode/src/server/routes/instance/httpapi/handlers/v2/provider.ts

@@ -1,4 +1,5 @@
 import { Catalog } from "@opencode-ai/core/catalog"
+import { PluginBoot } from "@opencode-ai/core/plugin/boot"
 import { Effect } from "effect"
 import { HttpApiBuilder } from "effect/unstable/httpapi"
 import { InstanceHttpApi } from "../../api"
@@ -6,13 +7,22 @@ import { notFound } from "../../errors"
 
 export const providerHandlers = HttpApiBuilder.group(InstanceHttpApi, "v2.provider", (handlers) =>
   Effect.gen(function* () {
-    const catalog = yield* Catalog.Service
-
     return handlers
-      .handle("providers", () => catalog.provider.available())
+      .handle(
+        "providers",
+        Effect.fn(function* () {
+          const catalog = yield* Catalog.Service
+          const pluginBoot = yield* PluginBoot.Service
+          yield* pluginBoot.wait()
+          return yield* catalog.provider.available()
+        }),
+      )
       .handle(
         "provider",
         Effect.fn(function* (ctx) {
+          const catalog = yield* Catalog.Service
+          const pluginBoot = yield* PluginBoot.Service
+          yield* pluginBoot.wait()
           return yield* catalog.provider
             .get(ctx.params.providerID)
             .pipe(Effect.catchTag("CatalogV2.ProviderNotFound", () => Effect.fail(notFound("Provider not found"))))

+ 1 - 1
packages/opencode/src/server/routes/instance/httpapi/server.ts

@@ -29,7 +29,7 @@ import { InstanceLayer } from "@/project/instance-layer"
 import { Plugin } from "@/plugin"
 import { Project } from "@/project/project"
 import { ProviderAuth } from "@/provider/auth"
-import { ModelsDev } from "@/provider/models"
+import { ModelsDev } from "@opencode-ai/core/models"
 import { Provider } from "@/provider/provider"
 import { Pty } from "@/pty"
 import { PtyTicket } from "@/pty/ticket"

+ 0 - 50
packages/opencode/src/v2/plugin-boot.ts

@@ -1,50 +0,0 @@
-export * as PluginBoot from "./plugin-boot"
-
-import { Npm } from "@opencode-ai/core/npm"
-import { Effect, Layer } from "effect"
-import { AuthV2 } from "@opencode-ai/core/auth"
-import { Catalog } from "@opencode-ai/core/catalog"
-import { PluginV2 } from "@opencode-ai/core/plugin"
-import { AuthPlugin } from "@opencode-ai/core/plugin/auth"
-import { EnvPlugin } from "@opencode-ai/core/plugin/env"
-import { ProviderPlugins } from "@opencode-ai/core/plugin/provider"
-import { ModelsDevPlugin } from "./plugin/models-dev"
-
-type Plugin = {
-  id: PluginV2.ID
-  effect: Effect.Effect<PluginV2.HookFunctions | void, never, Catalog.Service | AuthV2.Service | Npm.Service>
-}
-
-export const layer = Layer.effectDiscard(
-  Effect.gen(function* () {
-    const catalog = yield* Catalog.Service
-    const plugin = yield* PluginV2.Service
-    const auth = yield* AuthV2.Service
-    const npm = yield* Npm.Service
-
-    const add = Effect.fn("PluginBoot.add")(function* (input: Plugin) {
-      yield* plugin.add({
-        id: input.id,
-        effect: input.effect.pipe(
-          Effect.provideService(Catalog.Service, catalog),
-          Effect.provideService(AuthV2.Service, auth),
-          Effect.provideService(Npm.Service, npm),
-        ),
-      })
-    })
-
-    yield* add(EnvPlugin)
-    yield* add(AuthPlugin)
-    for (const item of ProviderPlugins) {
-      yield* add(item)
-    }
-    yield* add(ModelsDevPlugin)
-  }),
-)
-
-export const defaultLayer = layer.pipe(
-  Layer.provide(Catalog.defaultLayer),
-  Layer.provide(PluginV2.defaultLayer),
-  Layer.provide(Layer.orDie(AuthV2.defaultLayer)),
-  Layer.provide(Npm.defaultLayer),
-)

+ 1 - 1
packages/opencode/test/provider/model-status.test.ts

@@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"
 import { Schema } from "effect"
 import { ConfigProvider } from "@/config/provider"
 import { CatalogModelStatus, ModelStatus } from "@/provider/model-status"
-import { ModelsDev } from "@/provider/models"
+import { ModelsDev } from "@opencode-ai/core/models"
 import { Provider } from "@/provider/provider"
 
 describe("provider model status schemas", () => {

+ 1 - 1
packages/opencode/test/provider/provider.test.ts

@@ -7,7 +7,7 @@ import { Global } from "@opencode-ai/core/global"
 import { Instance } from "../../src/project/instance"
 import { WithInstance } from "../../src/project/with-instance"
 import { Plugin } from "../../src/plugin/index"
-import { ModelsDev } from "@/provider/models"
+import { ModelsDev } from "@opencode-ai/core/models"
 import { Provider } from "@/provider/provider"
 import { ProviderID, ModelID } from "../../src/provider/schema"
 import { Filesystem } from "@/util/filesystem"

+ 1 - 1
packages/opencode/test/session/llm.test.ts

@@ -9,7 +9,7 @@ import { Instance } from "../../src/project/instance"
 import { WithInstance } from "../../src/project/with-instance"
 import { Provider } from "@/provider/provider"
 import { ProviderTransform } from "@/provider/transform"
-import { ModelsDev } from "@/provider/models"
+import { ModelsDev } from "@opencode-ai/core/models"
 import { ProviderID, ModelID } from "../../src/provider/schema"
 import { Filesystem } from "@/util/filesystem"
 import { tmpdir } from "../fixture/fixture"

+ 37 - 3
packages/sdk/js/src/v2/gen/sdk.gen.ts

@@ -4385,10 +4385,20 @@ export class Model extends HeyApiClient {
    *
    * Retrieve available v2 models ordered by release date.
    */
-  public list<ThrowOnError extends boolean = false>(options?: Options<never, ThrowOnError>) {
+  public list<ThrowOnError extends boolean = false>(
+    parameters?: {
+      instance?: {
+        directory?: string
+        workspace?: string
+      }
+    },
+    options?: Options<never, ThrowOnError>,
+  ) {
+    const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "instance" }] }])
     return (options?.client ?? this.client).get<V2ModelListResponses, unknown, ThrowOnError>({
       url: "/api/model",
       ...options,
+      ...params,
     })
   }
 }
@@ -4399,10 +4409,20 @@ export class Provider2 extends HeyApiClient {
    *
    * Retrieve active v2 AI providers so clients can show provider availability and configuration.
    */
-  public list<ThrowOnError extends boolean = false>(options?: Options<never, ThrowOnError>) {
+  public list<ThrowOnError extends boolean = false>(
+    parameters?: {
+      instance?: {
+        directory?: string
+        workspace?: string
+      }
+    },
+    options?: Options<never, ThrowOnError>,
+  ) {
+    const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "instance" }] }])
     return (options?.client ?? this.client).get<V2ProviderListResponses, unknown, ThrowOnError>({
       url: "/api/provider",
       ...options,
+      ...params,
     })
   }
 
@@ -4414,10 +4434,24 @@ export class Provider2 extends HeyApiClient {
   public get<ThrowOnError extends boolean = false>(
     parameters: {
       providerID: string
+      instance?: {
+        directory?: string
+        workspace?: string
+      }
     },
     options?: Options<never, ThrowOnError>,
   ) {
-    const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "providerID" }] }])
+    const params = buildClientParams(
+      [parameters],
+      [
+        {
+          args: [
+            { in: "path", key: "providerID" },
+            { in: "query", key: "instance" },
+          ],
+        },
+      ],
+    )
     return (options?.client ?? this.client).get<V2ProviderGetResponses, V2ProviderGetErrors, ThrowOnError>({
       url: "/api/provider/{providerID}",
       ...options,

+ 38 - 23
packages/sdk/js/src/v2/gen/types.gen.ts

@@ -15,8 +15,6 @@ export type Event =
   | EventPermissionReplied
   | EventSessionDiff
   | EventSessionError
-  | EventInstallationUpdated
-  | EventInstallationUpdateAvailable
   | EventQuestionAsked
   | EventQuestionReplied
   | EventQuestionRejected
@@ -42,6 +40,8 @@ export type Event =
   | EventPtyUpdated
   | EventPtyExited
   | EventPtyDeleted
+  | EventInstallationUpdated
+  | EventInstallationUpdateAvailable
   | EventMessageUpdated
   | EventMessageRemoved
   | EventMessagePartUpdated
@@ -799,8 +799,6 @@ export type GlobalEvent = {
     | EventPermissionReplied
     | EventSessionDiff
     | EventSessionError
-    | EventInstallationUpdated
-    | EventInstallationUpdateAvailable
     | EventQuestionAsked
     | EventQuestionReplied
     | EventQuestionRejected
@@ -826,6 +824,8 @@ export type GlobalEvent = {
     | EventPtyUpdated
     | EventPtyExited
     | EventPtyDeleted
+    | EventInstallationUpdated
+    | EventInstallationUpdateAvailable
     | EventMessageUpdated
     | EventMessageRemoved
     | EventMessagePartUpdated
@@ -2496,22 +2496,6 @@ export type EventSessionError = {
   }
 }
 
-export type EventInstallationUpdated = {
-  id: string
-  type: "installation.updated"
-  properties: {
-    version: string
-  }
-}
-
-export type EventInstallationUpdateAvailable = {
-  id: string
-  type: "installation.update-available"
-  properties: {
-    version: string
-  }
-}
-
 export type EventQuestionAsked = {
   id: string
   type: "question.asked"
@@ -2681,6 +2665,22 @@ export type EventPtyDeleted = {
   }
 }
 
+export type EventInstallationUpdated = {
+  id: string
+  type: "installation.updated"
+  properties: {
+    version: string
+  }
+}
+
+export type EventInstallationUpdateAvailable = {
+  id: string
+  type: "installation.update-available"
+  properties: {
+    version: string
+  }
+}
+
 export type EventMessageUpdated = {
   id: string
   type: "message.updated"
@@ -6673,7 +6673,12 @@ export type V2SessionMessagesResponse2 = V2SessionMessagesResponses[keyof V2Sess
 export type V2ModelListData = {
   body?: never
   path?: never
-  query?: never
+  query?: {
+    instance?: {
+      directory?: string
+      workspace?: string
+    }
+  }
   url: "/api/model"
 }
 
@@ -6689,7 +6694,12 @@ export type V2ModelListResponse = V2ModelListResponses[keyof V2ModelListResponse
 export type V2ProviderListData = {
   body?: never
   path?: never
-  query?: never
+  query?: {
+    instance?: {
+      directory?: string
+      workspace?: string
+    }
+  }
   url: "/api/provider"
 }
 
@@ -6707,7 +6717,12 @@ export type V2ProviderGetData = {
   path: {
     providerID: string
   }
-  query?: never
+  query?: {
+    instance?: {
+      directory?: string
+      workspace?: string
+    }
+  }
   url: "/api/provider/{providerID}"
 }
 

部分文件因文件數量過多而無法顯示