billion-context-pi 0.1.41 → 0.1.43

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.
@@ -0,0 +1,45 @@
1
+ export declare const OVERFLOW_MARKER: RegExp;
2
+ export interface OverflowInfo {
3
+ isOverflow: boolean;
4
+ /** The real context window, when the provider stated it in the error. */
5
+ window?: number;
6
+ message: string;
7
+ }
8
+ export declare function inspectOverflowMessage(haystack: string | undefined | null): OverflowInfo;
9
+ /**
10
+ * Reserve the model's output budget from the context window, so the kernel's
11
+ * nudge/truncate bands sit below (window - maxOutput) and the context always
12
+ * leaves room for the model's reply. This prevents the "context + output >
13
+ * window" overflow on a small window (agents routinely set a large max output).
14
+ * Returns the window unchanged when maxOutput is not usable (non-positive,
15
+ * non-finite, or >= window — a maxOutput >= window request is degenerate and is
16
+ * left to the overflow self-heal).
17
+ */
18
+ export declare function reserveOutputHeadroom(window: number, maxOutput: number): number;
19
+ /**
20
+ * Whether the OUTPUT budget should be reserved from the context window at
21
+ * all. Anthropic's Messages API enforces the input limit INDEPENDENTLY of
22
+ * max_tokens (the output budget is separate — input up to the window works
23
+ * with any max_tokens), so reserving the model's output capability would
24
+ * shift the nudge/truncate bands down by maxTokens on every session with no
25
+ * safety gain (e.g. a 200k model with a 64k output budget would start
26
+ * compressing around 136k). The OpenAI-family APIs count output against the
27
+ * window, so the reservation is only needed there. Unknown APIs reserve
28
+ * (conservative — a missed reservation at worst overflows once and the
29
+ * self-heal corrects it).
30
+ */
31
+ export declare function shouldReserveOutputHeadroom(api: string | undefined): boolean;
32
+ export declare class OverflowEpisode {
33
+ /** Real windows learned from overflow errors, keyed by model id. A learned
34
+ * window is model-specific: switching to a bigger model mid-session must
35
+ * not inherit the smaller model's learned limit (that would re-center the
36
+ * bands below the new model's real window → premature compression). */
37
+ private learned;
38
+ learnedWindowFor(modelId: string): number | null;
39
+ setLearnedWindow(modelId: string, window: number): void;
40
+ /** When true, the next context event forces usage >=95% (emergency). Kept
41
+ * session-scoped (not per-model): the context did not shrink, so the next
42
+ * turn needs the emergency regardless of which model answers it. */
43
+ armed: boolean;
44
+ reset(): void;
45
+ }
package/dist/runtime.d.ts CHANGED
@@ -4,11 +4,22 @@ import { type AdapterConfig } from "./config.js";
4
4
  import { DensityEstimator } from "./density.js";
5
5
  import { entriesToCoreMessages } from "./messages.js";
6
6
  import { SessionStateStore } from "./state.js";
7
+ import { ThrottleEpisode } from "./throttle-retry.js";
8
+ import { OverflowEpisode } from "./overflow-selfheal.js";
7
9
  type AgentMessage = SessionMessageEntry["message"];
8
10
  export declare function readContextEntries(sm: ExtensionContext["sessionManager"]): SessionEntry[];
9
11
  export declare function isPiHost(sm: ExtensionContext["sessionManager"]): boolean;
