Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions packages/scout-agent/lib/compaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,8 +400,8 @@ describe("applyCompactionToMessages", () => {
]);
const ids2 = result2.map((m) => m.id);
expect(ids2).toContain("1");
expect(ids2).not.toContain("2");
expect(ids2).not.toContain("2-assistant");
expect(ids2).toContain("2");
expect(ids2).toContain("2-assistant");
expect(ids2).not.toContain("2-assistant2");
expect(ids2).not.toContain("3");
});
Expand Down Expand Up @@ -443,7 +443,7 @@ describe("applyCompactionToMessages", () => {
test("replaces old messages with summary and excluded messages when compaction complete", () => {
const messages: Message[] = [
userMsg("kept", "Will be summarized"),
userMsg("excluded-1", "Will be excluded and restored"),
userMsg("kept-1", "Will be excluded and restored"),
assistantMsg("excluded-1-assistant", "Will be excluded and restored"),
markerMsg("marker-msg"),
summaryMsg("summary-msg", "Summary"),
Expand All @@ -453,7 +453,7 @@ describe("applyCompactionToMessages", () => {
const result = applyCompactionToMessages(messages);
const ids = result.map((m) => m.id);
expect(ids).not.toContain("kept");
expect(ids).toContain("excluded-1");
expect(ids).not.toContain("kept-1");
expect(ids).toContain("excluded-1-assistant");
expect(ids).not.toContain("marker-msg");
expect(ids).not.toContain("summary-msg");
Expand Down Expand Up @@ -663,7 +663,7 @@ describe("applyCompactionToMessages", () => {
expect(ids).not.toContain("1");
expect(ids).not.toContain("2");
// Messages excluded during compaction request should be restored
expect(ids).toContain("3");
expect(ids).not.toContain("3");
expect(ids).toContain("4");
expect(ids).toContain("interrupted"); // The interrupted user message is preserved
// Markers and summary message itself should be gone
Expand Down
30 changes: 23 additions & 7 deletions packages/scout-agent/lib/compaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,22 @@ export function isOutOfContextError(error: unknown): boolean {
if (!apiError) {
return false;
}
return OUT_OF_CONTEXT_PATTERNS.some((pattern) =>
pattern.test(
apiError.responseBody ?? util.inspect(apiError, { depth: null })
)
);
let textToTest = apiError.responseBody ?? "";
// even though typings say message is always a string, empirically it's not always a string
if (!textToTest && typeof apiError.message === "string") {
textToTest = apiError.message;
}
if (!textToTest) {
try {
textToTest = JSON.stringify(apiError);
} catch {
// note: util.inspect returns different values in Bun and Node.js
// in Node.js it includes the error message, in Bun it doesn't
// that's why it's the final fallback
textToTest = util.inspect(apiError, { depth: null });
}
}
return OUT_OF_CONTEXT_PATTERNS.some((pattern) => pattern.test(textToTest));
}

/**
Expand Down Expand Up @@ -157,6 +168,10 @@ function isCompactionMarkerPart(part: Message["parts"][number]): boolean {
);
}

function isCompactionMarkerMessage(message: Message): boolean {
return message.parts.some((part) => isCompactionMarkerPart(part));
}

/**
* Check if a message part is a compaction summary.
*/
Expand Down Expand Up @@ -404,8 +419,9 @@ function findExcludedMessagesStartIndex(
let lastUserIndex = messages.length;
let found = 0;
for (let i = messages.length - 1; i >= 0; i--) {
const message = messages[i];
if (message?.role !== "user") {
// biome-ignore lint/style/noNonNullAssertion: we know the message is not null
const message = messages[i]!;
if (isCompactionMarkerMessage(message)) {
continue;
}
lastUserIndex = i;
Expand Down
6 changes: 4 additions & 2 deletions packages/scout-agent/lib/core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,7 @@ describe("compaction", () => {
const scout = new Scout({ agent, logger: noopLogger });
agent.on("chat", async ({ messages }) => {
const params = await scout.buildStreamTextParams({
systemPrompt: "<system>hello</system>",
chatID,
messages,
model,
Expand Down Expand Up @@ -1455,7 +1456,7 @@ describe("compaction", () => {
{
id: "msg-3",
role: "user",
parts: [{ type: "text", text: "Second message - will be excluded" }],
parts: [{ type: "text", text: "Second message to summarize" }],
},
],
});
Expand All @@ -1478,14 +1479,15 @@ describe("compaction", () => {
// Verify: only 1 marker, so excludeCount=1
const params = await scout.buildStreamTextParams({
chatID,
systemPrompt: "system",
messages: helper.messages as Message[],
model,
});
const allContent = extractAllContent(params);

expect(allContent).toContain("CONVERSATION SUMMARY");
expect(allContent).toContain("Summary after non-context error");
expect(allContent).toContain("Second message - will be excluded"); // restored
expect(allContent).not.toContain("Second message to summarize"); // restored
expect(allContent).not.toContain("First message to summarize"); // summarized
expect(allContent).not.toContain("First response to summarize"); // summarized
expect(allContent).toContain("Retry after network error"); // added after
Expand Down
2 changes: 1 addition & 1 deletion packages/scout-agent/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@blink-sdk/scout-agent",
"description": "A general-purpose AI agent with GitHub, Slack, web search, and compute capabilities built on Blink SDK.",
"version": "0.0.13",
"version": "0.0.14",
"type": "module",
"keywords": [
"blink",
Expand Down