From 0fffcdfe4660ba826be9705313a0c6441bf62e12 Mon Sep 17 00:00:00 2001 From: Dax Date: Sun, 10 May 2026 18:53:14 -0400 Subject: [PATCH] Persist session model switches outside event flag (#26765) --- packages/opencode/src/session/prompt.ts | 70 ++++++++++++++++--------- packages/opencode/src/v2/event.ts | 5 +- packages/opencode/src/v2/session.ts | 28 ++++++---- 3 files changed, 65 insertions(+), 38 deletions(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 5cf04719e5..521bacbc2f 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -758,7 +758,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the yield* bus.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() }) throw error } - const model = input.model ?? agent.model ?? (yield* lastModel(input.sessionID)) + const model = input.model ?? agent.model ?? (yield* currentModel(input.sessionID)) const userMsg: MessageV2.User = { id: input.messageID ?? MessageID.ascending(), sessionID: input.sessionID, @@ -916,7 +916,17 @@ NOTE: At any point in time through this workflow you should feel free to ask the return yield* Effect.failCause(exit.cause) }) - const lastModel = Effect.fnUntraced(function* (sessionID: SessionID) { + const currentModel = Effect.fnUntraced(function* (sessionID: SessionID) { + const current = Database.use((db) => + db.select({ model: SessionTable.model }).from(SessionTable).where(eq(SessionTable.id, sessionID)).get(), + ) + if (current?.model) { + return { + providerID: ProviderID.make(current.model.providerID), + modelID: ModelID.make(current.model.id), + ...(current.model.variant && current.model.variant !== "default" ? { variant: current.model.variant } : {}), + } + } const match = yield* sessions.findMessage(sessionID, (m) => m.info.role === "user" && !!m.info.model) if (Option.isSome(match) && match.value.info.role === "user") return match.value.info.model return yield* provider.defaultModel() @@ -933,7 +943,14 @@ NOTE: At any point in time through this workflow you should feel free to ask the throw error } - const model = input.model ?? ag.model ?? (yield* lastModel(input.sessionID)) + const current = Database.use((db) => + db + .select({ agent: SessionTable.agent, model: SessionTable.model }) + .from(SessionTable) + .where(eq(SessionTable.id, input.sessionID)) + .get(), + ) + const model = input.model ?? ag.model ?? (yield* currentModel(input.sessionID)) const same = ag.model && model.providerID === ag.model.providerID && model.modelID === ag.model.modelID const full = !input.variant && ag.variant && same @@ -957,34 +974,35 @@ NOTE: At any point in time through this workflow you should feel free to ask the format: input.format, } - const current = Database.use((db) => - db - .select({ agent: SessionTable.agent, model: SessionTable.model }) - .from(SessionTable) - .where(eq(SessionTable.id, input.sessionID)) - .get(), - ) if (current?.agent !== info.agent) { - EventV2.run(SessionEvent.AgentSwitched.Sync, { - sessionID: input.sessionID, - timestamp: DateTime.makeUnsafe(info.time.created), - agent: info.agent, - }) + EventV2.run( + SessionEvent.AgentSwitched.Sync, + { + sessionID: input.sessionID, + timestamp: DateTime.makeUnsafe(info.time.created), + agent: info.agent, + }, + { bypassExperimentalEventSystem: true }, + ) } if ( current?.model?.providerID !== info.model.providerID || current.model.id !== info.model.modelID || - current.model.variant !== info.model.variant + (current.model.variant === "default" ? undefined : current.model.variant) !== info.model.variant ) { - EventV2.run(SessionEvent.ModelSwitched.Sync, { - sessionID: input.sessionID, - timestamp: DateTime.makeUnsafe(info.time.created), - model: { - id: Modelv2.ID.make(info.model.modelID), - providerID: Modelv2.ProviderID.make(info.model.providerID), - variant: Modelv2.VariantID.make(info.model.variant ?? "default"), + EventV2.run( + SessionEvent.ModelSwitched.Sync, + { + sessionID: input.sessionID, + timestamp: DateTime.makeUnsafe(info.time.created), + model: { + id: Modelv2.ID.make(info.model.modelID), + providerID: Modelv2.ProviderID.make(info.model.providerID), + variant: Modelv2.VariantID.make(info.model.variant ?? "default"), + }, }, - }) + { bypassExperimentalEventSystem: true }, + ) } yield* Effect.addFinalizer(() => instruction.clear(info.id)) @@ -1704,7 +1722,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the if (cmdAgent?.model) return cmdAgent.model } if (input.model) return Provider.parseModel(input.model) - return yield* lastModel(input.sessionID) + return yield* currentModel(input.sessionID) }) yield* getModel(taskModel.providerID, taskModel.modelID, input.sessionID) @@ -1737,7 +1755,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the const userModel = isSubtask ? input.model ? Provider.parseModel(input.model) - : yield* lastModel(input.sessionID) + : yield* currentModel(input.sessionID) : taskModel yield* plugin.trigger( diff --git a/packages/opencode/src/v2/event.ts b/packages/opencode/src/v2/event.ts index 8d0562eba3..0eb6af966c 100644 --- a/packages/opencode/src/v2/event.ts +++ b/packages/opencode/src/v2/event.ts @@ -44,9 +44,10 @@ export function define( def: Def, data: SyncEvent.Event["data"], - options?: { publish?: boolean }, + // Temporary escape hatch while the full v2 event system remains experimental. + options?: { publish?: boolean; bypassExperimentalEventSystem?: boolean }, ) { - if (!Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) return + if (!options?.bypassExperimentalEventSystem && !Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) return SyncEvent.run(def, data, options) } diff --git a/packages/opencode/src/v2/session.ts b/packages/opencode/src/v2/session.ts index b3da6009f6..39deabfc7c 100644 --- a/packages/opencode/src/v2/session.ts +++ b/packages/opencode/src/v2/session.ts @@ -269,18 +269,26 @@ export const layer = Layer.effect( shell: Effect.fn("V2Session.shell")(function* (_input) {}), skill: Effect.fn("V2Session.skill")(function* (_input) {}), switchAgent: Effect.fn("V2Session.switchAgent")(function* (input) { - EventV2.run(SessionEvent.AgentSwitched.Sync, { - sessionID: input.sessionID, - timestamp: DateTime.makeUnsafe(Date.now()), - agent: input.agent, - }) + EventV2.run( + SessionEvent.AgentSwitched.Sync, + { + sessionID: input.sessionID, + timestamp: DateTime.makeUnsafe(Date.now()), + agent: input.agent, + }, + { bypassExperimentalEventSystem: true }, + ) }), switchModel: Effect.fn("V2Session.switchModel")(function* (input) { - EventV2.run(SessionEvent.ModelSwitched.Sync, { - sessionID: input.sessionID, - timestamp: DateTime.makeUnsafe(Date.now()), - model: input.model, - }) + EventV2.run( + SessionEvent.ModelSwitched.Sync, + { + sessionID: input.sessionID, + timestamp: DateTime.makeUnsafe(Date.now()), + model: input.model, + }, + { bypassExperimentalEventSystem: true }, + ) }), subagent: Effect.fn("V2Session.subagent")(function* (input) { const parent = yield* result.get(input.parentID)