Skip to content

Commit 5a83d63

Browse files
authored
feat(scout-agent): more granular message exclusion during compaction (#113)
1 parent 0310fd6 commit 5a83d63

File tree

4 files changed

+33
-15
lines changed

4 files changed

+33
-15
lines changed

packages/scout-agent/lib/compaction.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -400,8 +400,8 @@ describe("applyCompactionToMessages", () => {
400400
]);
401401
const ids2 = result2.map((m) => m.id);
402402
expect(ids2).toContain("1");
403-
expect(ids2).not.toContain("2");
404-
expect(ids2).not.toContain("2-assistant");
403+
expect(ids2).toContain("2");
404+
expect(ids2).toContain("2-assistant");
405405
expect(ids2).not.toContain("2-assistant2");
406406
expect(ids2).not.toContain("3");
407407
});
@@ -443,7 +443,7 @@ describe("applyCompactionToMessages", () => {
443443
test("replaces old messages with summary and excluded messages when compaction complete", () => {
444444
const messages: Message[] = [
445445
userMsg("kept", "Will be summarized"),
446-
userMsg("excluded-1", "Will be excluded and restored"),
446+
userMsg("kept-1", "Will be excluded and restored"),
447447
assistantMsg("excluded-1-assistant", "Will be excluded and restored"),
448448
markerMsg("marker-msg"),
449449
summaryMsg("summary-msg", "Summary"),
@@ -453,7 +453,7 @@ describe("applyCompactionToMessages", () => {
453453
const result = applyCompactionToMessages(messages);
454454
const ids = result.map((m) => m.id);
455455
expect(ids).not.toContain("kept");
456-
expect(ids).toContain("excluded-1");
456+
expect(ids).not.toContain("kept-1");
457457
expect(ids).toContain("excluded-1-assistant");
458458
expect(ids).not.toContain("marker-msg");
459459
expect(ids).not.toContain("summary-msg");
@@ -663,7 +663,7 @@ describe("applyCompactionToMessages", () => {
663663
expect(ids).not.toContain("1");
664664
expect(ids).not.toContain("2");
665665
// Messages excluded during compaction request should be restored
666-
expect(ids).toContain("3");
666+
expect(ids).not.toContain("3");
667667
expect(ids).toContain("4");
668668
expect(ids).toContain("interrupted"); // The interrupted user message is preserved
669669
// Markers and summary message itself should be gone

packages/scout-agent/lib/compaction.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,22 @@ export function isOutOfContextError(error: unknown): boolean {
5454
if (!apiError) {
5555
return false;
5656
}
57-
return OUT_OF_CONTEXT_PATTERNS.some((pattern) =>
58-
pattern.test(
59-
apiError.responseBody ?? util.inspect(apiError, { depth: null })
60-
)
61-
);
57+
let textToTest = apiError.responseBody ?? "";
58+
// even though typings say message is always a string, empirically it's not always a string
59+
if (!textToTest && typeof apiError.message === "string") {
60+
textToTest = apiError.message;
61+
}
62+
if (!textToTest) {
63+
try {
64+
textToTest = JSON.stringify(apiError);
65+
} catch {
66+
// note: util.inspect returns different values in Bun and Node.js
67+
// in Node.js it includes the error message, in Bun it doesn't
68+
// that's why it's the final fallback
69+
textToTest = util.inspect(apiError, { depth: null });
70+
}
71+
}
72+
return OUT_OF_CONTEXT_PATTERNS.some((pattern) => pattern.test(textToTest));
6273
}
6374

6475
/**
@@ -157,6 +168,10 @@ function isCompactionMarkerPart(part: Message["parts"][number]): boolean {
157168
);
158169
}
159170

171+
function isCompactionMarkerMessage(message: Message): boolean {
172+
return message.parts.some((part) => isCompactionMarkerPart(part));
173+
}
174+
160175
/**
161176
* Check if a message part is a compaction summary.
162177
*/
@@ -404,8 +419,9 @@ function findExcludedMessagesStartIndex(
404419
let lastUserIndex = messages.length;
405420
let found = 0;
406421
for (let i = messages.length - 1; i >= 0; i--) {
407-
const message = messages[i];
408-
if (message?.role !== "user") {
422+
// biome-ignore lint/style/noNonNullAssertion: we know the message is not null
423+
const message = messages[i]!;
424+
if (isCompactionMarkerMessage(message)) {
409425
continue;
410426
}
411427
lastUserIndex = i;

packages/scout-agent/lib/core.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,6 +1091,7 @@ describe("compaction", () => {
10911091
const scout = new Scout({ agent, logger: noopLogger });
10921092
agent.on("chat", async ({ messages }) => {
10931093
const params = await scout.buildStreamTextParams({
1094+
systemPrompt: "<system>hello</system>",
10941095
chatID,
10951096
messages,
10961097
model,
@@ -1455,7 +1456,7 @@ describe("compaction", () => {
14551456
{
14561457
id: "msg-3",
14571458
role: "user",
1458-
parts: [{ type: "text", text: "Second message - will be excluded" }],
1459+
parts: [{ type: "text", text: "Second message to summarize" }],
14591460
},
14601461
],
14611462
});
@@ -1478,14 +1479,15 @@ describe("compaction", () => {
14781479
// Verify: only 1 marker, so excludeCount=1
14791480
const params = await scout.buildStreamTextParams({
14801481
chatID,
1482+
systemPrompt: "system",
14811483
messages: helper.messages as Message[],
14821484
model,
14831485
});
14841486
const allContent = extractAllContent(params);
14851487

14861488
expect(allContent).toContain("CONVERSATION SUMMARY");
14871489
expect(allContent).toContain("Summary after non-context error");
1488-
expect(allContent).toContain("Second message - will be excluded"); // restored
1490+
expect(allContent).not.toContain("Second message to summarize"); // restored
14891491
expect(allContent).not.toContain("First message to summarize"); // summarized
14901492
expect(allContent).not.toContain("First response to summarize"); // summarized
14911493
expect(allContent).toContain("Retry after network error"); // added after

packages/scout-agent/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@blink-sdk/scout-agent",
33
"description": "A general-purpose AI agent with GitHub, Slack, web search, and compute capabilities built on Blink SDK.",
4-
"version": "0.0.13",
4+
"version": "0.0.14",
55
"type": "module",
66
"keywords": [
77
"blink",

0 commit comments

Comments
 (0)