@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.
@@ -1,7 +1,6 @@
1
1
  import { Agent } from "@earendil-works/pi-agent-core";
2
- import { createAssistantMessageEventStream, getSupportedThinkingLevels, isContextOverflow, streamSimple, StringEnum, } from "@earendil-works/pi-ai";
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
- const moduleRequire = createRequire(import.meta.url);
858
- function createTransientSqliteDatabase() {
859
- // better-sqlite3 is the portable/package dependency for Node-based Atomic installs.
860
- // Bun cannot dlopen better-sqlite3 yet, so Bun runtime/tests use the API-compatible builtin.
861
- if (process.versions.bun) {
862
- const sqlite = moduleRequire("bun:sqlite");
863
- return new sqlite.Database(":memory:");
864
- }
865
- const BetterSqliteDatabase = moduleRequire("better-sqlite3");
866
- return new BetterSqliteDatabase(":memory:");
867
- }
868
- class SqliteAdapter {
869
- constructor(db) {
870
- this.db = db;
871
- }
872
- static createTransient() {
873
- return new SqliteAdapter(createTransientSqliteDatabase());
874
- }
875
- exec(sql) {
876
- this.db.exec(sql);
877
- }
878
- run(sql, ...params) {
879
- this.db.prepare(sql).run(...params);
880
- }
881
- all(sql, ...params) {
882
- return this.db.prepare(sql).all(...params);
883
- }
884
- get(sql, ...params) {
885
- return this.db.prepare(sql).get(...params);
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.exec("BEGIN IMMEDIATE");
905
+ const snapshot = this.snapshot();
889
906
  try {
890
- const result = operation();
891
- this.exec("COMMIT");
892
- return result;
907
+ return operation();
893
908
  }
894
909
  catch (error) {
895
- try {
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.sqlite
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.sqlite.run("DELETE FROM deletion_targets");
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.sqlite.all("SELECT entry_id, text, is_protected FROM transcript_entries ORDER BY position");
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.sqlite.all(`
976
- SELECT
977
- blocks.entry_id,
978
- blocks.block_index,
979
- blocks.text,
980
- entries.is_protected AS entry_protected,
981
- blocks.is_protected AS block_protected,
982
- (
983
- SELECT COUNT(*)
984
- FROM transcript_content_blocks sibling
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
- return this.sqlite.get("SELECT entry_id, role, is_protected, token_estimate, text FROM transcript_entries WHERE entry_id = ?", entryId);
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
- return this.sqlite.get(`
997
- SELECT
998
- blocks.entry_id,
999
- blocks.block_index,
1000
- blocks.type,
1001
- blocks.token_estimate,
1002
- blocks.text,
1003
- entries.is_protected AS entry_protected,
1004
- blocks.is_protected AS block_protected,
1005
- (
1006
- SELECT COUNT(*)
1007
- FROM transcript_content_blocks sibling
1008
- WHERE sibling.entry_id = blocks.entry_id
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 table = target === "entry" ? "transcript_entries" : "transcript_content_blocks";
1017
- const row = this.sqlite.get(`SELECT SUM(LENGTH(text)) AS scan_chars FROM ${table}`);
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
- const next = this.getCallCount() + 1;
1022
- this.setState("call_count", String(next));
1023
- return next;
971
+ this.callCount += 1;
972
+ return this.callCount;
1024
973
  }
1025
974
  getCallCount() {
1026
- return Number(this.getState("call_count") ?? "0");
975
+ return this.callCount;
1027
976
  }
1028
977
  setLastError(message) {
1029
- this.setState("last_error", message);
978
+ this.lastError = message;
1030
979
  }
1031
980
  clearLastError() {
1032
- this.sqlite.run("DELETE FROM context_compaction_state WHERE key = ?", "last_error");
981
+ this.lastError = undefined;
1033
982
  }
1034
983
  getLastError() {
1035
- return this.getState("last_error");
984
+ return this.lastError;
1036
985
  }
1037
- getState(key) {
1038
- return this.sqlite.get("SELECT value FROM context_compaction_state WHERE key = ?", key)?.value;
1039
- }
1040
- setState(key, value) {
1041
- this.sqlite.run("INSERT INTO context_compaction_state (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value", key, value);
986
+ snapshot() {
987
+ return {
988
+ deletionTargets: this.readTargets(),
989
+ callCount: this.callCount,
990
+ ...(this.lastError === undefined ? {} : { lastError: this.lastError }),
991
+ };
1042
992
  }
1043
- close() {
1044
- this.sqlite.close();
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 createContextDeletionSqliteStore(transcript) {
1048
- const store = new ContextDeletionSqliteStore(SqliteAdapter.createTransient());
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 = createContextDeletionSqliteStore(transcript);
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
- export function getLowestContextCompactionThinkingLevel(model) {
1558
- const supportedLevels = getSupportedThinkingLevels(model);
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: effectiveThinkingLevel,
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, _thinkingLevel, mode = preparation.mode ?? "standard") {
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
  }