billion-context-pi 0.1.82 → 0.1.83-pr.241.511

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.
package/README.md CHANGED
@@ -216,6 +216,29 @@ Blocks: 3 active (3.7K summary, 15.2K original compressed)
216
216
  b3 (T2) 3.3K→1.0K age=1m "Architecture review"
217
217
  ```
218
218
 
219
+ ## `/acp compact` — dedicated compression model
220
+
221
+ By default the `compress` tool's summaries are written by your **main model** — it spends output tokens producing them, and the summary-writing reasoning runs inside the main model's own turn. `/acp compact` offloads summary-writing to a **separate call** in one of two modes:
222
+
223
+ - **`session`** — use the **same model as your session**, but in a separate call that **reuses the session's prompt prefix** (system prompt + active tools + the exact messages Pi just sent). Because that prefix matches what the main model already sent, the provider's **prompt cache** makes the input cheap, and the compression reasoning stays out of the main model's context. Recommended way to get isolation without switching models.
224
+ - **`<id>`** — a model from Pi's `~/.pi/agent/models.json` (a cheaper small Qwen, `gpt-4o-mini`, …). No extra `baseUrl`/`apiKey` to duplicate. A different model has its own cache namespace, so it does **not** share the session prefix cache (input is billed in full), but you save on the model's per-token price.
225
+
226
+ ```
227
+ /acp compact # show the current compression model + list available models
228
+ /acp compact session # use the session model (shared prefix → prompt-cache friendly)
229
+ /acp compact <id> # set <id> (or <provider>/<id>) from models.json as the compression model
230
+ /acp compact reset # clear it — fall back to the main model
231
+ ```
232
+
233
+ - **No argument** (`/acp compact`): shows the currently configured compression model (or "not set — the main model writes summaries") and lists the model ids available in `models.json` for reference.
234
+ - **`/acp compact session`**: sets the compression model to the session model (shared-prefix mode).
235
+ - **`/acp compact <id>`**: sets a `models.json` model. Accepts a bare id (`qwen-mini`) or an explicit `provider/id` (`openai/gpt-4o-mini`). The choice is persisted to `~/.pi/acp.json` (`compressionModelId`) and applies across sessions.
236
+ - **`/acp compact reset`**: clears the setting; compression falls back to the main model (the default behavior).
237
+
238
+ When a compression model is set, each `compress` call produces the summary in a separate call, so the **main model spends no output tokens** on summaries. If the call fails (network error, API error, empty response), the extension **falls back to the main model's summary** for that range — compression never blocks the session.
239
+
240
+ > For `<id>` mode the model must have a working API key (in `models.json` or `~/.pi/agent/auth.json`); `session` mode reuses your session's existing credentials. See [CONFIGURATION.md](./CONFIGURATION.md#compressionmodelid) for details.
241
+
219
242
  ## `/acp-subagents` command
220
243
 
221
244
  **Optional, one-time setup — only if you also use [pi-subagents](https://github.com/nicobailon/pi-subagents).**
@@ -241,7 +264,9 @@ billion-context-pi writes a structured, always-on log to `~/.pi/acp.log` (overri
241
264
  - **Always written** (even with `debug: false`): `error`, `warn`, `info` levels — session start, every context turn (token usage / nudge decision), compress/decompress, delegate spawn/done, and **all errors and warnings** (config/state/tool failures, delegate errors, guardrail caps, update failures). Error lines include the message and stack trace.
242
265
  - **Written only when `debug: true`**: verbose `debug`-level diagnostics (full field dumps, per-turn internals).
243
266
 
244
- Each line: `<ISO timestamp> [<level>] [<scope>] key=value key=value`. The file rotates to `~/.pi/acp.log.old` at 10 MB.
267
+ - **Nudge audit trail**: whenever a context-limit nudge is injected, a compact one-line record (e.g. `[ACP nudge] EMERGENCY 95% · T1 · top range m00120–m00168`) is appended as a *display-only* session entry — it survives process restarts, shows up in TUI scrollback and the session file, and is never sent to the model.
268
+
269
+ Each line: `<ISO timestamp> [<level>] [<scope>] key=value key=value`. Multi-line values (e.g. the full nudge text in `nudge-injected`) are escaped (`\n` → literal `\\n`), so every entry occupies exactly **one physical line** and stays self-contained for `grep`. The file rotates to `~/.pi/acp.log.old` at 10 MB.
245
270
 
246
271
  ```sh
247
272
  tail -f ~/.pi/acp.log # watch the session live
