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

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() })
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(

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>(
def: Def,
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)
}

View File

@@ -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)