feat: add experimental.compaction.autocontinue hook to disable auto continuing after compaction (#22361)
This commit is contained in:
@@ -310,6 +310,25 @@ When constructing the summary, try to stick to this template:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!replay) {
|
if (!replay) {
|
||||||
|
const info = yield* provider.getProvider(userMessage.model.providerID)
|
||||||
|
if (
|
||||||
|
(yield* plugin.trigger(
|
||||||
|
"experimental.compaction.autocontinue",
|
||||||
|
{
|
||||||
|
sessionID: input.sessionID,
|
||||||
|
agent: userMessage.agent,
|
||||||
|
model: yield* provider.getModel(userMessage.model.providerID, userMessage.model.modelID),
|
||||||
|
provider: {
|
||||||
|
source: info.source,
|
||||||
|
info,
|
||||||
|
options: info.options,
|
||||||
|
},
|
||||||
|
message: userMessage,
|
||||||
|
overflow: input.overflow === true,
|
||||||
|
},
|
||||||
|
{ enabled: true },
|
||||||
|
)).enabled
|
||||||
|
) {
|
||||||
const continueMsg = yield* session.updateMessage({
|
const continueMsg = yield* session.updateMessage({
|
||||||
id: MessageID.ascending(),
|
id: MessageID.ascending(),
|
||||||
role: "user",
|
role: "user",
|
||||||
@@ -337,6 +356,7 @@ When constructing the summary, try to stick to this template:
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (processor.message.error) return "stop"
|
if (processor.message.error) return "stop"
|
||||||
if (result === "continue") yield* bus.publish(Event.Compacted, { sessionID: input.sessionID })
|
if (result === "continue") yield* bus.publish(Event.Compacted, { sessionID: input.sessionID })
|
||||||
|
|||||||
@@ -244,6 +244,20 @@ function plugin(ready: ReturnType<typeof defer>) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function autocontinue(enabled: boolean) {
|
||||||
|
return Layer.mock(Plugin.Service)({
|
||||||
|
trigger: <Name extends string, Input, Output>(name: Name, _input: Input, output: Output) => {
|
||||||
|
if (name !== "experimental.compaction.autocontinue") return Effect.succeed(output)
|
||||||
|
return Effect.sync(() => {
|
||||||
|
;(output as { enabled: boolean }).enabled = enabled
|
||||||
|
return output
|
||||||
|
})
|
||||||
|
},
|
||||||
|
list: () => Effect.succeed([]),
|
||||||
|
init: () => Effect.void,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
describe("session.compaction.isOverflow", () => {
|
describe("session.compaction.isOverflow", () => {
|
||||||
test("returns true when token count exceeds usable context", async () => {
|
test("returns true when token count exceeds usable context", async () => {
|
||||||
await using tmp = await tmpdir()
|
await using tmp = await tmpdir()
|
||||||
@@ -671,6 +685,49 @@ describe("session.compaction.process", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("allows plugins to disable synthetic continue prompt", async () => {
|
||||||
|
await using tmp = await tmpdir()
|
||||||
|
await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => {
|
||||||
|
const session = await Session.create({})
|
||||||
|
const msg = await user(session.id, "hello")
|
||||||
|
const rt = runtime("continue", autocontinue(false), wide())
|
||||||
|
try {
|
||||||
|
const msgs = await Session.messages({ sessionID: session.id })
|
||||||
|
const result = await rt.runPromise(
|
||||||
|
SessionCompaction.Service.use((svc) =>
|
||||||
|
svc.process({
|
||||||
|
parentID: msg.id,
|
||||||
|
messages: msgs,
|
||||||
|
sessionID: session.id,
|
||||||
|
auto: true,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
const all = await Session.messages({ sessionID: session.id })
|
||||||
|
const last = all.at(-1)
|
||||||
|
|
||||||
|
expect(result).toBe("continue")
|
||||||
|
expect(last?.info.role).toBe("assistant")
|
||||||
|
expect(
|
||||||
|
all.some(
|
||||||
|
(msg) =>
|
||||||
|
msg.info.role === "user" &&
|
||||||
|
msg.parts.some(
|
||||||
|
(part) =>
|
||||||
|
part.type === "text" && part.synthetic && part.text.includes("Continue if you have next steps"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).toBe(false)
|
||||||
|
} finally {
|
||||||
|
await rt.dispose()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test("replays the prior user turn on overflow when earlier context exists", async () => {
|
test("replays the prior user turn on overflow when earlier context exists", async () => {
|
||||||
await using tmp = await tmpdir()
|
await using tmp = await tmpdir()
|
||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
|
|||||||
@@ -304,6 +304,24 @@ export interface Hooks {
|
|||||||
input: { sessionID: string },
|
input: { sessionID: string },
|
||||||
output: { context: string[]; prompt?: string },
|
output: { context: string[]; prompt?: string },
|
||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
|
/**
|
||||||
|
* Called after compaction succeeds and before a synthetic user
|
||||||
|
* auto-continue message is added.
|
||||||
|
*
|
||||||
|
* - `enabled`: Defaults to `true`. Set to `false` to skip the synthetic
|
||||||
|
* user "continue" turn.
|
||||||
|
*/
|
||||||
|
"experimental.compaction.autocontinue"?: (
|
||||||
|
input: {
|
||||||
|
sessionID: string
|
||||||
|
agent: string
|
||||||
|
model: Model
|
||||||
|
provider: ProviderContext
|
||||||
|
message: UserMessage
|
||||||
|
overflow: boolean
|
||||||
|
},
|
||||||
|
output: { enabled: boolean },
|
||||||
|
) => Promise<void>
|
||||||
"experimental.text.complete"?: (
|
"experimental.text.complete"?: (
|
||||||
input: { sessionID: string; messageID: string; partID: string },
|
input: { sessionID: string; messageID: string; partID: string },
|
||||||
output: { text: string },
|
output: { text: string },
|
||||||
|
|||||||
Reference in New Issue
Block a user