package/README.zh-CN.md CHANGED
@@ -229,8 +229,9 @@ billion-context-pi 会向 `~/.pi/acp.log`(可用 `ACP_LOG_FILE` 覆盖)写入结
229
229
  - `warn` — 值得注意的非致命情况:紧急 nudge 注入、配置加载失败、自动更新网络错误、工具输出被截断、委派结果注入被跳过。
230
230
  - `info` — 生命周期事件:会话启动、每轮上下文变换摘要(消息数/token/压缩比/活跃块数)、压缩/解压、delegate 派发与完成、自动更新检查。
231
231
  - `debug` —— 仅在 `debug: true` 时额外写入(细粒度的字段级事件)。
232
+ - **Nudge 审计轨迹** — 无论何时注入上下文限制 nudge,一条紧凑的单行记录(如 `[ACP nudge] EMERGENCY 95% · T1 · top range m00120–m00168`)会作为*仅供显示*的会话条目写入——它跨进程重启存活,在 TUI 回滚历史和会话文件中可见,且永远不会发给模型。
232
233
 
233
- 每行格式:`<ISO 时间戳> [<级别>] [<范围>] key=value key=value`。文件达到 10 MB 时轮转为 `~/.pi/acp.log.old`。
234
+ 每行格式:`<ISO 时间戳> [<级别>] [<范围>] key=value key=value`。多行字段值(如 `nudge-injected` 中的完整 nudge 正文)会被转义(`\n` → 字面量 `\\n`),保证每条日志恰好占**一行物理行**,便于 `grep`。文件达到 10 MB 时轮转为 `~/.pi/acp.log.old`。
234
235
 