10
12
  export interface AcpRuntime {
11
13
  core: CompressionCore;
14
+ /** Per-session provider-throttle retry episode (attempt budget + kick
15
+ * pacing), keyed by session id so concurrent sessions in one extension
16
+ * instance cannot share an episode. Reset on session_start and on any
17
+ * real progress / user input. */
18
+ throttleFor: (sid: string) => ThrottleEpisode;
19
+ /** Drop a session's throttle episode entirely (session_shutdown): aborts a
20
+ * pending kick sleep and releases the map entry so a long-lived process
21
+ * that cycles through many sessions doesn't accumulate them. */
22
+ throttleDrop: (sid: string) => void;
12
23
  store: SessionStateStore;
13
24
  density: DensityEstimator;
14
25
  /** 设置 countTokens 闭包使用的 modelId(每轮 context 事件调用)。 */
@@ -22,11 +33,29 @@ export interface AcpRuntime {
22
33
  /** Drop per-session tracking state (session_start). */
23
34
  clearSessionTracking(sid: string): void;
24
35
  adapter: AdapterConfig;
36
+ setAdapter(adapter: AdapterConfig): void;
25
37
  prompts: Prompts;
26
38
  setPrompts(prompts: Prompts): void;
27
39
  markNudgeShown(turnKey: string): void;
28
40
  nudgeShownFor(turnKey: string): boolean;
41
+ /** Process compress toolResults for the CURRENT user turn only (the caller
42
+ * scopes the list — see collectCompressOutcomes in src/index.ts); idempotent
43
+ * per toolCallId. Outcome classes: isError → failure (count++), success
44
+ * panel text → reset, other non-error text → neutral (count unchanged).
45
+ * Returns the failure count, the toolCallId of the newest failure that
46
+ * still needs a retry prompt (null when none, capped, or count 0), and
47
+ * whether the cap was just reached by this call. */
48
+ noteCompressOutcomes(turnKey: string, outcomes: ReadonlyArray<{
49
+ toolCallId: string;
50
+ isError: boolean;
51
+ success: boolean;
52
+ }>): {
53
+ count: number;
54
+ retryFor: string | null;
55
+ cappedNow: boolean;
56
+ };
29
57
  clearNudgeTracking(): void;
58
+ clearCompressRetryTracking(): void;
30
59
  liveContextLimit(ctx: ExtensionContext): number;
31
60
  configFor(ctx: ExtensionContext): Config;
32
61
  /** Re-read ~/.<dir>/acp.json + <cwd>/<dir>/acp.json and re-derive the adapter
@@ -40,6 +69,15 @@ export interface AcpRuntime {
40
69
  }>;
41
70
  save(state: CompressionState, ctx: ExtensionContext): Promise<void>;
42
71
  acquireLock(sid: string): Promise<() => void>;
72
+ /** Per-session overflow self-heal state (learned window + armed emergency).
73
+ * Keyed by session id so concurrent sessions cannot share an episode. */
74
+ overflowFor(sid: string): OverflowEpisode;
75
+ /** Drop a session's overflow episode entirely (session_shutdown): releases
76
+ * the map entry so a long-lived process cycling through many sessions
77
+ * doesn't accumulate them. */
78
+ overflowDrop(sid: string): void;
43
79
  }
80
+ /** Max FAILED compress calls that get a retry prompt per user turn. */
81
+ export declare const MAX_COMPRESS_ATTEMPTS = 3;
44
82
  export declare function createRuntime(adapter: AdapterConfig): AcpRuntime;
45
83
  export {};
@@ -0,0 +1,52 @@
1
+ export declare const THROTTLE_RETRY_ERROR_MESSAGE = "429 rate limit: Too many tokens, please wait before trying again.";
2
+ export declare const THROTTLE_KICK_SENTINEL = "[ACP:provider-throttle]";
3
+ export declare const THROTTLE_KICK_TEXT = "[ACP:provider-throttle] The previous assistant response was interrupted by a provider rate limit (transient, not a real failure). Resume the task exactly where it left off \u2014 do not re-run completed steps and do not discuss the interruption unless asked.";
4
+ export interface ThrottleErrorProbe {
5
+ role: string;
6
+ stopReason?: string;
7
+ errorMessage?: string;
8
+ content: unknown;
9
+ }
10
+ export declare function isThrottleError(msg: ThrottleErrorProbe): boolean;
11
+ export declare function isKickMessage(msg: {
12
+ role: string;
13
+ content: unknown;
14
+ }): boolean;
15
+ export interface ThrottleRetryConfig {
16
+ enabled?: boolean;
17
+ maxRetries?: number;
18
+ baseDelayMs?: number;
19
+ maxDelayMs?: number;
20
+ backoffMode?: "exponential" | "fixed";
21
+ }
22
+ export interface ResolvedThrottleRetry {
23
+ enabled: boolean;
24
+ maxRetries: number;
25
+ baseDelayMs: number;
26
+ maxDelayMs: number;
27
+ backoffMode: "exponential" | "fixed";
28
+ }
29
+ export declare const DEFAULT_THROTTLE_RETRY: ResolvedThrottleRetry;
30
+ export declare function resolveThrottleRetry(cfg: boolean | ThrottleRetryConfig | undefined): ResolvedThrottleRetry;
31
+ export declare function throttleDelayMs(kickNumber: number, r: ResolvedThrottleRetry): number;
32
+ export interface ThrottleEpisodeState {
33
+ attempts: number;
34
+ kicks: number;
35
+ candidate: boolean;
36
+ }
37
+ export declare const INITIAL_THROTTLE_STATE: ThrottleEpisodeState;
38
+ export declare class ThrottleEpisode {
39
+ state: ThrottleEpisodeState;
40
+ private cancel;
41
+ reset(): void;
42
+ onProgress(): void;
43
+ onUserMessage(kick: boolean): void;
44
+ onThrottleError(maxRetries: number): "rewrite" | "exhausted";
45
+ onNonThrottleError(): void;
46
+ readyToKick(maxRetries: number): boolean;
47
+ onKickStarted(): void;
48
+ onKickCancelled(): void;
49
+ sleepController(): AbortController;
50
+ cancelSleep(): void;
51
+ }
52
+ export declare function abortableSleep(ms: number, signal: AbortSignal): Promise<"ok" | "aborted">;
@@ -1,5 +1,6 @@
1
1
  import type { Prompts } from "acp-kernel";
2
2
  import type { AdapterConfig, CompressConfig, DelegateConfig } from "./config.js";
3
+ import type { ThrottleRetryConfig } from "./throttle-retry.js";
3
4
  /** User-facing config keys (subset of AdapterConfig). Loaded from
4
5
  * ~/.<CONFIG_DIR_NAME>/acp.json (global) and <cwd>/.<CONFIG_DIR_NAME>/acp.json
5
6
  * (project-local overrides project-global). Project wins over global. */
@@ -11,6 +12,7 @@ export interface UserAcpConfig {
11
12
  toolOutputMaxBytes?: number;
12
13
  delegate?: boolean | DelegateConfig;
13
14
  compress?: CompressConfig;
15
+ throttleRetry?: boolean | ThrottleRetryConfig;
14
16
  displayUsage?: "merged" | "separate";
15
17
  prompts?: Partial<Prompts>;
16
18
  acknowledgePromptsRisk?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "billion-context-pi",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "description": "One billion, not one million. Model-driven context management for the Pi coding agent.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -60,7 +60,7 @@
60
60
  "devDependencies": {
61
61
  "@earendil-works/pi-coding-agent": "0.83.0",
62
62
  "@types/node": "^26.1.2",
63
- "acp-kernel": "0.0.28",
63
+ "acp-kernel": "0.0.30",
64
64
  "billion-context-kit": "0.2.0",
65
65
  "tsup": "^8.5.1",
66
66
  "tsx": "^4.23.1",