소스 검색

feat(core): expose session model switching (#31011)

Kit Langton 3 달 전
부모
커밋
a57fb32d95
3개의 변경된 파일46개의 추가작업 그리고 1개의 파일을 삭제
  1. 1 0
      packages/core/src/public/opencode.ts
  2. 6 0
      packages/core/src/public/session.ts
  3. 39 1
      packages/core/test/public-opencode.test.ts

+ 1 - 0
packages/core/src/public/opencode.ts

@@ -51,6 +51,7 @@ export const layer = Layer.effect(
           }),
         get: sessions.get,
         list: sessions.list,
+        switchModel: sessions.switchModel,
         interrupt: sessions.interrupt,
         prompt: (input) =>
           sessions.prompt({

+ 6 - 0
packages/core/src/public/session.ts

@@ -59,6 +59,11 @@ export interface PromptInput {
   readonly delivery?: Delivery
 }
 
+export interface SwitchModelInput {
+  readonly sessionID: ID
+  readonly model: Model.Ref
+}
+
 export interface MessagesInput {
   readonly sessionID: ID
   readonly limit?: number
@@ -84,6 +89,7 @@ export interface Interface {
   readonly get: (sessionID: ID) => Effect.Effect<Info, NotFoundError>
   readonly list: (input?: ListInput) => Effect.Effect<Info[]>
   readonly prompt: (input: PromptInput) => Effect.Effect<Admission, NotFoundError | PromptConflictError>
+  readonly switchModel: (input: SwitchModelInput) => Effect.Effect<void, NotFoundError>
   /** Interrupt the active V2 execution chain for one Session on this process. Interrupting an idle or missing Session is a no-op. */
   readonly interrupt: (sessionID: ID) => Effect.Effect<void>
   readonly messages: (input: MessagesInput) => Effect.Effect<Message[], NotFoundError | MessageDecodeError>

+ 39 - 1
packages/core/test/public-opencode.test.ts

@@ -1,6 +1,6 @@
 import { describe, expect } from "bun:test"
 import { Effect, Schema } from "effect"
-import { OpenCode, Session, Tool } from "@opencode-ai/core/public"
+import { AbsolutePath, Location, Model, OpenCode, Session, Tool } from "@opencode-ai/core/public"
 import { testEffect } from "./lib/effect"
 
 const it = testEffect(OpenCode.layer)
@@ -22,6 +22,7 @@ describe("public native OpenCode API", () => {
         "message",
         "messages",
         "prompt",
+        "switchModel",
       ])
       expect(Session.ID.create()).toStartWith("ses_")
       expect(Session.MessageID.create()).toStartWith("msg_")
@@ -36,4 +37,41 @@ describe("public native OpenCode API", () => {
       })
     }),
   )
+
+  it.effect("switches the exact Session to the exact model through the durable facade", () =>
+    Effect.gen(function* () {
+      const opencode = yield* OpenCode.Service
+      const targetID = Session.ID.make("ses_public_switch_target")
+      const otherID = Session.ID.make("ses_public_switch_other")
+      const model = Schema.decodeUnknownSync(Model.Ref)({
+        id: "claude-sonnet-4-5",
+        providerID: "anthropic",
+        variant: "high",
+      })
+      const location = Location.Ref.make({ directory: AbsolutePath.make("/public-session-switch-model") })
+      yield* opencode.sessions.create({ id: targetID, location })
+      yield* opencode.sessions.create({ id: otherID, location })
+
+      yield* opencode.sessions.switchModel({ sessionID: targetID, model })
+
+      expect((yield* opencode.sessions.get(targetID)).model).toEqual(model)
+      expect((yield* opencode.sessions.get(otherID)).model).toBeUndefined()
+    }),
+  )
+
+  it.effect("preserves the typed not-found error for a missing Session", () =>
+    Effect.gen(function* () {
+      const opencode = yield* OpenCode.Service
+      const sessionID = Session.ID.make("ses_public_switch_missing")
+      const error = yield* opencode.sessions
+        .switchModel({
+          sessionID,
+          model: Schema.decodeUnknownSync(Model.Ref)({ id: "claude-sonnet-4-5", providerID: "anthropic" }),
+        })
+        .pipe(Effect.flip)
+
+      expect(error).toBeInstanceOf(Session.NotFoundError)
+      expect(error.sessionID).toBe(sessionID)
+    }),
+  )
 })