feat(core): expose session model switching (#31011)
This commit is contained in:
parent
05d1104ecd
commit
a57fb32d95
3 changed files with 46 additions and 1 deletions
|
|
@ -51,6 +51,7 @@ export const layer = Layer.effect(
|
||||||
}),
|
}),
|
||||||
get: sessions.get,
|
get: sessions.get,
|
||||||
list: sessions.list,
|
list: sessions.list,
|
||||||
|
switchModel: sessions.switchModel,
|
||||||
interrupt: sessions.interrupt,
|
interrupt: sessions.interrupt,
|
||||||
prompt: (input) =>
|
prompt: (input) =>
|
||||||
sessions.prompt({
|
sessions.prompt({
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,11 @@ export interface PromptInput {
|
||||||
readonly delivery?: Delivery
|
readonly delivery?: Delivery
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SwitchModelInput {
|
||||||
|
readonly sessionID: ID
|
||||||
|
readonly model: Model.Ref
|
||||||
|
}
|
||||||
|
|
||||||
export interface MessagesInput {
|
export interface MessagesInput {
|
||||||
readonly sessionID: ID
|
readonly sessionID: ID
|
||||||
readonly limit?: number
|
readonly limit?: number
|
||||||
|
|
@ -84,6 +89,7 @@ export interface Interface {
|
||||||
readonly get: (sessionID: ID) => Effect.Effect<Info, NotFoundError>
|
readonly get: (sessionID: ID) => Effect.Effect<Info, NotFoundError>
|
||||||
readonly list: (input?: ListInput) => Effect.Effect<Info[]>
|
readonly list: (input?: ListInput) => Effect.Effect<Info[]>
|
||||||
readonly prompt: (input: PromptInput) => Effect.Effect<Admission, NotFoundError | PromptConflictError>
|
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. */
|
/** 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 interrupt: (sessionID: ID) => Effect.Effect<void>
|
||||||
readonly messages: (input: MessagesInput) => Effect.Effect<Message[], NotFoundError | MessageDecodeError>
|
readonly messages: (input: MessagesInput) => Effect.Effect<Message[], NotFoundError | MessageDecodeError>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { describe, expect } from "bun:test"
|
import { describe, expect } from "bun:test"
|
||||||
import { Effect, Schema } from "effect"
|
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"
|
import { testEffect } from "./lib/effect"
|
||||||
|
|
||||||
const it = testEffect(OpenCode.layer)
|
const it = testEffect(OpenCode.layer)
|
||||||
|
|
@ -22,6 +22,7 @@ describe("public native OpenCode API", () => {
|
||||||
"message",
|
"message",
|
||||||
"messages",
|
"messages",
|
||||||
"prompt",
|
"prompt",
|
||||||
|
"switchModel",
|
||||||
])
|
])
|
||||||
expect(Session.ID.create()).toStartWith("ses_")
|
expect(Session.ID.create()).toStartWith("ses_")
|
||||||
expect(Session.MessageID.create()).toStartWith("msg_")
|
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)
|
||||||
|
}),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue