@caupulican/pi-adaptative 0.81.2 → 0.81.3

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.
Files changed (81) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/core/agent-session.d.ts +1 -23
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +117 -131
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/background-lane-controller.d.ts +2 -0
  7. package/dist/core/background-lane-controller.d.ts.map +1 -1
  8. package/dist/core/background-lane-controller.js +3 -0
  9. package/dist/core/background-lane-controller.js.map +1 -1
  10. package/dist/core/billing-failover-controller.d.ts +31 -0
  11. package/dist/core/billing-failover-controller.d.ts.map +1 -0
  12. package/dist/core/billing-failover-controller.js +72 -0
  13. package/dist/core/billing-failover-controller.js.map +1 -0
  14. package/dist/core/billing-failover.d.ts +25 -0
  15. package/dist/core/billing-failover.d.ts.map +1 -0
  16. package/dist/core/billing-failover.js +21 -0
  17. package/dist/core/billing-failover.js.map +1 -0
  18. package/dist/core/context-pipeline.d.ts +1 -0
  19. package/dist/core/context-pipeline.d.ts.map +1 -1
  20. package/dist/core/context-pipeline.js +51 -14
  21. package/dist/core/context-pipeline.js.map +1 -1
  22. package/dist/core/model-router/config-diagnostics.d.ts +1 -1
  23. package/dist/core/model-router/config-diagnostics.d.ts.map +1 -1
  24. package/dist/core/model-router/config-diagnostics.js +29 -1
  25. package/dist/core/model-router/config-diagnostics.js.map +1 -1
  26. package/dist/core/model-router/fitness-gate.d.ts +23 -0
  27. package/dist/core/model-router/fitness-gate.d.ts.map +1 -0
  28. package/dist/core/model-router/fitness-gate.js +33 -0
  29. package/dist/core/model-router/fitness-gate.js.map +1 -0
  30. package/dist/core/model-router/status.d.ts +25 -1
  31. package/dist/core/model-router/status.d.ts.map +1 -1
  32. package/dist/core/model-router/status.js +27 -4
  33. package/dist/core/model-router/status.js.map +1 -1
  34. package/dist/core/model-router-controller.d.ts +10 -1
  35. package/dist/core/model-router-controller.d.ts.map +1 -1
  36. package/dist/core/model-router-controller.js +108 -27
  37. package/dist/core/model-router-controller.js.map +1 -1
  38. package/dist/core/runtime-builder.d.ts +16 -0
  39. package/dist/core/runtime-builder.d.ts.map +1 -1
  40. package/dist/core/runtime-builder.js +88 -1
  41. package/dist/core/runtime-builder.js.map +1 -1
  42. package/dist/core/scout-controller.d.ts +44 -0
  43. package/dist/core/scout-controller.d.ts.map +1 -0
  44. package/dist/core/scout-controller.js +165 -0
  45. package/dist/core/scout-controller.js.map +1 -0
  46. package/dist/core/settings-manager.d.ts +16 -0
  47. package/dist/core/settings-manager.d.ts.map +1 -1
  48. package/dist/core/settings-manager.js +35 -3
  49. package/dist/core/settings-manager.js.map +1 -1
  50. package/dist/core/tools/context-scout.d.ts +19 -0
  51. package/dist/core/tools/context-scout.d.ts.map +1 -0
  52. package/dist/core/tools/context-scout.js +47 -0
  53. package/dist/core/tools/context-scout.js.map +1 -0
  54. package/dist/core/tools/index.d.ts +1 -0
  55. package/dist/core/tools/index.d.ts.map +1 -1
  56. package/dist/core/tools/index.js +1 -0
  57. package/dist/core/tools/index.js.map +1 -1
  58. package/dist/main.d.ts.map +1 -1
  59. package/dist/main.js +1 -1
  60. package/dist/main.js.map +1 -1
  61. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  62. package/dist/modes/interactive/components/settings-selector.js +77 -7
  63. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  64. package/dist/modes/interactive/local-model-commands.d.ts +5 -2
  65. package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
  66. package/dist/modes/interactive/local-model-commands.js +115 -1
  67. package/dist/modes/interactive/local-model-commands.js.map +1 -1
  68. package/docs/index.md +1 -0
  69. package/docs/providers.md +2 -0
  70. package/docs/scout.md +71 -0
  71. package/docs/settings.md +14 -3
  72. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  73. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  74. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  75. package/examples/extensions/sandbox/package-lock.json +2 -2
  76. package/examples/extensions/sandbox/package.json +1 -1
  77. package/examples/extensions/with-deps/package-lock.json +2 -2
  78. package/examples/extensions/with-deps/package.json +1 -1
  79. package/examples/sdk/12-full-control.ts +1 -1
  80. package/npm-shrinkwrap.json +12 -12
  81. package/package.json +4 -4
@@ -15,7 +15,7 @@
15
15
  import { readFileSync } from "node:fs";
16
16
  import { basename, dirname, join } from "node:path";
17
17
  import { classifyFailure, compactToolResultDetailsForRetention, computeRetryDelayMs, createCustomMessage, DEFAULT_RETRY_POLICY, RetryController, sleepAbortable, withStreamIdleWatchdog, } from "@caupulican/pi-agent-core";
18
- import { calculateContextTokens, compact, estimateContextTokens, getLatestCompactionEntry, prepareCompaction, shouldCompact, } from "@caupulican/pi-agent-core/node";
18
+ import { calculateContextTokens, compact, createDeterministicCompaction, estimateContextTokens, getLatestCompactionEntry, prepareCompaction, runCompactionLoop, shouldCompact, } from "@caupulican/pi-agent-core/node";
19
19
  import { cleanupSessionResources, isContextOverflow, streamSimple } from "@caupulican/pi-ai";
20
20
  import { getAgentDir } from "../config.js";
21
21
  import { stripFrontmatter } from "../utils/frontmatter.js";
@@ -24,6 +24,7 @@ import { buildForegroundEnvelope, formatForegroundEnvelopeObservation } from "./
24
24
  import { AutonomyTelemetry } from "./autonomy-telemetry.js";
25
25
  import { BackgroundLaneController } from "./background-lane-controller.js";
26
26
  import { BashExecutionController } from "./bash-execution-controller.js";
27
+ import { BillingFailoverController, ExhaustedProviderRegistry } from "./billing-failover-controller.js";
27
28
  import { CompactionSupport } from "./compaction-support.js";
28
29
  import { buildContextCompositionReport, formatContextCompositionDashboard, } from "./context/context-composition.js";
29
30
  import { ContextPipeline } from "./context-pipeline.js";
