fix(session): distinguish idle reasons for completion and abort
parent
a1b06d63c9
commit
8084f9dfd8
|
|
@ -376,7 +376,7 @@ export const SessionRoutes = lazy(() =>
|
|||
}),
|
||||
),
|
||||
async (c) => {
|
||||
SessionPrompt.cancel(c.req.valid("param").sessionID)
|
||||
SessionPrompt.cancel(c.req.valid("param").sessionID, "aborted")
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -381,7 +381,10 @@ export namespace SessionProcessor {
|
|||
sessionID: input.assistantMessage.sessionID,
|
||||
error: input.assistantMessage.error,
|
||||
})
|
||||
SessionStatus.set(input.sessionID, { type: "idle" })
|
||||
SessionStatus.set(input.sessionID, {
|
||||
type: "idle",
|
||||
reason: error.name === "MessageAbortedError" ? "aborted" : "error",
|
||||
})
|
||||
}
|
||||
}
|
||||
if (snapshot) {
|
||||
|
|
|
|||
|
|
@ -254,17 +254,21 @@ export namespace SessionPrompt {
|
|||
return s[sessionID].abort.signal
|
||||
}
|
||||
|
||||
export function cancel(sessionID: string) {
|
||||
export function cancel(sessionID: string, reason: SessionStatus.IdleReason = "aborted") {
|
||||
log.info("cancel", { sessionID })
|
||||
const idle = () => {
|
||||
if (SessionStatus.get(sessionID).type === "idle") return
|
||||
SessionStatus.set(sessionID, { type: "idle", reason })
|
||||
}
|
||||
const s = state()
|
||||
const match = s[sessionID]
|
||||
if (!match) {
|
||||
SessionStatus.set(sessionID, { type: "idle" })
|
||||
idle()
|
||||
return
|
||||
}
|
||||
match.abort.abort()
|
||||
delete s[sessionID]
|
||||
SessionStatus.set(sessionID, { type: "idle" })
|
||||
idle()
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -283,7 +287,8 @@ export namespace SessionPrompt {
|
|||
})
|
||||
}
|
||||
|
||||
using _ = defer(() => cancel(sessionID))
|
||||
let reason: SessionStatus.IdleReason = "completed"
|
||||
using _ = defer(() => cancel(sessionID, reason))
|
||||
|
||||
// Structured output state
|
||||
// Note: On session resumption, state is reset but outputFormat is preserved
|
||||
|
|
@ -295,7 +300,10 @@ export namespace SessionPrompt {
|
|||
while (true) {
|
||||
SessionStatus.set(sessionID, { type: "busy" })
|
||||
log.info("loop", { step, sessionID })
|
||||
if (abort.aborted) break
|
||||
if (abort.aborted) {
|
||||
reason = "aborted"
|
||||
break
|
||||
}
|
||||
let msgs = await MessageV2.filterCompacted(MessageV2.stream(sessionID))
|
||||
|
||||
let lastUser: MessageV2.User | undefined
|
||||
|
|
@ -536,7 +544,10 @@ export namespace SessionPrompt {
|
|||
auto: task.auto,
|
||||
overflow: task.overflow,
|
||||
})
|
||||
if (result === "stop") break
|
||||
if (result === "stop") {
|
||||
reason = abort.aborted ? "aborted" : "completed"
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -698,11 +709,19 @@ export namespace SessionPrompt {
|
|||
retries: 0,
|
||||
}).toObject()
|
||||
await Session.updateMessage(processor.message)
|
||||
reason = "error"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (result === "stop") break
|
||||
if (result === "stop") {
|
||||
if (processor.message.error?.name === "MessageAbortedError") {
|
||||
reason = "aborted"
|
||||
} else if (processor.message.error) {
|
||||
reason = "error"
|
||||
}
|
||||
break
|
||||
}
|
||||
if (result === "compact") {
|
||||
await SessionCompaction.create({
|
||||
sessionID,
|
||||
|
|
|
|||
|
|
@ -4,10 +4,14 @@ import { Instance } from "@/project/instance"
|
|||
import z from "zod"
|
||||
|
||||
export namespace SessionStatus {
|
||||
export const IdleReason = z.enum(["completed", "aborted", "error"])
|
||||
export type IdleReason = z.infer<typeof IdleReason>
|
||||
|
||||
export const Info = z
|
||||
.union([
|
||||
z.object({
|
||||
type: z.literal("idle"),
|
||||
reason: IdleReason.optional(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("retry"),
|
||||
|
|
@ -65,9 +69,11 @@ export namespace SessionStatus {
|
|||
})
|
||||
if (status.type === "idle") {
|
||||
// deprecated
|
||||
Bus.publish(Event.Idle, {
|
||||
sessionID,
|
||||
})
|
||||
if (!status.reason || status.reason === "completed") {
|
||||
Bus.publish(Event.Idle, {
|
||||
sessionID,
|
||||
})
|
||||
}
|
||||
delete state()[sessionID]
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ export const TaskTool = Tool.define("task", async (ctx) => {
|
|||
const messageID = Identifier.ascending("message")
|
||||
|
||||
function cancel() {
|
||||
SessionPrompt.cancel(session.id)
|
||||
SessionPrompt.cancel(session.id, "aborted")
|
||||
}
|
||||
ctx.abort.addEventListener("abort", cancel)
|
||||
using _ = defer(() => ctx.abort.removeEventListener("abort", cancel))
|
||||
|
|
|
|||
|
|
@ -453,6 +453,7 @@ export type EventPermissionReplied = {
|
|||
export type SessionStatus =
|
||||
| {
|
||||
type: "idle"
|
||||
reason?: "completed" | "aborted" | "error"
|
||||
}
|
||||
| {
|
||||
type: "retry"
|
||||
|
|
|
|||
|
|
@ -581,6 +581,7 @@ export type EventPermissionReplied = {
|
|||
export type SessionStatus =
|
||||
| {
|
||||
type: "idle"
|
||||
reason?: "completed" | "aborted" | "error"
|
||||
}
|
||||
| {
|
||||
type: "retry"
|
||||
|
|
|
|||
Loading…
Reference in New Issue