@caupulican/pi-adaptative 0.81.0 → 0.81.2

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
@@ -587,8 +594,8 @@ export class AgentSession {
587
594
  }
588
595
  throw new Error(formatNoApiKeyFoundMessage(model.provider));
589
596
  }
590
- // Summarizer model selection, request auth (with session-model fallback), and window-adapted
591
- // settings live in CompactionSupport (see compaction-support.ts). Same-signature delegations.
597
+ // Summarizer model/thinking selection, request auth (with session-model fallback), and
598
+ // window-adapted settings live in CompactionSupport (see compaction-support.ts).
592
599
  _getCompactionRequestAuth(model) {
593
600
  return this._compactionSupport.getRequestAuth(model);
594
601
  }
@@ -598,6 +605,9 @@ export class AgentSession {
598
605
  _resolveCompactionModel(sessionModel) {
599
606
  return this._compactionSupport.resolveModel(sessionModel);
600
607
  }
608
+ _resolveCompactionThinkingLevel(compactionModel, sessionModel) {
609
+ return this._compactionSupport.resolveThinkingLevel(this.thinkingLevel, compactionModel, sessionModel);
610
+ }
601
611
  /**
602
612
  * Install tool hooks once on the Agent instance.
603
613
  *
@@ -2135,6 +2145,7 @@ export class AgentSession {
2135
2145
  throw new Error(formatNoModelSelectedMessage());
2136
2146
  }
2137
2147
  const compactionModel = this._resolveCompactionModel(this.model);
2148
+ const compactionThinkingLevel = this._resolveCompactionThinkingLevel(compactionModel, this.model);
2138
2149
  const { apiKey, headers } = await this._getCompactionRequestAuth(compactionModel);
2139
2150
  const pathEntries = this.sessionManager.getBranch();
2140
2151
  const settings = this._getAdaptedCompactionSettings();
@@ -2178,7 +2189,8 @@ export class AgentSession {
2178
2189
  }
2179
2190
  else {
2180
2191
  // Generate compaction result
2181
- const result = await compact(preparation, compactionModel, apiKey, headers, customInstructions, this._compactionAbortController.signal, this.thinkingLevel, this.agent.streamFn, this._buildCompactionPreDigest());
2192
+ const compactionSignal = this._compactionAbortController.signal;
2193
+ const result = await this._compactWithRetry(() => compact(preparation, compactionModel, apiKey, headers, customInstructions, compactionSignal, compactionThinkingLevel, this.agent.streamFn, this._buildCompactionPreDigest()), compactionSignal);
2182
2194
  summary = result.summary;
2183
2195
  firstKeptEntryId = result.firstKeptEntryId;
2184
2196
  tokensBefore = result.tokensBefore;
@@ -2390,9 +2402,9 @@ export class AgentSession {
2390
2402
  });
2391
2403
  return false;
2392
2404
  }
2393
- // Summarize with the cheap auxiliary model when available (cost guard, #30). If its auth
2394
- // turns out unusable at request time (e.g. expired token), fall back to the session model —
2395
- // the one manual /compact demonstrably works with — before giving up.
2405
+ // Summarize with the configured/default compaction model. If its auth turns out unusable at
2406
+ // request time (e.g. expired token), fall back to the session model — the one manual /compact
2407
+ // demonstrably works with — before giving up.
2396
2408
  const resolvedCompaction = await this._resolveCompactionModelAndAuth(this._resolveCompactionModel(this.model), this.model);
2397
2409
  if (resolvedCompaction.failure) {
2398
2410
  this._emit({
@@ -2406,6 +2418,7 @@ export class AgentSession {
2406
2418
  return false;
2407
2419
  }
2408
2420
  const compactionModel = resolvedCompaction.model;
2421
+ const compactionThinkingLevel = this._resolveCompactionThinkingLevel(compactionModel, this.model);
2409
2422
  const apiKey = resolvedCompaction.apiKey;
2410
2423
  const headers = resolvedCompaction.headers;
2411
2424
  const pathEntries = this.sessionManager.getBranch();
@@ -2460,7 +2473,8 @@ export class AgentSession {
2460
2473
  }
2461
2474
  else {
2462
2475
  // Generate compaction result
2463
- const compactResult = await compact(preparation, compactionModel, apiKey, headers, undefined, this._autoCompactionAbortController.signal, this.thinkingLevel, this.agent.streamFn, this._buildCompactionPreDigest());
2476
+ const autoCompactionSignal = this._autoCompactionAbortController.signal;
2477
+ const compactResult = await this._compactWithRetry(() => compact(preparation, compactionModel, apiKey, headers, undefined, autoCompactionSignal, compactionThinkingLevel, this.agent.streamFn, this._buildCompactionPreDigest()), autoCompactionSignal);
2464
2478
  summary = compactResult.summary;
2465
2479
  firstKeptEntryId = compactResult.firstKeptEntryId;
2466
2480
  tokensBefore = compactResult.tokensBefore;
@@ -2526,6 +2540,38 @@ export class AgentSession {
2526
2540
  this._autoCompactionAbortController = undefined;
2527
2541
  }
2528
2542
  }
2543
+ /**
2544
+ * Run one compaction attempt, retrying retryable provider failures (stream stalls,
2545
+ * 429/5xx, network drops) with the session's retry policy. The reliability kernel
2546
+ * classifies a stall as retryable by design (see withStreamIdleWatchdog); without this
2547
+ * loop a single transient killed the whole compaction while ordinary turns survived the
2548
+ * same failure via auto-retry. Caller aborts are never retried; sleepAbortable rejects
2549
+ * with the abort reason if the signal fires mid-backoff.
2550
+ */
2551
+ async _compactWithRetry(run, signal) {
2552
+ const retrySettings = this.settingsManager.getRetrySettings();
2553
+ const maxAttempts = retrySettings.enabled ? Math.max(1, retrySettings.maxRetries + 1) : 1;
2554
+ const policy = {
2555
+ maxAttempts,
2556
+ baseDelayMs: retrySettings.baseDelayMs,
2557
+ maxDelayMs: DEFAULT_RETRY_POLICY.maxDelayMs,
2558
+ jitterRatio: 0,
2559
+ };
2560
+ for (let attempt = 1;; attempt++) {
2561
+ try {
2562
+ return await run();
2563
+ }
2564
+ catch (error) {
2565
+ if (signal.aborted || attempt >= maxAttempts)
2566
+ throw error;
2567
+ const message = error instanceof Error ? error.message : String(error);
2568
+ const classified = classifyFailure({ message });
2569
+ if (!classified.retryable)
2570
+ throw error;
2571
+ await sleepAbortable(computeRetryDelayMs(policy, attempt, { retryAfterMs: classified.retryAfterMs }), signal);
2572
+ }
2573
+ }
2574
+ }
2529
2575
  /**
2530
2576
  * Toggle auto-compaction setting.
2531
2577
  */