@@ -116,52 +117,34 @@ export class AgentSession {
116
117
  _unsubscribeAgent;
117
118
  _eventListeners = [];
118
119
  _extensionsChangedListeners = [];
119
- /** Tracks pending steering messages for UI display. Removed when delivered. */
120
120
  _steeringMessages = [];
121
- /** Tracks pending follow-up messages for UI display. Removed when delivered. */
122
121
  _followUpMessages = [];
123
- /** Tracks extension slash commands queued while the agent is streaming. */
124
122
  _queuedExtensionCommands = [];
125
- /** Messages queued to be included with the next user prompt as context ("asides"). */
126
123
  _pendingNextTurnMessages = [];
127
- /** Serializes prompt() submissions made while streaming so queued steering/follow-ups keep user-typed FIFO order. */
128
124
  _streamingPromptSubmissionTail = Promise.resolve();
129
125
  /**
130
126
  * The last tool set requested via setActiveToolsByName BEFORE model-capability filtering, so
131
127
  * switching from a small-window model back to a large one restores the full requested set.
132
128
  */
133
129
  _requestedActiveToolNames;
134
- // Compaction/context hygiene state
135
130
  _compactionAbortController = undefined;
136
131
  _autoCompactionAbortController = undefined;
137
132
  _overflowRecoveryAttempted = false;
138
133
  _unboundToolGrantWarnings = [];
139
- // Branch summarization state
140
134
  _branchSummaryAbortController = undefined;
141
- // Auto-retry driver — owns the attempt counter and abortable backoff (reliability kernel).
142
135
  _retryController;
143
- // Bash execution state
144
- /** User-facing model + thinking-level selection (see model-selection-controller.ts). */
145
136
  _modelSelection;
146
- /** Standalone bash-command execution (see bash-execution-controller.ts); owns the bash abort
147
- * controller and the streaming-deferred pending-message queue. */
148
137
  _bash;
149
- /** Resource-profile tool/extension gating + reload-time profile model re-apply (see profile-filter-controller.ts). */
150
138
  _profileFilter;
151
- /** Agent tool-call gate: escalation, autonomy gating, extension hooks, untrusted boundary (see tool-gate-controller.ts). */
152
139
  _toolGate;
153
- // Extension system
154
140
  _extensionRunner;
155
141
  _turnIndex = 0;
156
- /** G7: per-turn foreground CapabilityEnvelope auto-built for visibility (observe-only; not enforced). */
157
142
  _currentForegroundEnvelope;
158
143
  _resourceLoader;
159
144
  _customTools;
160
145
  _cwd;
161
146
  _agentDir;
162
147
  _collectWorkspaceSources;
163
- /** Owns the cached per-server OllamaRuntime instances and the local-model router readiness gate
164
- * (see local-runtime-controller.ts). */
165
148
  _localRuntimeController;
166
149
  /** Assembles the session's base system prompt from live session state (see
167
150
  * system-prompt-builder.ts); owns the paired _baseSystemPromptOptions. */
@@ -177,7 +160,6 @@ export class AgentSession {
177
160
  * latest retrieval/prompt-inclusion reports, the reload-safe MemoryManager, the recall
178
161
  * effectiveness tracker, and the extension-contributed pending providers. */
179
162
  _memory;
180
- /** Summarizer model/auth/settings resolution (see compaction-support.ts). */
181
163
  _compactionSupport;
182
164
  /** Per-turn context-shaping subsystem (see context-pipeline.ts); owns the latest
183
165
  * audit/policy/correlation/enforcement/gc reports, the brain-curation sidecar + its skip reasons,
@@ -190,28 +172,21 @@ export class AgentSession {
190
172
  _toolProfileFilter;
191
173
  _isExplicitModel;
192
174
  _isExplicitThinking;
193
- /** R8: registry for deployment-supplied gateway channels + schedulers (lifecycle driven by the host runner). */
194
175
  _gatewayRegistry = new GatewayRegistry();
195
- /** Cache for getSpawnedUsage(), keyed by session entry count (Bug #22 — avoid O(N) per render frame). */
196
176
  /** Usage/cost/stats accounting, /context estimate, and session export (see session-analytics.ts);
197
177
  * owns the spawned-usage and daily-usage memo caches. */
198
178
  _analytics;
199
- /** In-file session-tree branch switching + fork-selector reads (see session-tree-navigator.ts). */
200
179
  _treeNavigator;
201
- /** Latest proactive cost-guard decision (#34), for the host UI to surface. Undefined when disabled. */
202
180
  _lastCostGuardDecision;
203
- /** One-shot latch so the cost guard downgrades reasoning once per over-threshold episode, not every call. */
204
181
  _costGuardDowngraded = false;
205
182
  /** Per-turn model-router subsystem (see model-router-controller.ts); owns the transient route/intent,
206
183
  * the cheap-turn session buffer, the escalation/retry flags, and the sticky last-decision/skip-reason
207
184
  * used by the status report. Its parallel routed drive path delegates every turn back to
208
185
  * {@link _runAgentPrompt} so the drive loop stays host-side. */
209
186
  _modelRouter;
210
- /** Lazily-built skill curator (#32) over `<agentDir>/skills`. */
187
+ _billingFailover;
211
188
  _skillCuratorInstance;
212
- /** Set on dispose so in-flight background reflection bails instead of writing to a dead session (Bug #21). */
213
189
  _disposed = false;
214
- /** Aborts in-flight background reflection completions on dispose (Bug #21). */
215
190
  _reflectionAbort = new AbortController();
216
191
  /** Native reflection engine + learning-apply/rollback path (see reflection-controller.ts); owns no
217
192
  * session state, applies durable writes through the bundled memory tool and the session log. */
@@ -229,7 +204,6 @@ export class AgentSession {
229
204
  _extensionShutdownHandler;
230
205
  _extensionErrorListener;
231
206
  _extensionErrorUnsubscriber;
232
- // Model registry for API key resolution
233
207
  _modelRegistry;
234
208
  /** Tool-registry assembly + the self-modification-safe extension reload (see runtime-builder.ts);
235
209
  * owns the base/wrapped tool definitions, the live tool registry, and the per-tool prompt
@@ -314,6 +288,7 @@ export class AgentSession {
314
288
  getLearningDecisionSnapshots: () => this.getLearningDecisionSnapshots(),
315
289
  getLearningAuditRecords: () => this.getLearningAuditRecords(),
316
290
  });
291
+ this._modelRegistry = config.modelRegistry;
317
292
  this._backgroundLanes = new BackgroundLaneController({
318
293
  isDisposed: () => this._disposed,
319
294
  isChildSession: () => this._isChildSession,
@@ -323,6 +298,7 @@ export class AgentSession {
323
298
  getSessionManager: () => this.sessionManager,
324
299
  getSettingsManager: () => this.settingsManager,
325
300
  getModelRegistry: () => this._modelRegistry,
301
+ isModelExhausted: (model) => this._billingFailover.isExhausted(`${model.provider}/${model.id}`),
326
302
  getModel: () => this.model ?? undefined,
327
303
  getCapabilityEnvelope: () => this.capabilityEnvelope,
328
304
  getModelCapabilityProfile: () => this.getModelCapabilityProfile(),
@@ -367,12 +343,21 @@ export class AgentSession {
367
343
  addSpawnedUsage: (usage, opts) => this.addSpawnedUsage(usage, opts),
368
344
  runIsolatedCompletion: (opts) => this.runIsolatedCompletion(opts),
369
345
  });
346
+ this._billingFailover = new BillingFailoverController({
347
+ agent: this.agent,
348
+ modelRegistry: this._modelRegistry,
349
+ exhausted: new ExhaustedProviderRegistry(),
350
+ subscriptionHop: this.settingsManager.getFailoverSettings().subscriptionHop,
351
+ emit: (event) => this._emit(event),
352
+ });
370
353
  this._modelRouter = new ModelRouterController({
371
354
  getAgent: () => this.agent,
372
355
  getModel: () => this.model ?? undefined,
373
356
  getSettingsManager: () => this.settingsManager,
374
357
  getSessionManager: () => this.sessionManager,
375
358
  getModelRegistry: () => this._modelRegistry,
359
+ isModelExhausted: (model) => this._billingFailover.isExhausted(`${model.provider}/${model.id}`),
360
+ getFailoverStatus: () => this._billingFailover.getStatus(),
376
361
  getAgentDir: () => this._agentDir,
377
362
  getReflectionSignal: () => this._reflectionAbort.signal,
378
363
  getBaseSystemPrompt: () => this._baseSystemPrompt,
@@ -407,7 +392,6 @@ export class AgentSession {
407
392
  getGoalRuntimeSnapshot: (settings) => this.getGoalRuntimeSnapshot(settings),
408
393
  prompt: (text, options) => this.prompt(text, options),
409
394
  });
410
- this._modelRegistry = config.modelRegistry;
411
395
  this._extensionRunnerRef = config.extensionRunnerRef;
412
396
  this._initialActiveToolNames = config.initialActiveToolNames;
413
397
  this._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;
@@ -423,9 +407,11 @@ export class AgentSession {
423
407
  this._runtimeBuilder = new RuntimeBuilder({
424
408
  getAgent: () => this.agent,
425
409
  getCwd: () => this._cwd,
410
+ getAgentDir: () => this._agentDir,
426
411
  getSessionManager: () => this.sessionManager,
427
412
  getSettingsManager: () => this.settingsManager,
428
413
  getModelRegistry: () => this._modelRegistry,
414
+ isModelExhausted: (model) => this._billingFailover.isExhausted(`${model.provider}/${model.id}`),
429
415
  getResourceLoader: () => this._resourceLoader,
430
416
  getExtensionRunner: () => this._extensionRunner,
431
417
  setExtensionRunner: (runner) => {
@@ -1530,7 +1516,6 @@ export class AgentSession {
1530
1516
  async _ensureRouteModelReady(resolved) {
1531
1517
  return this._localRuntimeController.ensureRouteModelReady(resolved);
1532
1518
  }
1533
- /** Model-router status + config diagnostics report. Delegates to {@link ModelRouterController}. */
1534
1519
  getModelRouterStatus(formatLabel) {
1535
1520
  return this._modelRouter.getStatus(formatLabel);
1536
1521
  }
@@ -1543,6 +1528,8 @@ export class AgentSession {
1543
1528
  if (this._isRetryableError(msg) && (await this._prepareRetry(msg))) {
1544
1529
  return true;
1545
1530
  }
1531
+ if (await this._billingFailover.handleAssistantError(msg))
1532
+ return false;
1546
1533
  if (msg.stopReason === "error" && this._retryController.attempt > 0) {
1547
1534
  this._emit({
1548
1535
  type: "auto_retry_end",
@@ -2190,7 +2177,7 @@ export class AgentSession {
2190
2177
  else {
2191
2178
  // Generate compaction result
2192
2179
  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);
2180
+ const result = await this._compactWithRetry(() => compact(preparation, compactionModel, apiKey, headers, customInstructions, compactionSignal, compactionThinkingLevel, this.agent.streamFn, this._buildCompactionPreDigest()), compactionSignal, compactionModel.provider);
2194
2181
  summary = result.summary;
2195
2182
  firstKeptEntryId = result.firstKeptEntryId;
2196
2183
  tokensBefore = result.tokensBefore;
@@ -2388,10 +2375,16 @@ export class AgentSession {
2388
2375
  */
2389
2376
  async _runAutoCompaction(reason, willRetry) {
2390
2377
  const settings = this._getAdaptedCompactionSettings();
2378
+ const model = this.model;
2391
2379
  this._emit({ type: "compaction_start", reason });
2380
+ const hadQueuedMessages = this.agent.hasQueuedMessages();
2392
2381
  this._autoCompactionAbortController = new AbortController();
2382
+ const signal = this._autoCompactionAbortController.signal;
2383
+ let fromExtension = false;
2384
+ let lastCompaction;
2385
+ let extensionCancelled = false;
2393
2386
  try {
2394
- if (!this.model) {
2387
+ if (!model) {
2395
2388
  this._emit({
2396
2389
  type: "compaction_end",
2397
2390
  reason,
@@ -2402,113 +2395,107 @@ export class AgentSession {
2402
2395
  });
2403
2396
  return false;
2404
2397
  }
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.
2408
- const resolvedCompaction = await this._resolveCompactionModelAndAuth(this._resolveCompactionModel(this.model), this.model);
2409
- if (resolvedCompaction.failure) {
2410
- this._emit({
2411
- type: "compaction_end",
2412
- reason,
2413
- result: undefined,
2414
- aborted: false,
2415
- willRetry: false,
2416
- errorMessage: `Auto-compaction failed: ${resolvedCompaction.failure}`,
2417
- });
2418
- return false;
2419
- }
2420
- const compactionModel = resolvedCompaction.model;
2421
- const compactionThinkingLevel = this._resolveCompactionThinkingLevel(compactionModel, this.model);
2422
- const apiKey = resolvedCompaction.apiKey;
2423
- const headers = resolvedCompaction.headers;
2424
- const pathEntries = this.sessionManager.getBranch();
2425
- const preparation = prepareCompaction(pathEntries, settings);
2426
- if (!preparation) {
2427
- const lastEntry = pathEntries[pathEntries.length - 1];
2398
+ const contextWindow = model.contextWindow;
2399
+ const triggerThreshold = Math.max(0, contextWindow - settings.reserveTokens);
2400
+ const margin = Math.max(0, Math.floor(0.01 * contextWindow));
2401
+ let measureReads = 0;
2402
+ const outcome = await runCompactionLoop({
2403
+ getBranch: () => this.sessionManager.getBranch(),
2404
+ measureLiveTokens: () => {
2405
+ const measured = this._pipeline.estimateCurrentContextTokens(this.agent.state.messages);
2406
+ return measureReads++ === 0 ? Math.max(measured, triggerThreshold + 1) : measured;
2407
+ },
2408
+ getTriggerThreshold: () => triggerThreshold,
2409
+ getMargin: () => margin,
2410
+ getBaseKeepRecentTokens: () => settings.keepRecentTokens,
2411
+ resolveModelAndAuth: async (modelTier) => this._resolveCompactionModelAndAuth(modelTier === "session" ? model : this._resolveCompactionModel(model), model),
2412
+ summarizeAndVerify: async (params, compactModel, apiKey, headers, branchEntries) => {
2413
+ fromExtension = false;
2414
+ const adaptedSettings = { ...settings, keepRecentTokens: params.keepRecentTokens };
2415
+ const preparation = prepareCompaction(branchEntries, adaptedSettings);
2416
+ if (!preparation) {
2417
+ throw new Error("already compacted");
2418
+ }
2419
+ const compactionThinkingLevel = this._resolveCompactionThinkingLevel(compactModel, model);
2420
+ if (this._extensionRunner.hasHandlers("session_before_compact")) {
2421
+ const extensionResult = (await this._extensionRunner.emit({
2422
+ type: "session_before_compact",
2423
+ preparation,
2424
+ branchEntries,
2425
+ customInstructions: undefined,
2426
+ signal,
2427
+ }));
2428
+ if (extensionResult?.cancel) {
2429
+ extensionCancelled = true;
2430
+ throw new Error("auto-compaction-cancelled");
2431
+ }
2432
+ if (extensionResult?.compaction) {
2433
+ fromExtension = true;
2434
+ return { result: extensionResult.compaction };
2435
+ }
2436
+ }
2437
+ const compactResult = await this._compactWithRetry(() => compact(preparation, compactModel, apiKey, headers, undefined, signal, compactionThinkingLevel, this.agent.streamFn, this._buildCompactionPreDigest(), { chunked: params.chunked }), signal, compactModel.provider);
2438
+ return { result: compactResult };
2439
+ },
2440
+ buildDeterministicCheckpoint: async () => {
2441
+ const branch = this.sessionManager.getBranch();
2442
+ const preparation = prepareCompaction(branch, {
2443
+ ...settings,
2444
+ });
2445
+ if (!preparation) {
2446
+ throw new Error("already compacted");
2447
+ }
2448
+ fromExtension = false;
2449
+ return { result: createDeterministicCompaction(preparation) };
2450
+ },
2451
+ apply: async (result) => {
2452
+ lastCompaction = result;
2453
+ this.sessionManager.appendCompaction(result.summary, result.firstKeptEntryId, result.tokensBefore, result.details, fromExtension);
2454
+ this.agent.state.messages = this.sessionManager.buildSessionContext().messages;
2455
+ const newEntries = this.sessionManager.getEntries();
2456
+ const savedCompactionEntry = newEntries.find((entry) => entry.type === "compaction" && entry.summary === result.summary);
2457
+ if (this._extensionRunner && savedCompactionEntry) {
2458
+ await this._extensionRunner.emit({
2459
+ type: "session_compact",
2460
+ compactionEntry: savedCompactionEntry,
2461
+ fromExtension,
2462
+ });
2463
+ }
2464
+ },
2465
+ onTransition: ({ cycle, cause }) => {
2466
+ this._emit({
2467
+ type: "warning",
2468
+ message: `auto-compaction cycle ${cycle}: ${cause} — retrying from step 0`,
2469
+ });
2470
+ },
2471
+ signal,
2472
+ });
2473
+ if (outcome.kind === "skip") {
2428
2474
  this._emit({
2429
2475
  type: "compaction_end",
2430
2476
  reason,
2431
2477
  result: undefined,
2432
2478
  aborted: false,
2433
2479
  willRetry: false,
2434
- skipReason: lastEntry?.type === "compaction" ? "already compacted" : "nothing to compact (session too small)",
2480
+ skipReason: outcome.reason,
2435
2481
  });
2436
- return false;
2482
+ return hadQueuedMessages || this.agent.hasQueuedMessages();
2437
2483
  }
2438
- let extensionCompaction;
2439
- let fromExtension = false;
2440
- if (this._extensionRunner.hasHandlers("session_before_compact")) {
2441
- const extensionResult = (await this._extensionRunner.emit({
2442
- type: "session_before_compact",
2443
- preparation,
2444
- branchEntries: pathEntries,
2445
- customInstructions: undefined,
2446
- signal: this._autoCompactionAbortController.signal,
2447
- }));
2448
- if (extensionResult?.cancel) {
2449
- this._emit({
2450
- type: "compaction_end",
2451
- reason,
2452
- result: undefined,
2453
- aborted: true,
2454
- willRetry: false,
2455
- });
2484
+ if (outcome.kind === "failed") {
2485
+ if (outcome.reason === "aborted") {
2486
+ this._emit({ type: "compaction_end", reason, result: undefined, aborted: true, willRetry: false });
2456
2487
  return false;
2457
2488
  }
2458
- if (extensionResult?.compaction) {
2459
- extensionCompaction = extensionResult.compaction;
2460
- fromExtension = true;
2461
- }
2489
+ throw new Error(outcome.reason);
2462
2490
  }
2463
- let summary;
2464
- let firstKeptEntryId;
2465
- let tokensBefore;
2466
- let details;
2467
- if (extensionCompaction) {
2468
- // Extension provided compaction content
2469
- summary = extensionCompaction.summary;
2470
- firstKeptEntryId = extensionCompaction.firstKeptEntryId;
2471
- tokensBefore = extensionCompaction.tokensBefore;
2472
- details = extensionCompaction.details;
2473
- }
2474
- else {
2475
- // Generate compaction result
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);
2478
- summary = compactResult.summary;
2479
- firstKeptEntryId = compactResult.firstKeptEntryId;
2480
- tokensBefore = compactResult.tokensBefore;
2481
- details = compactResult.details;
2482
- }
2483
- if (this._autoCompactionAbortController.signal.aborted) {
2484
- this._emit({
2485
- type: "compaction_end",
2486
- reason,
2487
- result: undefined,
2488
- aborted: true,
2489
- willRetry: false,
2490
- });
2491
+ if (extensionCancelled || signal.aborted) {
2492
+ this._emit({ type: "compaction_end", reason, result: undefined, aborted: true, willRetry: false });
2491
2493
  return false;
2492
2494
  }
2493
- this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
2494
- const newEntries = this.sessionManager.getEntries();
2495
- const sessionContext = this.sessionManager.buildSessionContext();
2496
- this.agent.state.messages = sessionContext.messages;
2497
- // Get the saved compaction entry for the extension event
2498
- const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
2499
- if (this._extensionRunner && savedCompactionEntry) {
2500
- await this._extensionRunner.emit({
2501
- type: "session_compact",
2502
- compactionEntry: savedCompactionEntry,
2503
- fromExtension,
2504
- });
2495
+ const result = outcome.kind === "success" ? outcome.result : lastCompaction;
2496
+ if (!result) {
2497
+ throw new Error("Auto-compaction succeeded without a result");
2505
2498
  }
2506
- const result = {
2507
- summary,
2508
- firstKeptEntryId,
2509
- tokensBefore,
2510
- details,
2511
- };
2512
2499
  this._emit({ type: "compaction_end", reason, result, aborted: false, willRetry });
2513
2500
  if (willRetry) {
2514
2501
  const messages = this.agent.state.messages;
@@ -2518,9 +2505,7 @@ export class AgentSession {
2518
2505
  }
2519
2506
  return true;
2520
2507
  }
2521
- // Auto-compaction can complete while follow-up/steering/custom messages are waiting.
2522
- // Continue once so queued messages are delivered.
2523
- return this.agent.hasQueuedMessages();
2508
+ return hadQueuedMessages || this.agent.hasQueuedMessages();
2524
2509
  }
2525
2510
  catch (error) {
2526
2511
  const errorMessage = error instanceof Error ? error.message : "compaction failed";
@@ -2548,7 +2533,7 @@ export class AgentSession {
2548
2533
  * same failure via auto-retry. Caller aborts are never retried; sleepAbortable rejects
2549
2534
  * with the abort reason if the signal fires mid-backoff.
2550
2535
  */
2551
- async _compactWithRetry(run, signal) {
2536
+ async _compactWithRetry(run, signal, provider) {
2552
2537
  const retrySettings = this.settingsManager.getRetrySettings();
2553
2538
  const maxAttempts = retrySettings.enabled ? Math.max(1, retrySettings.maxRetries + 1) : 1;
2554
2539
  const policy = {
@@ -2565,7 +2550,7 @@ export class AgentSession {
2565
2550
  if (signal.aborted || attempt >= maxAttempts)
2566
2551
  throw error;
2567
2552
  const message = error instanceof Error ? error.message : String(error);
2568
- const classified = classifyFailure({ message });
2553
+ const classified = classifyFailure({ message, provider });
2569
2554
  if (!classified.retryable)
2570
2555
  throw error;
2571
2556
  await sleepAbortable(computeRetryDelayMs(policy, attempt, { retryAfterMs: classified.retryAfterMs }), signal);
@@ -2854,6 +2839,7 @@ export class AgentSession {
2854
2839
  return classifyFailure({
2855
2840
  message: message.errorMessage,
2856
2841
  contextOverflow: isContextOverflow(message, contextWindow),
2842
+ provider: message.provider,
2857
2843
  }).retryable;
2858
2844
  }
2859
2845
  /**