billion-context-pi 0.1.75-pr.493.309 → 0.1.75

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
@@ -76,14 +76,6 @@ pi install npm:billion-context-pi
76
76
 
77
77
  That's it. The extension auto-loads on next Pi startup. No configuration needed — it reads your model's context window automatically.
78
78
 
79
- **Prefer a stable channel?** Install with the `stable` dist-tag instead:
80
-
81
- ```bash
82
- pi install npm:billion-context-pi@stable
83
- ```
84
-
85
- Stable releases are cut explicitly (less often than regular releases). Stable installs auto-update to the newest *stable* release only — regular releases and dev prereleases never reach them. Plain installs track `latest`. Switch channels at any time by reinstalling with the other spec.
86
-
87
79
  > **Using another sub-agent extension?** billion-context-pi ships its own `acp_delegate` sub-agent tool (see below) at a fraction of the context cost (~600 tok vs ~7K tok/turn). Two delegation tools in one session only make the model's choice noisier, so pick one:
88
80
  > - **Use ACP's delegate** — remove the other extension: `pi remove npm:pi-subagents`
89
81
  > - **Keep your own sub-agent** — turn ACP's delegate off in `acp.json`: `{ "delegate": false }` (see *Using your own sub-agent instead* below)
package/README.zh-CN.md CHANGED
@@ -75,14 +75,6 @@ pi install npm:billion-context-pi
75
75
 
76
76
  完成。扩展在下次 Pi 启动时自动加载。无需配置 —— 它会自动读取模型的上下文窗口。
77
77
 
78
- **想用 stable 通道?** 改用 `stable` dist-tag 安装:
79
-
80
- ```bash
81
- pi install npm:billion-context-pi@stable
82
- ```
83
-
84
- stable 版本是显式指定发布的(频率低于常规发版)。stable 安装的自动更新只追最新的 *stable* 正式版 —— 常规正式版和 dev 预发布版都不会推给它们。普通安装跟踪 `latest`。随时可以用另一个 spec 重装来切换通道。
85
-
86
78
  > **你另有子代理扩展?** billion-context-pi 自带 `acp_delegate` 子代理工具(见下文),上下文成本极低(~600 tok vs ~7K tok/轮)。同一会话里两套委派工具只会让模型的选择更混乱,二选一:
87
79
  > - **用 ACP 的 delegate** —— 卸载另一个扩展:`pi remove npm:pi-subagents`
88
80
  > - **保留你自己的子代理** —— 在 `acp.json` 里关掉 ACP 的 delegate:`{ "delegate": false }`(见下文*改用你自己的子代理*)
@@ -0,0 +1 @@
1
+ export declare function assertNotAborted(signal: AbortSignal | undefined): void;
package/dist/config.d.ts CHANGED
@@ -152,6 +152,16 @@ export interface CompressSettings {
152
152
  * overrides only; a pack's `toolPrompts` follow the base selection (tool
153
153
  * definitions freeze at extension load, before the model is known). */
154
154
  promptPack?: string;
155
+ /** Opt-in wire-level strip of historical image payloads (issue #321, kernel
156
+ * #215). Default: false (images ride along verbatim, current behavior).
157
+ * When true, every message older than `stripImagesKeepRecent` has its image
158
+ * parts dropped from the outbound provider body (image-only messages
159
+ * collapse to a "[image]" text placeholder). Host-side policy only — the
160
+ * strip primitive lives in acp-kernel's wire layer. */
161
+ stripImages?: boolean;
162
+ /** How many of the MOST RECENT messages keep their image payloads when
163
+ * `stripImages` is enabled. Default: 5. Ignored when stripImages is off. */
164
+ stripImagesKeepRecent?: number;
155
165
  }
156
166
  /** Per-provider compression overrides. Carries the same tuning fields as the
157
167
  * global level, plus an optional per-model map keyed by model id. */
package/dist/index.js CHANGED
@@ -5631,7 +5631,9 @@ function mergeCompress(global, provider, model) {
5631
5631
  drop: model?.reasoning?.drop ?? provider?.reasoning?.drop ?? global?.reasoning?.drop,
5632
5632
  threshold: model?.reasoning?.threshold ?? provider?.reasoning?.threshold ?? global?.reasoning?.threshold
5633
5633
  },
5634
- promptPack: model?.promptPack ?? provider?.promptPack ?? global?.promptPack
5634
+ promptPack: model?.promptPack ?? provider?.promptPack ?? global?.promptPack,
5635
+ stripImages: model?.stripImages ?? provider?.stripImages ?? global?.stripImages,
5636
+ stripImagesKeepRecent: model?.stripImagesKeepRecent ?? provider?.stripImagesKeepRecent ?? global?.stripImagesKeepRecent
5635
5637
  };
