billion-context-pi 0.1.41 → 0.1.45

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,34 @@ 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 or noop (0-block panel) →
44
+ * failure (count++), success panel (>= 1 block) → reset, other non-error
45
+ * text → neutral (count unchanged). Returns the failure count, the
46
+ * toolCallId of the newest failure that still needs a retry prompt (null
47
+ * when none, capped, or count 0), and whether the cap was just reached. */
48
+ noteCompressOutcomes(turnKey: string, outcomes: ReadonlyArray<{
49
+ toolCallId: string;
50
+ isError: boolean;
51
+ success: boolean;
52
+ noop?: boolean;
53
+ }>): {
54
+ count: number;
55
+ retryFor: string | null;
56
+ cappedNow: boolean;
57
+ };
58
+ /** True when this turn already burned MAX_COMPRESS_ATTEMPTS failed/no-op
59
+ * compress calls — used to stop re-injecting the (dedup-exempt) emergency
60
+ * nudge that would otherwise keep looping no-op compressions (issue #6). */
61
+ compressRetryCappedFor(turnKey: string): boolean;
29
62
  clearNudgeTracking(): void;
63
+ clearCompressRetryTracking(): void;
30
64
  liveContextLimit(ctx: ExtensionContext): number;
31
65
  configFor(ctx: ExtensionContext): Config;
32
66
  /** Re-read ~/.<dir>/acp.json + <cwd>/<dir>/acp.json and re-derive the adapter
@@ -40,6 +74,15 @@ export interface AcpRuntime {
40
74
  }>;
41
75
  save(state: CompressionState, ctx: ExtensionContext): Promise<void>;
42
76
  acquireLock(sid: string): Promise<() => void>;
77
+ /** Per-session overflow self-heal state (learned window + armed emergency).
78
+ * Keyed by session id so concurrent sessions cannot share an episode. */
79
+ overflowFor(sid: string): OverflowEpisode;
80
+ /** Drop a session's overflow episode entirely (session_shutdown): releases
81
+ * the map entry so a long-lived process cycling through many sessions
82
+ * doesn't accumulate them. */
83
+ overflowDrop(sid: string): void;
43
84
  }
85
+ /** Max FAILED compress calls that get a retry prompt per user turn. */
86
+ export declare const MAX_COMPRESS_ATTEMPTS = 3;
44
87
  export declare function createRuntime(adapter: AdapterConfig): AcpRuntime;
45
88
  export {};
@@ -1,8 +1,51 @@
1
- export declare function resolveAgentDir(): string;
1
+ /** The four ACP tools to ensure on every pi-subagents builtin agent. */
2
+ export declare const ACP_TOOLS: readonly ["compress", "decompress", "search_context", "acp_status"];
2
3
  export interface SetupResult {
3
4
  path: string;
4
5
  action: "skipped" | "updated" | "failed";
5
6
  reason?: string;
6
7
  }
7
- export declare function ensureSubagentAcpTools(settingsPath?: string): Promise<SetupResult>;
8
- export declare function runSetupAndNotify(notify?: (msg: string) => void): Promise<SetupResult>;
8
+ export interface SetupOptions {
9
+ /** Injectable agent directory (defaults to env PI_CODING_AGENT_DIR or ~/.pi/agent). */
10
+ agentDir?: string;
11
+ /** Injectable cwd for project-scope detection (defaults to process.cwd()). */
12
+ cwd?: string;
13
+ /** Explicit install directory (skips detection; for git installs or forks). */
14
+ installDir?: string;
15
+ }
16
+ /**
17
+ * Resolve the pi agent config directory (e.g. ~/.pi/agent), honoring the
18
+ * PI_CODING_AGENT_DIR environment variable (mirroring pi's own resolution).
19
+ */
20
+ export declare function resolveAgentDir(): string;
21
+ interface ParsedBuiltin {
22
+ name: string;
23
+ /** Frontmatter `tools` list; undefined when the agent is unrestricted. */
24
+ tools?: string[];
25
+ }
26
+ /**
27
+ * Detect an installed pi-subagents package and return its directory, or null
28
+ * when no installation is found.
29
+ *
30
+ * Checked in priority order:
31
+ * 1. user-scope npm install: <agentDir>/npm/node_modules/pi-subagents
32
+ * 2. project-scope npm install: <cwd>/.pi/npm/node_modules/pi-subagents
33
+ * 3. user-scope extension dir: <agentDir>/extensions/<name>/package.json where name === "pi-subagents"
34
+ * 4. project-scope extension dir: <cwd>/.pi/extensions/<name>/package.json
35
+ *
36
+ * Git installs and the legacy global npm location are intentionally not
37
+ * checked: a miss there is a safe no-op (ACP tools stay un-injected).
38
+ */
39
+ export declare function findPiSubagentsInstall(agentDir: string, cwd: string): string | null;
40
+ /**
41
+ * Discover the builtin agents shipped by the detected pi-subagents package
42
+ * from its agents/*.md frontmatter (name + tools). Returns an empty list
43
+ * when the package ships no agents directory.
44
+ */
45
+ export declare function discoverBuiltinAgents(installDir: string): ParsedBuiltin[];
46
+ /**
47
+ * Ensure ACP context tools are present in pi-subagents' agent overrides.
48
+ * No-op (skipped) unless a pi-subagents installation is detected.
49
+ */
50
+ export declare function ensureSubagentAcpTools(settingsPath?: string, options?: SetupOptions): SetupResult;
51
+ 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">;
package/dist/update.d.ts CHANGED
@@ -1,2 +1,28 @@
1
+ export type NpmRunner = (args: string[], opts: {
2
+ cwd?: string;
3
+ timeout: number;
4
+ }) => Promise<{
5
+ code: number;
6
+ stdout: string;
7
+ stderr: string;
8
+ }>;
9
+ export declare const runNpm: NpmRunner;
10
+ export declare function setRunNpmForTest(impl: NpmRunner): void;
11
+ export type NodeRunner = (args: string[], opts: {
12
+ timeout: number;
13
+ }) => Promise<{
14
+ code: number;
15
+ stdout: string;
16
+ stderr: string;
17
+ }>;
18
+ export declare const runNode: NodeRunner;
19
+ export declare function setRunNodeForTest(impl: NodeRunner): void;
20
+ export declare function isNewer(latest: string, current: string): boolean;
1
21
  export declare function findNpmRoot(extDir: string): string | undefined;
22
+ export type InstallOutcome = "ok" | "failed" | "rolled-back";
23
+ export declare function verifyInstall(npmDir: string, latest: string): Promise<{
24
+ ok: boolean;
25
+ reason?: string;
26
+ }>;
27
+ export declare function autoInstallLatest(latest: string, extDirOverride?: string): Promise<InstallOutcome>;
2
28
  export declare function checkForUpdate(autoUpdate: boolean, notify?: (msg: string) => void): Promise<void>;
@@ -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.45",
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.32",
64
64
  "billion-context-kit": "0.2.0",
65
65
  "tsup": "^8.5.1",
66
66
  "tsx": "^4.23.1",