235
236
  ```bash
236
237
  tail -f ~/.pi/acp.log # 实时观察会话
@@ -7,7 +7,7 @@ var __export = (target, all) => {
7
7
  // src/contract.ts
8
8
  var SIDECAR_SCHEMA_VERSION = 1;
9
9
  function sidecarProducer() {
10
- return `billion-context-pi@${true ? "0.1.82" : "dev"}`;
10
+ return `billion-context-pi@${true ? "0.1.83" : "dev"}`;
11
11
  }
12
12
 
13
13
  export {
@@ -15,4 +15,4 @@ export {
15
15
  SIDECAR_SCHEMA_VERSION,
16
16
  sidecarProducer
17
17
  };
18
- //# sourceMappingURL=chunk-AKZJYVLH.js.map
18
+ //# sourceMappingURL=chunk-LLFL3CNI.js.map
@@ -0,0 +1,76 @@
1
+ import { ModelRuntime } from "@earendil-works/pi-coding-agent";
2
+ import type { Api, AssistantMessage, Context, Model, ModelsApiStreamOptions } from "@earendil-works/pi-ai";
3
+ /** Sentinel compressionModelId value meaning "use the session's own model".
4
+ * The compression call reuses the session's prompt prefix (system prompt +
5
+ * tools + messages) so it hits the provider's prompt cache — isolation without
6
+ * a cheaper model. Distinct from a models.json ref (which is "provider/id"). */
7
+ export declare const SESSION_MODEL_REF = "session";
8
+ /** A model defined in models.json, addressed by provider + id. */
9
+ export interface CompressionModelInfo {
10
+ provider: string;
11
+ id: string;
12
+ name?: string;
13
+ }
14
+ /** A resolved model ready for an LLM call. */
15
+ export interface ResolvedCompressionModel {
16
+ provider: string;
17
+ id: string;
18
+ model: Model<Api>;
19
+ }
20
+ /** Result of resolving a user-supplied ref. `model` is set when the ref is
21
+ * unambiguous; `ambiguous` lists candidates when a bare id matches several. */
22
+ export interface ResolveResult {
23
+ model: ResolvedCompressionModel | null;
24
+ ambiguous: CompressionModelInfo[];
25
+ }
26
+ /** Injectable LLM call. Defaults to ModelRuntime.complete (streams SSE). */
27
+ export type CompleteFn = (model: Model<Api>, context: Context, options?: ModelsApiStreamOptions<Api>) => Promise<AssistantMessage>;
28
+ export interface CompressionModelClientOptions {
29
+ /** Override the models.json path (default: ~/.pi/agent/models.json). */
30
+ modelsPath?: string;
31
+ /** Injectable LLM call for tests. */
32
+ complete?: CompleteFn;
33
+ /** Injectable runtime factory for tests. */
34
+ createRuntime?: (opts: {
35
+ modelsPath?: string;
36
+ }) => Promise<ModelRuntime>;
37
+ }
38
+ /** Default models.json path — Pi's own agent dir (homedir/.pi/agent by default,
39
+ * honors the PI_CODING_AGENT_DIR override). */
40
+ export declare function defaultModelsPath(): string;
41
+ /**
42
+ * Client for the dedicated compression model. Reads ~/.pi/agent/models.json for
43
+ * model discovery and uses a ModelRuntime (same source Pi uses) for the actual
44
+ * LLM call, so provider-specific request/auth handling is reused rather than
45
+ * re-implemented.
46
+ */
47
+ export declare class CompressionModelClient {
48
+ private readonly modelsPath;
49
+ private readonly complete;
50
+ private readonly createRuntime;
51
+ private runtimePromise;
52
+ constructor(options?: CompressionModelClientOptions);
53
+ private getRuntime;
54
+ /** Models the user defined in models.json (NOT built-in providers). */
55
+ listModels(): Promise<CompressionModelInfo[]>;
56
+ /** Resolve a ref: "provider/id" (explicit) or a bare id (searched across
57
+ * models.json first, then built-in providers). */
58
+ resolveModel(ref: string): Promise<ResolveResult>;
59
+ /** Generate a summary with the resolved model from a FULLY BUILT context
60
+ * (the caller controls systemPrompt/messages/tools — used for both the
61
+ * fresh single-message prompt and the shared-prefix prompt). Throws on API
62
+ * error or an empty response — the caller falls back to the main model. */
63
+ summarizeContext(resolved: ResolvedCompressionModel, context: Context, maxTokens: number): Promise<string>;
64
+ /** Generate a summary of `content` with the resolved model using a fresh
65
+ * single-message prompt (no session prefix reuse). */
66
+ summarize(resolved: ResolvedCompressionModel, content: string, systemPrompt: string, maxTokens: number): Promise<string>;
67
+ }
68
+ /** System prompt for the compression model: reuse the kernel's tier-1 rules so
69
+ * its output matches the quality/format the main model would produce. */
70
+ export declare function buildSummarizeSystemPrompt(prompts: {
71
+ compressPhilosophy: string;
72
+ howToCompressRules: string;
73
+ }, topic: string | undefined): string;
74
+ /** Truncate very large content, keeping the head (goal/early context) and tail
75
+ * (most recent state) with a marker in the middle. */
76
+ export declare function truncateContent(content: string, maxChars: number): string;
@@ -1,5 +1,5 @@
1
1
  import { Type, type Static } from "typebox";
2
- import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
2
+ import { type ExtensionAPI, type ToolDefinition } from "@earendil-works/pi-coding-agent";
3
3
  import type { AcpRuntime } from "./runtime.js";
4
4
  import { type ToolPromptOverrides } from "./surface.js";
5
5
  import { type CompressionBlock, type CompressionState } from "acp-kernel";
@@ -20,7 +20,7 @@ declare const CompressParams: Type.TObject<{
20
20
  summaryMaxChars: Type.TOptional<Type.TNumber>;
21
21
  }>;
22
22
  type CompressArgs = Static<typeof CompressParams>;
23
- export declare function makeCompressTool(runtime: AcpRuntime, overrides?: ToolPromptOverrides): ToolDefinition<typeof CompressParams>;
23
+ export declare function makeCompressTool(runtime: AcpRuntime, pi: ExtensionAPI, overrides?: ToolPromptOverrides): ToolDefinition<typeof CompressParams>;
24
24
  type RangeEntry = Static<typeof RangeSpec>;
25
25
  export declare function normalizeRanges(args: CompressArgs): RangeEntry[] | string;
26
26
  export declare function tailRepair(s: string): string | undefined;
@@ -1 +1,32 @@
1
+ export type HostConfigSurface = {
2
+ CONFIG_DIR_NAME?: unknown;
3
+ getAgentDir?: unknown;
4
+ };
5
+ /** Pi's name — the fallback, and what releases before fork-host resolution used on every host. */
6
+ export declare const PI_CONFIG_DIR_NAME = ".pi";
7
+ /**
8
+ * Config directory name, resolved from the host package the extension runs under.
9
+ *
10
+ * Every path in this adapter is `<home|cwd>/<name>/…`, with the agent dir at
11
+ * `~/<name>/agent` — Pi's layout, where CONFIG_DIR_NAME is ".pi". Resolution order:
12
+ * 1. the host's CONFIG_DIR_NAME, if it is a single directory name (Pi);
13
+ * 2. the parent of the host's getAgentDir(), if that is `~/<name>/agent` — Pi forks
14
+ * that do not re-export CONFIG_DIR_NAME, or export it with other semantics (Prime:
15
+ * CONFIG_DIR_NAME ".prime/agent", getAgentDir() ~/.prime/agent → ".prime");
16
+ * 3. ".pi".
17
+ * The namespace import is deliberate: a missing named export fails at link time
18
+ * under plain Node ESM→CJS interop and as `undefined` under loader aliasing (#364).
19
+ * It is the ONLY value import from the pi package. See docs/host-adapter.md §4.
20
+ */
21
+ export declare function resolveConfigDirName(host: HostConfigSurface, home: string): string;
1
22
  export declare const CONFIG_DIR_NAME: string;
23
+ /**
24
+ * `<root>/<name>/<segments>` for a user-authored file (acp.json, prompt packs). When
25
+ * `name` is not ".pi", an existing `<root>/.pi/<segments>` is still used while the
26
+ * host's own path does not exist: earlier releases read ".pi" on every host, and Prime
27
+ * users were told to put acp.json there (#467).
28
+ */
29
+ export declare function userConfigPathIn(name: string, root: string, ...segments: string[]): string;
30
+ export declare function userConfigPath(root: string, ...segments: string[]): string;
31
+ /** Global then project acp.json; later entries override earlier ones. */
32
+ export declare function acpJsonFiles(cwd: string): string[];
package/dist/config.d.ts CHANGED
@@ -334,6 +334,10 @@ export interface AdapterConfig {
334
334
  * replacing the kernel's tuned compression rules may reduce summary quality
335
335
  * (lost paths/signatures/decisions → worse retrieval). */
336
336
  acknowledgePromptsRisk?: boolean;
337
+ /** Model ref (bare id or "provider/id") from models.json used to write
338
+ * compression summaries with a dedicated (usually cheaper) model. Absent =
339
+ * the main model writes summaries (default). Set via `/acp compact <id>`. */
340
+ compressionModelId?: string;
337
341
  /** Override structural sections of the ACP system prompt (ACP TAGS, TOOLS,
338
342
  * WHEN TO COMPRESS, ...). Tri-state per section: string = replace, null =
339
343
  * remove, omitted = default. Not risk-gated — these are documentation
@@ -378,9 +382,19 @@ export interface HostSessionConfig {
378
382
  /** Count host-injected custom_message entries (agent_message) as turn
379
383
  * boundaries. Default: false (pi-native behavior). */
380
384
  countCustomMessages?: boolean;
385
+ /** #578: optional customType allowlist refining countCustomMessages — when
386
+ * set (with countCustomMessages:true), only injected entries whose
387
+ * customType is listed start a turn, keeping metadata injections
388
+ * (harness_digest, ipython_state) out of nudge/retry accounting. Ignored
389
+ * unless countCustomMessages:true; malformed values warn and fall back to
390
+ * "all injected types count". */
391
+ customMessageTypes?: string[];
381
392
  }
382
393
  export interface ResolvedHostSession {
383
394
  countCustomMessages: boolean;
395
+ /** Present only when countCustomMessages is on and a valid allowlist was
396
+ * configured (#578). */
397
+ customMessageTypes?: readonly string[];
384
398
  }
385
399
  /** Resolve the host-session turn-boundary policy from the adapter, handling
386
400
  * the boolean shorthand (`true` enables countCustomMessages). Invalid values
package/dist/contract.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SIDECAR_SCHEMA_VERSION,
3
3
  sidecarProducer
4
- } from "./chunk-AKZJYVLH.js";
4
+ } from "./chunk-LLFL3CNI.js";
5
5
  export {
6
6
  SIDECAR_SCHEMA_VERSION,
7
7
  sidecarProducer
@@ -26,6 +26,8 @@ export declare function usageAnchorPredatesCompression(entries: AnchorEntry[]):
26
26
  export interface AnchorStaleness {
27
27
  predates: boolean;
28
28
  netReclaimed: number;
29
+ fresh: boolean;
30
+ lastRealTokens: number;
29
31
  }
30
32
  export declare function compressionAnchorStaleness(entries: AnchorEntry[], blocks: readonly CompressionBlock[], countTokens: (text: string) => number): AnchorStaleness;
31
33
  export {};
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { ExtensionFactory } from "@earendil-works/pi-coding-agent";
2
+ import type { NudgeDecision } from "acp-kernel";
2
3
  import { type AdapterConfig } from "./config.js";
3
4
  export { createRuntime } from "./runtime.js";
4
5
  export type { AcpRuntime, SessionRef } from "./runtime.js";
@@ -6,3 +7,4 @@ export { deriveChildState } from "./state.js";
6
7
  export declare function createAcpExtension(adapter?: AdapterConfig): ExtensionFactory;
7
8
  declare const _default: ExtensionFactory;
8
9
  export default _default;
10
+ export declare function formatNudgeRecord(nudge: NudgeDecision, emergency: boolean): string;