5636
5638
  }
5637
5639
  function resolveCompress(compress, provider, modelId) {
@@ -7331,6 +7333,13 @@ function createRuntime(adapter) {
7331
7333
  }
7332
7334
  return gated;
7333
7335
  }
7336
+ function stripImagesFor(ctx) {
7337
+ const m2 = ctx.model;
7338
+ const c = resolveCompress(adapterRef.compress, m2?.provider, m2?.id);
7339
+ const raw = Number(c.stripImagesKeepRecent);
7340
+ const keepRecent = Number.isFinite(raw) && raw >= 0 ? Math.floor(raw) : 5;
7341
+ return { enabled: c.stripImages === true, keepRecent };
7342
+ }
7334
7343
  async function reloadConfig(cwd) {
7335
7344
  let user;
7336
7345
  try {
@@ -7434,7 +7443,7 @@ function createRuntime(adapter) {
7434
7443
  return promptsRef;
7435
7444
  }, setPrompts: (p) => {
7436
7445
  promptsRef = p;
7437
- }, markNudgeShown, nudgeShownFor, nudgeShownTokensFor, clearNudgeTracking, clearNudgeTokenStamps, noteCompressOutcomes, compressRetryCappedFor, clearCompressRetryTracking, liveContextLimit, configFor, reasoningDropFor, reloadConfig, stateFor, save, deriveChildState: deriveChild, acquireLock, overflowFor, overflowDrop, noteDeadCompress, clearDeadCompress, throttleFor, throttleDrop, noteTokenScale, dropTokenScale, noteHostUsage, dropHostUsageSamples, noteSizeDivergence, dropSizeDivergence, noteTerminalEscape, dropTerminalEscape, noteTruncationSkipped, dropTruncationSkipped };
7446
+ }, markNudgeShown, nudgeShownFor, nudgeShownTokensFor, clearNudgeTracking, clearNudgeTokenStamps, noteCompressOutcomes, compressRetryCappedFor, clearCompressRetryTracking, liveContextLimit, configFor, reasoningDropFor, reloadConfig, stateFor, save, deriveChildState: deriveChild, acquireLock, overflowFor, overflowDrop, noteDeadCompress, clearDeadCompress, throttleFor, throttleDrop, noteTokenScale, dropTokenScale, noteHostUsage, dropHostUsageSamples, noteSizeDivergence, dropSizeDivergence, noteTerminalEscape, dropTerminalEscape, noteTruncationSkipped, dropTruncationSkipped, stripImagesFor };
7438
7447
  }
7439
7448
 
7440
7449
  // node_modules/typebox/build/system/memory/memory.mjs
@@ -11888,6 +11897,14 @@ function adjustedTokenCount(core, messages, state, config, prelim, imageTokensBy
11888
11897
  return view.drifted ? view.viewTokens : prelim;
11889
11898
  }
11890
11899
 
11900
+ // src/abort.ts
11901
+ function assertNotAborted(signal) {
11902
+ if (!signal?.aborted) return;
11903
+ const err = new Error("Operation aborted");
11904
+ err.name = "AbortError";
11905
+ throw err;
11906
+ }
11907
+
11891
11908
  // src/compat.ts
