@earendil-works/pi-coding-agent 0.79.7 → 0.79.8

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.
@@ -21,7 +21,7 @@ import { resolvePath } from "../utils/paths.js";
21
21
  import { sleep } from "../utils/sleep.js";
22
22
  import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth-guidance.js";
23
23
  import { executeBashWithOperations } from "./bash-executor.js";
24
- import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
24
+ import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, estimateTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
25
25
  import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
26
26
  import { exportSessionToHtml } from "./export-html/index.js";
27
27
  import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
@@ -49,6 +49,13 @@ export function parseSkillBlock(text) {
49
49
  userMessage: match[4]?.trim() || undefined,
50
50
  };
51
51
  }
52
+ function estimateMessagesTokens(messages) {
53
+ let tokens = 0;
54
+ for (const message of messages) {
55
+ tokens += estimateTokens(message);
56
+ }
57
+ return tokens;
58
+ }
52
59
  // ============================================================================
53
60
  // Constants
54
61
  // ============================================================================
@@ -1329,6 +1336,7 @@ export class AgentSession {
1329
1336
  const newEntries = this.sessionManager.getEntries();
1330
1337
  const sessionContext = this.sessionManager.buildSessionContext();
1331
1338
  this.agent.state.messages = sessionContext.messages;
1339
+ const estimatedTokensAfter = estimateMessagesTokens(sessionContext.messages);
1332
1340
  // Get the saved compaction entry for the extension event
1333
1341
  const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
1334
1342
  if (this._extensionRunner && savedCompactionEntry) {
@@ -1342,6 +1350,7 @@ export class AgentSession {
1342
1350
  summary,
1343
1351
  firstKeptEntryId,
1344
1352
  tokensBefore,
1353
+ estimatedTokensAfter,
1345
1354
  details,
1346
1355
  };
1347
1356
  this._emit({
@@ -1416,8 +1425,15 @@ export class AgentSession {
1416
1425
  if (assistantIsFromBeforeCompaction) {
1417
1426
  return false;
1418
1427
  }
1419
- // Case 1: Overflow - LLM returned context overflow error
1428
+ // Case 1: Overflow - LLM returned context overflow error, or reported usage exceeded
1429
+ // the configured window. A successful response over the configured window should compact
1430
+ // but must not retry: the assistant answer already completed and agent.continue() cannot
1431
+ // continue from an assistant message.
1420
1432
  if (sameModel && isContextOverflow(assistantMessage, contextWindow)) {
1433
+ const willRetry = assistantMessage.stopReason !== "stop";
1434
+ if (!willRetry) {
1435
+ return await this._runAutoCompaction("overflow", false);
1436
+ }
1421
1437
  if (this._overflowRecoveryAttempted) {
1422
1438
  this._emit({
1423
1439
  type: "compaction_end",
@@ -1436,7 +1452,7 @@ export class AgentSession {
1436
1452
  if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
1437
1453
  this.agent.state.messages = messages.slice(0, -1);
1438
1454
  }
1439
- return await this._runAutoCompaction("overflow", true);
1455
+ return await this._runAutoCompaction("overflow", willRetry);
1440
1456
  }
1441
1457
  // Case 2: Threshold - context is getting large
1442
1458
  // For error messages (no usage data), estimate from last successful response.
@@ -1471,17 +1487,9 @@ export class AgentSession {
1471
1487
  */
1472
1488
  async _runAutoCompaction(reason, willRetry) {
1473
1489
  const settings = this.settingsManager.getCompactionSettings();
1474
- this._emit({ type: "compaction_start", reason });
1475
- this._autoCompactionAbortController = new AbortController();
1490
+ let started = false;
1476
1491
  try {
1477
1492
  if (!this.model) {
1478
- this._emit({
1479
- type: "compaction_end",
1480
- reason,
1481
- result: undefined,
1482
- aborted: false,
1483
- willRetry: false,
1484
- });
1485
1493
  return false;
1486
1494
  }
1487
1495
  let apiKey;
@@ -1490,13 +1498,6 @@ export class AgentSession {
1490
1498
  if (this.agent.streamFn === streamSimple) {
1491
1499
  const authResult = await this._modelRegistry.getApiKeyAndHeaders(this.model);
1492
1500
  if (!authResult.ok || !authResult.apiKey) {
1493
- this._emit({
1494
- type: "compaction_end",
1495
- reason,
1496
- result: undefined,
1497
- aborted: false,
1498
- willRetry: false,
1499
- });
1500
1501
  return false;
1501
1502
  }
1502
1503
  apiKey = authResult.apiKey;
@@ -1509,15 +1510,11 @@ export class AgentSession {
1509
1510
  const pathEntries = this.sessionManager.getBranch();
1510
1511
  const preparation = prepareCompaction(pathEntries, settings);
1511
1512
  if (!preparation) {
1512
- this._emit({
1513
- type: "compaction_end",
1514
- reason,
1515
- result: undefined,
1516
- aborted: false,
1517
- willRetry: false,
1518
- });
1519
1513
  return false;
1520
1514
  }
1515
+ this._emit({ type: "compaction_start", reason });
1516
+ this._autoCompactionAbortController = new AbortController();
1517
+ started = true;
1521
1518
  let extensionCompaction;
1522
1519
  let fromExtension = false;
1523
1520
  if (this._extensionRunner.hasHandlers("session_before_compact")) {
@@ -1576,6 +1573,7 @@ export class AgentSession {
1576
1573
  const newEntries = this.sessionManager.getEntries();
1577
1574
  const sessionContext = this.sessionManager.buildSessionContext();
1578
1575
  this.agent.state.messages = sessionContext.messages;
1576
+ const estimatedTokensAfter = estimateMessagesTokens(sessionContext.messages);
1579
1577
  // Get the saved compaction entry for the extension event
1580
1578
  const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
1581
1579
  if (this._extensionRunner && savedCompactionEntry) {
@@ -1589,6 +1587,7 @@ export class AgentSession {
1589
1587
  summary,
1590
1588
  firstKeptEntryId,
1591
1589
  tokensBefore,
1590
+ estimatedTokensAfter,
1592
1591
  details,
1593
1592
  };
1594
1593
  this._emit({ type: "compaction_end", reason, result, aborted: false, willRetry });
@@ -1606,16 +1605,18 @@ export class AgentSession {
1606
1605
  }
1607
1606
  catch (error) {
1608
1607
  const errorMessage = error instanceof Error ? error.message : "compaction failed";
1609
- this._emit({
1610
- type: "compaction_end",
1611
- reason,
1612
- result: undefined,
1613
- aborted: false,
1614
- willRetry: false,
1615
- errorMessage: reason === "overflow"
1616
- ? `Context overflow recovery failed: ${errorMessage}`
1617
- : `Auto-compaction failed: ${errorMessage}`,
1618
- });
1608
+ if (started) {
1609
+ this._emit({
1610
+ type: "compaction_end",
1611
+ reason,
1612
+ result: undefined,
1613
+ aborted: false,
1614
+ willRetry: false,
1615
+ errorMessage: reason === "overflow"
1616
+ ? `Context overflow recovery failed: ${errorMessage}`
1617
+ : `Auto-compaction failed: ${errorMessage}`,
1618
+ });
1619
+ }
1619
1620
  return false;
1620
1621
  }
1621
1622
  finally {