Persist session model switches outside event flag (#26765)

This commit is contained in:
Dax
2026-05-10 18:53:14 -04:00
committed by GitHub
parent 2ba2ee52e8
commit 0fffcdfe46
3 changed files with 65 additions and 38 deletions
+44 -26
View File
@@ -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() }) yield* bus.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() })
throw error 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 = { const userMsg: MessageV2.User = {
id: input.messageID ?? MessageID.ascending(), id: input.messageID ?? MessageID.ascending(),
sessionID: input.sessionID, 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) 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) 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 if (Option.isSome(match) && match.value.info.role === "user") return match.value.info.model
return yield* provider.defaultModel() 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 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 same = ag.model && model.providerID === ag.model.providerID && model.modelID === ag.model.modelID
const full = const full =
!input.variant && ag.variant && same !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, 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) { if (current?.agent !== info.agent) {
EventV2.run(SessionEvent.AgentSwitched.Sync, { EventV2.run(
sessionID: input.sessionID, SessionEvent.AgentSwitched.Sync,
timestamp: DateTime.makeUnsafe(info.time.created), {
agent: info.agent, sessionID: input.sessionID,
}) timestamp: DateTime.makeUnsafe(info.time.created),
agent: info.agent,
},
{ bypassExperimentalEventSystem: true },
)
} }
if ( if (
current?.model?.providerID !== info.model.providerID || current?.model?.providerID !== info.model.providerID ||
current.model.id !== info.model.modelID || 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, { EventV2.run(
sessionID: input.sessionID, SessionEvent.ModelSwitched.Sync,
timestamp: DateTime.makeUnsafe(info.time.created), {
model: { sessionID: input.sessionID,
id: Modelv2.ID.make(info.model.modelID), timestamp: DateTime.makeUnsafe(info.time.created),
providerID: Modelv2.ProviderID.make(info.model.providerID), model: {
variant: Modelv2.VariantID.make(info.model.variant ?? "default"), 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)) 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 (cmdAgent?.model) return cmdAgent.model
} }
if (input.model) return Provider.parseModel(input.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) 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 const userModel = isSubtask
? input.model ? input.model
? Provider.parseModel(input.model) ? Provider.parseModel(input.model)
: yield* lastModel(input.sessionID) : yield* currentModel(input.sessionID)
: taskModel : taskModel
yield* plugin.trigger( yield* plugin.trigger(
+3 -2
View File
@@ -44,9 +44,10 @@ export function define<const Type extends string, Fields extends Schema.Struct.F
export function run<Def extends SyncEvent.Definition>( export function run<Def extends SyncEvent.Definition>(
def: Def, def: Def,
data: SyncEvent.Event<Def>["data"], data: SyncEvent.Event<Def>["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) SyncEvent.run(def, data, options)
} }
+18 -10
View File
@@ -269,18 +269,26 @@ export const layer = Layer.effect(
shell: Effect.fn("V2Session.shell")(function* (_input) {}), shell: Effect.fn("V2Session.shell")(function* (_input) {}),
skill: Effect.fn("V2Session.skill")(function* (_input) {}), skill: Effect.fn("V2Session.skill")(function* (_input) {}),
switchAgent: Effect.fn("V2Session.switchAgent")(function* (input) { switchAgent: Effect.fn("V2Session.switchAgent")(function* (input) {
EventV2.run(SessionEvent.AgentSwitched.Sync, { EventV2.run(
sessionID: input.sessionID, SessionEvent.AgentSwitched.Sync,
timestamp: DateTime.makeUnsafe(Date.now()), {
agent: input.agent, sessionID: input.sessionID,
}) timestamp: DateTime.makeUnsafe(Date.now()),
agent: input.agent,
},
{ bypassExperimentalEventSystem: true },
)
}), }),
switchModel: Effect.fn("V2Session.switchModel")(function* (input) { switchModel: Effect.fn("V2Session.switchModel")(function* (input) {
EventV2.run(SessionEvent.ModelSwitched.Sync, { EventV2.run(
sessionID: input.sessionID, SessionEvent.ModelSwitched.Sync,
timestamp: DateTime.makeUnsafe(Date.now()), {
model: input.model, sessionID: input.sessionID,
}) timestamp: DateTime.makeUnsafe(Date.now()),
model: input.model,
},
{ bypassExperimentalEventSystem: true },
)
}), }),
subagent: Effect.fn("V2Session.subagent")(function* (input) { subagent: Effect.fn("V2Session.subagent")(function* (input) {
const parent = yield* result.get(input.parentID) const parent = yield* result.get(input.parentID)