11892
11909
  function normalizeSystemPrompt(input) {
11893
11910
  if (input === void 0) return "";
@@ -11959,11 +11976,11 @@ function makeCompressTool(runtime, overrides) {
11959
11976
  "Never compress content the current step is actively using."
11960
11977
  ],
11961
11978
  parameters: CompressParams,
11962
- async execute(toolCallId, params, _signal, _onUpdate, ctx) {
11979
+ async execute(toolCallId, params, signal, _onUpdate, ctx) {
11963
11980
  if (runtime.refused) return { details: void 0, content: [{ type: "text", text: runtime.refusalMessage ?? UNSUPPORTED_HOST_MESSAGE }] };
11964
11981
  let result;
11965
11982
  try {
11966
- result = await handleCompress(params, runtime, ctx, toolCallId);
11983
+ result = await handleCompress(params, runtime, ctx, toolCallId, signal);
11967
11984
  } catch (e) {
11968
11985
  logThrow("compress", e, { sid: ctx.sessionManager.getSessionId(), ranges: typeof params.content === "string" ? "string" : params.content?.length ?? 0 });
11969
11986
  throw e;
@@ -12219,12 +12236,14 @@ function tierReadyHint(state, config) {
12219
12236
  }
12220
12237
  return "";
12221
12238
  }
12222
- async function handleCompress(args, runtime, ctx, toolCallId) {
12239
+ async function handleCompress(args, runtime, ctx, toolCallId, signal) {
12240
+ assertNotAborted(signal);
12223
12241
  const maybeRanges = normalizeRanges(args);
12224
12242
  if (typeof maybeRanges === "string") throw new Error(maybeRanges);
12225
12243
  const ranges = maybeRanges;
12226
12244
  if (ranges.length === 0) return "No ranges provided.";
12227
12245
  const { state: initialState, coreMessages, entries } = await runtime.stateFor(ctx);
12246
+ assertNotAborted(signal);
12228
12247
  const config = runtime.configFor(ctx);
12229
12248
  const modelId = ctx.model?.id ?? "default";
12230
12249
  const systemPromptText = getSystemPromptText(ctx);
@@ -12281,6 +12300,7 @@ async function handleCompress(args, runtime, ctx, toolCallId) {
12281
12300
  state,
12282
12301
  config
12283
12302
  });
12303
+ assertNotAborted(signal);
12284
12304
  const rewriteSpans = applied.result.blocksCreated > 0 ? tier3OnlyRewrite(applied.state.blocks.slice(-applied.result.blocksCreated), applied.state.blocks) : null;
12285
12305
  if (rewriteSpans) {
12286
12306
  await runtime.save(state, ctx);
@@ -12389,11 +12409,11 @@ function makeDecompressTool(runtime, overrides) {
12389
12409
  "Use full:true to recurse through all nested tiers to original messages."
12390
12410
  ],
12391
12411
  parameters: DecompressParams,
12392
- async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
12412
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
12393
12413
  if (runtime.refused) return { details: void 0, content: [{ type: "text", text: runtime.refusalMessage ?? UNSUPPORTED_HOST_MESSAGE }] };
12394
12414
  let result;
12395
12415
  try {
12396
- result = await handleDecompress(params, runtime, ctx);
12416
+ result = await handleDecompress(params, runtime, ctx, signal);
12397
12417
  } catch (e) {
12398
12418
  logThrow("decompress", e, { sid: ctx.sessionManager.getSessionId(), blockId: params.blockId });
12399
12419
  throw e;
@@ -12506,8 +12526,10 @@ ${text}`;
12506
12526
  headPreview(text)
12507
12527
  ].join("\n");
12508
12528
  }
12509
- async function handleDecompress(args, runtime, ctx) {
12529
+ async function handleDecompress(args, runtime, ctx, signal) {
12530
+ assertNotAborted(signal);
12510
12531
  const { state, coreMessages } = await runtime.stateFor(ctx);
12532
+ assertNotAborted(signal);
12511
12533
  const arg = (args.blockId ?? "").trim();
12512
12534
  const owner = state.blocks.find((b2) => b2.effectiveMessageIds.includes(arg));
12513
12535
  if (owner) {
@@ -12536,6 +12558,7 @@ ${text}`;
12536
12558
  logError("decompress", { sid: ctx.sessionManager.getSessionId(), event: "block-path-rejected", blockId, toFile: args.toFile });
12537
12559
  return targetPath.error;
12538
12560
  }
12561
+ assertNotAborted(signal);
12539
12562
  await mkdir(AUTO_DIR, { recursive: true }).catch(() => {
12540
12563
  });
12541
12564
  await writeFile(targetPath, text, "utf8");
@@ -19727,8 +19750,148 @@ function degenerationNotice(runs2) {
19727
19750
  };
19728
19751
  }
19729
19752
 
19730
- // src/tool-guardrails.ts
19753
+ // node_modules/acp-kernel/dist/wire/index.js
19731
19754
  import { createHash as createHash2 } from "crypto";
19755
+ function hashId(s) {
19756
+ return createHash2("sha256").update(s, "utf8").digest("hex").slice(0, 16);
19757
+ }
19758
+ function createSubagentNamespaces() {
19759
+ const anchors = /* @__PURE__ */ new Map();
19760
+ return {
19761
+ namespaceFor(identityValue, instructions) {
19762
+ if (typeof instructions !== "string" || instructions.trim().length === 0) return identityValue;
19763
+ const fp = hashId(instructions);
19764
+ const anchor = anchors.get(identityValue);
19765
+ if (anchor === void 0) {
19766
+ anchors.set(identityValue, fp);
19767
+ return identityValue;
19768
+ }
19769
+ return anchor === fp ? identityValue : `${identityValue}|sub:${fp}`;
19770
+ }
19771
+ };
19772
+ }
19773
+ var defaultNamespaces = createSubagentNamespaces();
19774
+ var IMAGE_PLACEHOLDER = "[image]";
19775
+ function isObj(v2) {
19776
+ return typeof v2 === "object" && v2 !== null;
19777
+ }
19778
+ function isImagePart(protocol, part) {
19779
+ if (!isObj(part)) return false;
19780
+ if (protocol === "responses") return part.type === "input_image";
19781
+ if (protocol === "openai") return part.type === "image_url";
19782
+ if (protocol === "google")
19783
+ return isObj(part.inlineData) || isObj(part.fileData);
19784
+ return part.type === "image";
19785
+ }
19786
+ function placeholderContent(protocol) {
19787
+ if (protocol === "google") return [{ text: IMAGE_PLACEHOLDER }];
19788
+ const type = protocol === "responses" ? "input_text" : "text";
19789
+ return [{ type, text: IMAGE_PLACEHOLDER }];
19790
+ }
19791
+ function stripHistoricalImages(body, protocol, keepRecent) {
19792
+ if (!protocol || !isObj(body)) return { body, removed: 0 };
19793
+ const recentCount = Math.max(0, Math.floor(keepRecent));
19794
+ if (protocol === "responses") {
19795
+ const input = body.input;
19796
+ if (!Array.isArray(input)) return { body, removed: 0 };
19797
+ const cutoff2 = input.length - recentCount;
19798
+ let removed2 = 0;
19799
+ let touched2 = false;
19800
+ const nextInput = input.map((item, i) => {
19801
+ if (i < cutoff2 && isObj(item) && Array.isArray(item.content)) {
19802
+ const content = item.content;
19803
+ const imgs = content.filter((p) => isImagePart("responses", p)).length;
19804
+ if (imgs > 0) {
19805
+ removed2 += imgs;
19806
+ touched2 = true;
19807
+ const kept = content.filter((p) => !isImagePart("responses", p));
19808
+ return {
19809
+ ...item,
19810
+ content: kept.length > 0 ? kept : placeholderContent("responses")
19811
+ };
19812
+ }
19813
+ }
19814
+ return item;
19815
+ });
19816
+ if (!touched2) return { body, removed: 0 };
19817
+ return { body: { ...body, input: nextInput }, removed: removed2 };
19818
+ }
19819
+ if (protocol === "google") {
19820
+ const contents = body.contents;
19821
+ if (!Array.isArray(contents)) return { body, removed: 0 };
19822
+ const cutoff2 = contents.length - recentCount;
19823
+ let removed2 = 0;
19824
+ let touched2 = false;
19825
+ const nextContents = contents.map((c, i) => {
19826
+ if (i < cutoff2 && isObj(c) && Array.isArray(c.parts)) {
19827
+ const parts = c.parts;
19828
+ const imgs = parts.filter((p) => isImagePart("google", p)).length;
19829
+ if (imgs > 0) {
19830
+ removed2 += imgs;
19831
+ touched2 = true;
19832
+ const kept = parts.filter((p) => !isImagePart("google", p));
19833
+ return {
19834
+ ...c,
19835
+ parts: kept.length > 0 ? kept : placeholderContent("google")
19836
+ };
19837
+ }
19838
+ }
19839
+ return c;
19840
+ });
19841
+ if (!touched2) return { body, removed: 0 };
19842
+ return { body: { ...body, contents: nextContents }, removed: removed2 };
19843
+ }
19844
+ const messages = body.messages;
19845
+ if (!Array.isArray(messages)) return { body, removed: 0 };
19846
+ const cutoff = messages.length - recentCount;
19847
+ let removed = 0;
19848
+ let touched = false;
19849
+ const nextMessages = messages.map((m2, i) => {
19850
+ if (i < cutoff && isObj(m2) && Array.isArray(m2.content)) {
19851
+ const content = m2.content;
19852
+ const imgs = content.filter((p) => isImagePart(protocol, p)).length;
19853
+ if (imgs > 0) {
19854
+ removed += imgs;
19855
+ touched = true;
19856
+ const kept = content.filter((p) => !isImagePart(protocol, p));
19857
+ return {
19858
+ ...m2,
19859
+ content: kept.length > 0 ? kept : placeholderContent(protocol)
19860
+ };
19861
+ }
19862
+ }
19863
+ return m2;
19864
+ });
19865
+ if (!touched) return { body, removed: 0 };
19866
+ return { body: { ...body, messages: nextMessages }, removed };
19867
+ }
19868
+
19869
+ // src/strip-images.ts
19870
+ function apiToStripProtocol(api) {
19871
+ switch (api) {
19872
+ case "anthropic-messages":
19873
+ return "anthropic";
19874
+ case "openai-completions":
19875
+ return "openai";
19876
+ case "openai-responses":
19877
+ case "azure-openai-responses":
19878
+ case "openai-codex-responses":
19879
+ return "responses";
19880
+ default:
19881
+ return null;
19882
+ }
19883
+ }
19884
+ function applyStripImages(body, api, settings2) {
19885
+ if (!settings2.enabled) return { removed: 0 };
19886
+ const protocol = apiToStripProtocol(api);
19887
+ if (!protocol) return { removed: 0 };
19888
+ const result = stripHistoricalImages(body, protocol, settings2.keepRecent);
19889
+ if (result.removed === 0) return { removed: 0 };
19890
+ return { body: result.body, removed: result.removed };
19891
+ }
19892
+
19893
+ // src/tool-guardrails.ts
19894
+ import { createHash as createHash3 } from "crypto";
19732
19895
  function isBashToolResult(e) {
19733
19896
  return e.toolName === "bash";
19734
19897
  }
@@ -19829,7 +19992,7 @@ function canonicalStringify(value) {
19829
19992
  function repetitionFingerprint(toolName, input) {
19830
19993
  const payload = `${toolName}\0${canonicalStringify(input ?? {})}`;
19831
19994
  try {
19832
- return createHash2("sha1").update(payload).digest("hex");
19995
+ return createHash3("sha1").update(payload).digest("hex");
19833
19996
  } catch {
19834
19997
  return `unhashable:${toolName}`;
19835
19998
  }
@@ -19942,13 +20105,13 @@ import { join as join15, dirname as dirname10 } from "path";
19942
20105
  import { fileURLToPath as fileURLToPath3 } from "url";
19943
20106
  import { execFile } from "child_process";
19944
20107
  import { homedir as homedir9 } from "os";
19945
- import { createHash as createHash3 } from "crypto";
20108
+ import { createHash as createHash4 } from "crypto";
19946
20109
  var PACKAGE_NAME = "billion-context-pi";
19947
20110
  var registryUrl = (tag) => `https://registry.npmjs.org/${PACKAGE_NAME}/${encodeURIComponent(tag)}`;
19948
20111
  var SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/;
19949
20112
  var CHECK_INTERVAL_MS = 3 * 60 * 1e3;
19950
20113
  function locationKey(location) {
19951
- return createHash3("sha256").update(location).digest("hex").slice(0, 12);
20114
+ return createHash4("sha256").update(location).digest("hex").slice(0, 12);
19952
20115
  }
19953
20116
  var throttleFileFor = (extDir) => {
19954
20117
  if (process.env.ACP_UPDATE_THROTTLE_FILE) return process.env.ACP_UPDATE_THROTTLE_FILE;
@@ -20364,6 +20527,7 @@ function createAcpExtension(adapter = {}) {
20364
20527
  wireDelegateReadTracking(pi);
20365
20528
  wireSessionLifecycle(pi, runtime, standDownIfProxied);
20366
20529
  wireContextTransform(pi, runtime, standDownIfProxied);
20530
+ wireBeforeProviderRequest(pi, runtime, standDownIfProxied);
20367
20531
  wireSystemPrompt(pi, runtime);
20368
20532
  wireToolGuardrails(pi, runtime);
20369
20533
  wireOverflowSelfHeal(pi, runtime);
@@ -20533,6 +20697,25 @@ function wireSessionLifecycle(pi, runtime, standDownIfProxied) {
20533
20697
  });
20534
20698
  }
20535
20699
  var lastDegNoticeKey = null;
20700
+ function wireBeforeProviderRequest(pi, runtime, standDownIfProxied) {
20701
+ pi.on("before_provider_request", async (event, ctx) => {
20702
+ if (runtime.refused) return;
20703
+ if (standDownIfProxied(ctx)) return;
20704
+ const settings2 = runtime.stripImagesFor(ctx);
20705
+ if (!settings2.enabled) return;
20706
+ const outcome = applyStripImages(event.payload, ctx.model?.api, settings2);
20707
+ if (outcome.removed > 0) {
20708
+ logInfo("strip-images", {
20709
+ sid: ctx.sessionManager.getSessionId(),
20710
+ event: "stripped",
20711
+ removed: outcome.removed,
20712
+ keepRecent: settings2.keepRecent
20713
+ });
20714
+ return outcome.body;
20715
+ }
20716
+ return;
20717
+ });
20718
+ }
20536
20719
  function wireContextTransform(pi, runtime, standDownIfProxied) {
20537
20720
  pi.on("context", async (event, ctx) => {
20538
20721
  if (runtime.refused) return;