@caupulican/pi-adaptative 0.81.0 → 0.81.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.
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import { readFileSync } from "node:fs";
16
16
  import { basename, dirname, join } from "node:path";
17
- import { classifyFailure, compactToolResultDetailsForRetention, createCustomMessage, DEFAULT_RETRY_POLICY, DEFAULT_STREAM_IDLE, RetryController, withStreamIdleWatchdog, } from "@caupulican/pi-agent-core";
17
+ import { classifyFailure, compactToolResultDetailsForRetention, computeRetryDelayMs, createCustomMessage, DEFAULT_RETRY_POLICY, RetryController, sleepAbortable, withStreamIdleWatchdog, } from "@caupulican/pi-agent-core";
18
18
  import { calculateContextTokens, compact, estimateContextTokens, getLatestCompactionEntry, prepareCompaction, shouldCompact, } from "@caupulican/pi-agent-core/node";
19
19
  import { cleanupSessionResources, isContextOverflow, streamSimple } from "@caupulican/pi-ai";
20
20
  import { getAgentDir } from "../config.js";
@@ -64,12 +64,13 @@ import { ToolGateController } from "./tool-gate-controller.js";
64
64
  * key stable regardless of how many times this module is evaluated.
65
65
  */
66
66
  const RAW_STREAM_MARKER = Symbol.for("pi.rawStreamSimple");
67
- /** Test-only override of the stream-idle bounds. Read once per session, at construction. */
67
+ /** Test-only override of the stream-idle bounds. Read per-request by the wiring's resolver. */
68
68
  let streamIdleOptionsOverride;
69
69
  /**
70
70
  * Test hook: override the stream-idle bounds so a stall can be provoked in-suite without a
71
- * 30s wait. Pass `undefined` to restore the user-locked default (30s idle / 120s connect).
72
- * Must be set BEFORE the session is constructed — the wiring reads it in the constructor.
71
+ * multi-minute wait. Pass `undefined` to restore the user-locked defaults (connect 120s /
72
+ * active 180s / quiet 600s, or the user's retry.stall settings). Applies per request — it
73
+ * may be set or changed at any time before the request that should observe it.
73
74
  */
74
75
  export function setStreamIdleOptionsForTests(opts) {
75
76
  streamIdleOptionsOverride = opts;
@@ -248,7 +249,13 @@ export class AgentSession {
248
249
  // Wrapping also breaks the `streamFn === streamSimple` identity the auth-injection checks
249
250
  // use, so the wrapper carries a rawness marker that _isRawStreamSimple reads.
250
251
  const baseStreamFn = this.agent.streamFn;
251
- this.agent.streamFn = tagRawness(withStreamIdleWatchdog(baseStreamFn, streamIdleOptionsOverride ?? DEFAULT_STREAM_IDLE), baseStreamFn === streamSimple);
252
+ // `this.settingsManager` is assigned below; the resolver closes over the config reference
253
+ // because the wrapper must be installed before that assignment runs.
254
+ const stallSettingsSource = config.settingsManager;
255
+ this.agent.streamFn = tagRawness(withStreamIdleWatchdog(baseStreamFn, () => ({
256
+ ...stallSettingsSource.getStreamStallSettings(),
257
+ ...streamIdleOptionsOverride,
258
+ })), baseStreamFn === streamSimple);
252
259
  this.sessionManager = config.sessionManager;
253
260
  this.settingsManager = config.settingsManager;
254
261
  // Auto-retry rides the reliability kernel: the controller owns the attempt counter and the
@@ -2178,7 +2185,8 @@ export class AgentSession {
2178
2185
  }
2179
2186
  else {
2180
2187
  // Generate compaction result
2181
- const result = await compact(preparation, compactionModel, apiKey, headers, customInstructions, this._compactionAbortController.signal, this.thinkingLevel, this.agent.streamFn, this._buildCompactionPreDigest());
2188
+ const compactionSignal = this._compactionAbortController.signal;
2189
+ const result = await this._compactWithRetry(() => compact(preparation, compactionModel, apiKey, headers, customInstructions, compactionSignal, this.thinkingLevel, this.agent.streamFn, this._buildCompactionPreDigest()), compactionSignal);
2182
2190
  summary = result.summary;
2183
2191
  firstKeptEntryId = result.firstKeptEntryId;
2184
2192
  tokensBefore = result.tokensBefore;
@@ -2460,7 +2468,8 @@ export class AgentSession {
2460
2468
  }
2461
2469
  else {
2462
2470
  // Generate compaction result
2463
- const compactResult = await compact(preparation, compactionModel, apiKey, headers, undefined, this._autoCompactionAbortController.signal, this.thinkingLevel, this.agent.streamFn, this._buildCompactionPreDigest());
2471
+ const autoCompactionSignal = this._autoCompactionAbortController.signal;
2472
+ const compactResult = await this._compactWithRetry(() => compact(preparation, compactionModel, apiKey, headers, undefined, autoCompactionSignal, this.thinkingLevel, this.agent.streamFn, this._buildCompactionPreDigest()), autoCompactionSignal);
2464
2473
  summary = compactResult.summary;
2465
2474
  firstKeptEntryId = compactResult.firstKeptEntryId;
2466
2475
  tokensBefore = compactResult.tokensBefore;
@@ -2526,6 +2535,38 @@ export class AgentSession {
2526
2535
  this._autoCompactionAbortController = undefined;
2527
2536
  }
2528
2537
  }
2538
+ /**
2539
+ * Run one compaction attempt, retrying retryable provider failures (stream stalls,
2540
+ * 429/5xx, network drops) with the session's retry policy. The reliability kernel
2541
+ * classifies a stall as retryable by design (see withStreamIdleWatchdog); without this
2542
+ * loop a single transient killed the whole compaction while ordinary turns survived the
2543
+ * same failure via auto-retry. Caller aborts are never retried; sleepAbortable rejects
2544
+ * with the abort reason if the signal fires mid-backoff.
2545
+ */
2546
+ async _compactWithRetry(run, signal) {
2547
+ const retrySettings = this.settingsManager.getRetrySettings();
2548
+ const maxAttempts = retrySettings.enabled ? Math.max(1, retrySettings.maxRetries + 1) : 1;
2549
+ const policy = {
2550
+ maxAttempts,
2551
+ baseDelayMs: retrySettings.baseDelayMs,
2552
+ maxDelayMs: DEFAULT_RETRY_POLICY.maxDelayMs,
2553
+ jitterRatio: 0,
2554
+ };
2555
+ for (let attempt = 1;; attempt++) {
2556
+ try {
2557
+ return await run();
2558
+ }
2559
+ catch (error) {
2560
+ if (signal.aborted || attempt >= maxAttempts)
2561
+ throw error;
2562
+ const message = error instanceof Error ? error.message : String(error);
2563
+ const classified = classifyFailure({ message });
2564
+ if (!classified.retryable)
2565
+ throw error;
2566
+ await sleepAbortable(computeRetryDelayMs(policy, attempt, { retryAfterMs: classified.retryAfterMs }), signal);
2567
+ }
2568
+ }
2569
+ }
2529
2570
  /**
2530
2571
  * Toggle auto-compaction setting.
2531
2572
  */