@bastani/atomic 0.8.26 → 0.8.27-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +1 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/context-compaction.d.ts +1 -2
- package/dist/core/compaction/context-compaction.d.ts.map +1 -1
- package/dist/core/compaction/context-compaction.js +118 -177
- package/dist/core/compaction/context-compaction.js.map +1 -1
- package/package.json +1 -3
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Agent } from "@earendil-works/pi-agent-core";
|
|
2
|
-
import { createAssistantMessageEventStream,
|
|
2
|
+
import { createAssistantMessageEventStream, isContextOverflow, streamSimple, StringEnum, } from "@earendil-works/pi-ai";
|
|
3
3
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { createRequire } from "node:module";
|
|
5
4
|
import { tmpdir } from "node:os";
|
|
6
5
|
import { join } from "node:path";
|
|
7
6
|
import { Type } from "typebox";
|
|
@@ -9,7 +8,6 @@ import { createBranchSummaryMessage, createCompactionSummaryMessage, createCusto
|
|
|
9
8
|
import { buildContextDeletionFilteredPath, buildContextDeletionFilters, } from "../session-manager.js";
|
|
10
9
|
import { estimateTokens } from "./compaction.js";
|
|
11
10
|
export const CONTEXT_COMPACTION_PROMPT_VERSION = 1;
|
|
12
|
-
const CONTEXT_COMPACTION_THINKING_LEVEL_ORDER = ["off", "minimal", "low", "medium", "high", "xhigh"];
|
|
13
11
|
const CONTEXT_DELETE_TOOL_NAME = "context_delete";
|
|
14
12
|
const CONTEXT_GREP_DELETE_TOOL_NAME = "context_grep_delete";
|
|
15
13
|
const CONTEXT_SEARCH_TRANSCRIPT_TOOL_NAME = "context_search_transcript";
|
|
@@ -854,204 +852,156 @@ function addGrepCandidate(candidates, matches, seenTargets, candidate, match) {
|
|
|
854
852
|
candidates.push(candidate);
|
|
855
853
|
matches.push(match);
|
|
856
854
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
855
|
+
function copyDeletionTarget(target) {
|
|
856
|
+
return target.kind === "entry"
|
|
857
|
+
? { kind: "entry", entryId: target.entryId }
|
|
858
|
+
: { kind: "content_block", entryId: target.entryId, blockIndex: target.blockIndex };
|
|
859
|
+
}
|
|
860
|
+
class ContextDeletionMemoryStore {
|
|
861
|
+
constructor(transcript) {
|
|
862
|
+
this.deletionTargets = [];
|
|
863
|
+
this.callCount = 0;
|
|
864
|
+
const entryIds = new Set();
|
|
865
|
+
const blockKeys = new Set();
|
|
866
|
+
this.entries = transcript.entries.map((entry) => {
|
|
867
|
+
if (entryIds.has(entry.entryId)) {
|
|
868
|
+
throw new Error(`Duplicate transcript entry id: ${entry.entryId}`);
|
|
869
|
+
}
|
|
870
|
+
entryIds.add(entry.entryId);
|
|
871
|
+
return {
|
|
872
|
+
entryId: entry.entryId,
|
|
873
|
+
role: entry.role,
|
|
874
|
+
protected: entry.protected,
|
|
875
|
+
tokenEstimate: entry.tokenEstimate,
|
|
876
|
+
text: entry.text,
|
|
877
|
+
};
|
|
878
|
+
});
|
|
879
|
+
this.entriesById = new Map(this.entries.map((entry) => [entry.entryId, entry]));
|
|
880
|
+
this.contentBlocks = transcript.entries.flatMap((entry, entryPosition) => entry.contentBlocks.map((block) => {
|
|
881
|
+
if (block.entryId !== entry.entryId) {
|
|
882
|
+
throw new Error(`Transcript content block ${block.entryId}:${block.blockIndex} does not belong to entry ${entry.entryId}`);
|
|
883
|
+
}
|
|
884
|
+
const blockKey = `${block.entryId}:${block.blockIndex}`;
|
|
885
|
+
if (blockKeys.has(blockKey)) {
|
|
886
|
+
throw new Error(`Duplicate transcript content block: ${blockKey}`);
|
|
887
|
+
}
|
|
888
|
+
blockKeys.add(blockKey);
|
|
889
|
+
return {
|
|
890
|
+
entryPosition,
|
|
891
|
+
entryId: block.entryId,
|
|
892
|
+
blockIndex: block.blockIndex,
|
|
893
|
+
type: block.type,
|
|
894
|
+
protected: block.protected,
|
|
895
|
+
tokenEstimate: block.tokenEstimate,
|
|
896
|
+
text: block.text,
|
|
897
|
+
};
|
|
898
|
+
}));
|
|
899
|
+
this.contentBlockCountByEntryId = new Map();
|
|
900
|
+
for (const block of this.contentBlocks) {
|
|
901
|
+
this.contentBlockCountByEntryId.set(block.entryId, (this.contentBlockCountByEntryId.get(block.entryId) ?? 0) + 1);
|
|
902
|
+
}
|
|
886
903
|
}
|
|
887
904
|
transaction(operation) {
|
|
888
|
-
this.
|
|
905
|
+
const snapshot = this.snapshot();
|
|
889
906
|
try {
|
|
890
|
-
|
|
891
|
-
this.exec("COMMIT");
|
|
892
|
-
return result;
|
|
907
|
+
return operation();
|
|
893
908
|
}
|
|
894
909
|
catch (error) {
|
|
895
|
-
|
|
896
|
-
this.exec("ROLLBACK");
|
|
897
|
-
}
|
|
898
|
-
catch { }
|
|
910
|
+
this.restore(snapshot);
|
|
899
911
|
throw error;
|
|
900
912
|
}
|
|
901
913
|
}
|
|
902
|
-
close() {
|
|
903
|
-
this.db.close();
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
class ContextDeletionSqliteStore {
|
|
907
|
-
constructor(sqlite) {
|
|
908
|
-
this.sqlite = sqlite;
|
|
909
|
-
}
|
|
910
|
-
initialize(transcript) {
|
|
911
|
-
this.sqlite.transaction(() => {
|
|
912
|
-
this.sqlite.exec(`
|
|
913
|
-
PRAGMA foreign_keys = ON;
|
|
914
|
-
CREATE TABLE transcript_entries (
|
|
915
|
-
position INTEGER PRIMARY KEY,
|
|
916
|
-
entry_id TEXT NOT NULL UNIQUE,
|
|
917
|
-
role TEXT NOT NULL,
|
|
918
|
-
is_protected INTEGER NOT NULL,
|
|
919
|
-
token_estimate INTEGER NOT NULL,
|
|
920
|
-
text TEXT NOT NULL,
|
|
921
|
-
tool_result_for TEXT
|
|
922
|
-
);
|
|
923
|
-
CREATE TABLE transcript_content_blocks (
|
|
924
|
-
entry_id TEXT NOT NULL,
|
|
925
|
-
block_index INTEGER NOT NULL,
|
|
926
|
-
type TEXT NOT NULL,
|
|
927
|
-
is_protected INTEGER NOT NULL,
|
|
928
|
-
token_estimate INTEGER NOT NULL,
|
|
929
|
-
text TEXT NOT NULL,
|
|
930
|
-
tool_call_id TEXT,
|
|
931
|
-
PRIMARY KEY (entry_id, block_index),
|
|
932
|
-
FOREIGN KEY (entry_id) REFERENCES transcript_entries(entry_id) ON DELETE CASCADE
|
|
933
|
-
);
|
|
934
|
-
CREATE TABLE deletion_targets (
|
|
935
|
-
position INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
936
|
-
target_key TEXT NOT NULL UNIQUE,
|
|
937
|
-
kind TEXT NOT NULL CHECK (kind IN ('entry', 'content_block')),
|
|
938
|
-
entry_id TEXT NOT NULL,
|
|
939
|
-
block_index INTEGER
|
|
940
|
-
);
|
|
941
|
-
CREATE TABLE context_compaction_state (
|
|
942
|
-
key TEXT PRIMARY KEY,
|
|
943
|
-
value TEXT NOT NULL
|
|
944
|
-
);
|
|
945
|
-
INSERT INTO context_compaction_state (key, value) VALUES ('call_count', '0');
|
|
946
|
-
`);
|
|
947
|
-
for (const [position, entry] of transcript.entries.entries()) {
|
|
948
|
-
this.sqlite.run("INSERT INTO transcript_entries (position, entry_id, role, is_protected, token_estimate, text, tool_result_for) VALUES (?, ?, ?, ?, ?, ?, ?)", position, entry.entryId, entry.role, entry.protected ? 1 : 0, entry.tokenEstimate, entry.text, entry.toolResultFor ?? null);
|
|
949
|
-
for (const block of entry.contentBlocks) {
|
|
950
|
-
this.sqlite.run("INSERT INTO transcript_content_blocks (entry_id, block_index, type, is_protected, token_estimate, text, tool_call_id) VALUES (?, ?, ?, ?, ?, ?, ?)", block.entryId, block.blockIndex, block.type, block.protected ? 1 : 0, block.tokenEstimate, block.text, block.toolCallId ?? null);
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
});
|
|
954
|
-
}
|
|
955
|
-
transaction(operation) {
|
|
956
|
-
return this.sqlite.transaction(operation);
|
|
957
|
-
}
|
|
958
914
|
readTargets() {
|
|
959
|
-
return this.
|
|
960
|
-
.all("SELECT kind, entry_id, block_index FROM deletion_targets ORDER BY position")
|
|
961
|
-
.map((row) => row.kind === "entry"
|
|
962
|
-
? { kind: "entry", entryId: row.entry_id }
|
|
963
|
-
: { kind: "content_block", entryId: row.entry_id, blockIndex: row.block_index });
|
|
915
|
+
return this.deletionTargets.map(copyDeletionTarget);
|
|
964
916
|
}
|
|
965
917
|
replaceTargets(targets) {
|
|
966
|
-
this.
|
|
967
|
-
for (const target of targets) {
|
|
968
|
-
this.sqlite.run("INSERT INTO deletion_targets (target_key, kind, entry_id, block_index) VALUES (?, ?, ?, ?)", targetKey(target), target.kind, target.entryId, target.kind === "content_block" ? target.blockIndex : null);
|
|
969
|
-
}
|
|
918
|
+
this.deletionTargets = targets.map(copyDeletionTarget);
|
|
970
919
|
}
|
|
971
920
|
listEntriesForGrep() {
|
|
972
|
-
return this.
|
|
921
|
+
return this.entries.map((entry) => ({
|
|
922
|
+
entry_id: entry.entryId,
|
|
923
|
+
text: entry.text,
|
|
924
|
+
is_protected: entry.protected ? 1 : 0,
|
|
925
|
+
}));
|
|
973
926
|
}
|
|
974
927
|
listContentBlocksForGrep() {
|
|
975
|
-
return this.
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
WHERE sibling.entry_id = blocks.entry_id
|
|
986
|
-
) AS block_count
|
|
987
|
-
FROM transcript_content_blocks blocks
|
|
988
|
-
JOIN transcript_entries entries ON entries.entry_id = blocks.entry_id
|
|
989
|
-
ORDER BY entries.position, blocks.block_index
|
|
990
|
-
`);
|
|
928
|
+
return [...this.contentBlocks]
|
|
929
|
+
.sort((a, b) => a.entryPosition - b.entryPosition || a.blockIndex - b.blockIndex)
|
|
930
|
+
.map((block) => ({
|
|
931
|
+
entry_id: block.entryId,
|
|
932
|
+
block_index: block.blockIndex,
|
|
933
|
+
text: block.text,
|
|
934
|
+
entry_protected: this.entriesById.get(block.entryId)?.protected ? 1 : 0,
|
|
935
|
+
block_protected: block.protected ? 1 : 0,
|
|
936
|
+
block_count: this.contentBlockCountByEntryId.get(block.entryId) ?? 0,
|
|
937
|
+
}));
|
|
991
938
|
}
|
|
992
939
|
getEntryForRead(entryId) {
|
|
993
|
-
|
|
940
|
+
const entry = this.entriesById.get(entryId);
|
|
941
|
+
if (!entry)
|
|
942
|
+
return undefined;
|
|
943
|
+
return {
|
|
944
|
+
entry_id: entry.entryId,
|
|
945
|
+
role: entry.role,
|
|
946
|
+
is_protected: entry.protected ? 1 : 0,
|
|
947
|
+
token_estimate: entry.tokenEstimate,
|
|
948
|
+
text: entry.text,
|
|
949
|
+
};
|
|
994
950
|
}
|
|
995
951
|
getContentBlockForRead(entryId, blockIndex) {
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
) AS block_count
|
|
1010
|
-
FROM transcript_content_blocks blocks
|
|
1011
|
-
JOIN transcript_entries entries ON entries.entry_id = blocks.entry_id
|
|
1012
|
-
WHERE blocks.entry_id = ? AND blocks.block_index = ?
|
|
1013
|
-
`, entryId, blockIndex);
|
|
952
|
+
const block = this.contentBlocks.find((candidate) => candidate.entryId === entryId && candidate.blockIndex === blockIndex);
|
|
953
|
+
if (!block)
|
|
954
|
+
return undefined;
|
|
955
|
+
return {
|
|
956
|
+
entry_id: block.entryId,
|
|
957
|
+
block_index: block.blockIndex,
|
|
958
|
+
type: block.type,
|
|
959
|
+
token_estimate: block.tokenEstimate,
|
|
960
|
+
text: block.text,
|
|
961
|
+
entry_protected: this.entriesById.get(block.entryId)?.protected ? 1 : 0,
|
|
962
|
+
block_protected: block.protected ? 1 : 0,
|
|
963
|
+
block_count: this.contentBlockCountByEntryId.get(block.entryId) ?? 0,
|
|
964
|
+
};
|
|
1014
965
|
}
|
|
1015
966
|
getGrepScanTextLength(target) {
|
|
1016
|
-
const
|
|
1017
|
-
|
|
1018
|
-
return row?.scan_chars ?? 0;
|
|
967
|
+
const texts = target === "entry" ? this.entries : this.contentBlocks;
|
|
968
|
+
return texts.reduce((sum, item) => sum + item.text.length, 0);
|
|
1019
969
|
}
|
|
1020
970
|
incrementCallCount() {
|
|
1021
|
-
|
|
1022
|
-
this.
|
|
1023
|
-
return next;
|
|
971
|
+
this.callCount += 1;
|
|
972
|
+
return this.callCount;
|
|
1024
973
|
}
|
|
1025
974
|
getCallCount() {
|
|
1026
|
-
return
|
|
975
|
+
return this.callCount;
|
|
1027
976
|
}
|
|
1028
977
|
setLastError(message) {
|
|
1029
|
-
this.
|
|
978
|
+
this.lastError = message;
|
|
1030
979
|
}
|
|
1031
980
|
clearLastError() {
|
|
1032
|
-
this.
|
|
981
|
+
this.lastError = undefined;
|
|
1033
982
|
}
|
|
1034
983
|
getLastError() {
|
|
1035
|
-
return this.
|
|
984
|
+
return this.lastError;
|
|
1036
985
|
}
|
|
1037
|
-
|
|
1038
|
-
return
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
986
|
+
snapshot() {
|
|
987
|
+
return {
|
|
988
|
+
deletionTargets: this.readTargets(),
|
|
989
|
+
callCount: this.callCount,
|
|
990
|
+
...(this.lastError === undefined ? {} : { lastError: this.lastError }),
|
|
991
|
+
};
|
|
1042
992
|
}
|
|
1043
|
-
|
|
1044
|
-
this.
|
|
993
|
+
restore(snapshot) {
|
|
994
|
+
this.deletionTargets = snapshot.deletionTargets.map(copyDeletionTarget);
|
|
995
|
+
this.callCount = snapshot.callCount;
|
|
996
|
+
this.lastError = snapshot.lastError;
|
|
1045
997
|
}
|
|
1046
998
|
}
|
|
1047
|
-
function
|
|
1048
|
-
|
|
1049
|
-
store.initialize(transcript);
|
|
1050
|
-
return store;
|
|
999
|
+
function createContextDeletionStore(transcript) {
|
|
1000
|
+
return new ContextDeletionMemoryStore(transcript);
|
|
1051
1001
|
}
|
|
1052
1002
|
export function createContextDeletionTool(transcript, options = {}) {
|
|
1053
1003
|
const mode = options.mode ?? "standard";
|
|
1054
|
-
const store =
|
|
1004
|
+
const store = createContextDeletionStore(transcript);
|
|
1055
1005
|
let validatedResult;
|
|
1056
1006
|
function readTargets() {
|
|
1057
1007
|
return store.readTargets();
|
|
@@ -1554,16 +1504,8 @@ function createContextCompactionStopStream(model, text) {
|
|
|
1554
1504
|
function isContextCompactionOverflowError(model, errorMessage) {
|
|
1555
1505
|
return isContextOverflow(createContextCompactionAssistantMessage(model, [], "error", errorMessage), model.contextWindow);
|
|
1556
1506
|
}
|
|
1557
|
-
|
|
1558
|
-
const
|
|
1559
|
-
for (const level of CONTEXT_COMPACTION_THINKING_LEVEL_ORDER) {
|
|
1560
|
-
if (supportedLevels.includes(level))
|
|
1561
|
-
return level;
|
|
1562
|
-
}
|
|
1563
|
-
return "off";
|
|
1564
|
-
}
|
|
1565
|
-
async function runContextDeletionAssistant(transcript, model, apiKey, headers, signal, mode = "standard") {
|
|
1566
|
-
const maxTokens = Math.min(4096, model.maxTokens > 0 ? model.maxTokens : Number.POSITIVE_INFINITY);
|
|
1507
|
+
async function runContextDeletionAssistant(transcript, model, apiKey, headers, signal, thinkingLevel = "off", mode = "standard") {
|
|
1508
|
+
const maxTokens = model.maxTokens > 0 ? model.maxTokens : Number.POSITIVE_INFINITY;
|
|
1567
1509
|
if (signal?.aborted) {
|
|
1568
1510
|
throw new Error("Context compaction failed: Request was aborted");
|
|
1569
1511
|
}
|
|
@@ -1574,13 +1516,12 @@ async function runContextDeletionAssistant(transcript, model, apiKey, headers, s
|
|
|
1574
1516
|
timestamp: Date.now(),
|
|
1575
1517
|
};
|
|
1576
1518
|
const deletionTool = createContextDeletionTool(transcript, { mode });
|
|
1577
|
-
const effectiveThinkingLevel = getLowestContextCompactionThinkingLevel(model);
|
|
1578
1519
|
let compactionTurnCount = 0;
|
|
1579
1520
|
const agent = new Agent({
|
|
1580
1521
|
initialState: {
|
|
1581
1522
|
systemPrompt: CONTEXT_COMPACTION_SYSTEM_PROMPT,
|
|
1582
1523
|
model,
|
|
1583
|
-
thinkingLevel
|
|
1524
|
+
thinkingLevel,
|
|
1584
1525
|
tools: deletionTool.tools,
|
|
1585
1526
|
},
|
|
1586
1527
|
toolExecution: "parallel",
|
|
@@ -1626,8 +1567,8 @@ async function runContextDeletionAssistant(transcript, model, apiKey, headers, s
|
|
|
1626
1567
|
lastToolError: deletionTool.getLastError(),
|
|
1627
1568
|
};
|
|
1628
1569
|
}
|
|
1629
|
-
export async function contextCompact(preparation, model, apiKey, headers, signal,
|
|
1630
|
-
const { validatedResult, lastToolError } = await runContextDeletionAssistant(preparation.transcript, model, apiKey, headers, signal, mode);
|
|
1570
|
+
export async function contextCompact(preparation, model, apiKey, headers, signal, thinkingLevel = "off", mode = preparation.mode ?? "standard") {
|
|
1571
|
+
const { validatedResult, lastToolError } = await runContextDeletionAssistant(preparation.transcript, model, apiKey, headers, signal, thinkingLevel, mode);
|
|
1631
1572
|
if (!validatedResult || validatedResult.deletedTargets.length === 0) {
|
|
1632
1573
|
throw new Error(lastToolError ? `No safe context deletions proposed; last deletion tool error: ${lastToolError}` : "No safe context deletions proposed");
|
|
1633
1574
|
}
|