billion-context 0.1.117 → 0.1.118

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
@@ -320,6 +320,18 @@ Then send one message from your client and watch the log
320
320
  should see a `processTurn` line per request, and once the conversation grows,
321
321
  `[acp-usage] round N input=X cached=Y (cache hit Z%)` + a `compress` event.
322
322
 
323
+ ### Client uses `http.proxy` (CONNECT) but nothing compresses
324
+
325
+ Some clients (VS Code-based IDEs: CodeBuddy, Cursor, Windsurf, …) only offer an HTTP **proxy** setting (`http.proxy`, `codingcopilot.httpProxyURL`, …) — no model base-URL to rewrite. Such clients send `CONNECT <model-host>:443` through the proxy instead of plain `/bili/…` requests. That path is only decrypted when the model host is on bili's **MITM whitelist**; otherwise bili blind-tunnels the TLS bytes (opaque relay) and can never see — or compress — the model requests (#897).
326
+
327
+ This failure mode is now loud instead of silent:
328
+
329
+ - a one-time `BLIND TUNNEL WARNING` per target host in the log, with the fix steps;
330
+ - `blindTunnels` (count + exact target hosts) in `curl -s http://localhost:8787/__bili/health` and `/__bili/stats` (loopback-only);
331
+ - an `UNDECRYPTED TRAFFIC (instance-level)` section in `acp_status` output while such tunnels exist.
332
+
333
+ To actually compress such a client: add its model domain to `"mitm".domains` in `billion-context.json` (e.g. `"mitm": { "domains": ["copilot.tencent.com"] }`) or via `BILI_MITM_DOMAINS`, restart bili, and make the client trust bili's root CA (`NODE_EXTRA_CA_CERTS=~/.local/share/billion-context/ca/root-ca.pem` for Node-based clients, or the client's own CA-path setting). The `/bili/` prefix trick does not apply here — there is no URL to change. Details: [CONFIGURATION.md → MITM](CONFIGURATION.md#mitm-transparent-proxy-login-clients).
334
+
323
335
  ## Running the proxy
324
336
 
325
337
  ### Flags
package/README.zh-CN.md CHANGED
@@ -159,6 +159,18 @@ curl -s http://localhost:8787/__bili/stats
159
159
  同时也打到 stderr)。每个请求应该看到一行 `processTurn`,等对话变长后
160
160
  会出现 `[acp-usage] round N input=X cached=Y (cache hit Z%)` + `compress` 事件。
161
161
 
162
+ ### 客户端用 `http.proxy`(CONNECT)接入但从不压缩
163
+
164
+ 部分客户端(VS Code 系 IDE:CodeBuddy、Cursor、Windsurf……)只提供一个 HTTP **代理**设置(`http.proxy`、`codingcopilot.httpProxyURL` 等),没有可改写的模型 base-URL。这类客户端不走普通的 `/bili/…` 请求,而是把 `CONNECT <模型域名>:443` 发给代理。只有当模型域名在 bili 的 **MITM 白名单**里时这条路径才会被解密;否则 bili 只做盲隧道(不透明转发),永远看不到——也就无法压缩——模型请求(#897)。
165
+
166
+ 该失效模式现在不再静默:
167
+
168
+ - 日志里对每个目标域名打一次 `BLIND TUNNEL WARNING`,附修复步骤;
169
+ - `curl -s http://localhost:8787/__bili/health` 与 `/__bili/stats` 输出 `blindTunnels`(计数 + 精确目标域名,仅 loopback);
170
+ - 存在此类隧道时,`acp_status` 输出会多一节 `UNDECRYPTED TRAFFIC (instance-level)`。
171
+
172
+ 要真正压缩这类客户端:把它的模型域名加进 `billion-context.json` 的 `"mitm".domains`(如 `"mitm": { "domains": ["copilot.tencent.com"] }`)或环境变量 `BILI_MITM_DOMAINS`,重启 bili,并让客户端信任 bili 的根 CA(Node 系客户端用 `NODE_EXTRA_CA_CERTS=~/.local/share/billion-context/ca/root-ca.pem`,有 CA 路径设置的用其设置)。`/bili/` 前缀方案在这里不适用——没有 URL 可改。详见 [CONFIGURATION.zh-CN.md → MITM](CONFIGURATION.zh-CN.md#mitm-透明代理登录客户端)。
173
+
162
174
  ## 运行代理
163
175
 
164
176
  ### 命令行参数
@@ -25599,7 +25599,7 @@ ${captureLines}` : capture.stack;
25599
25599
  }
25600
25600
  });
25601
25601
 
25602
- // node_modules/acp-kernel/dist/chunk-QPVKTDYF.js
25602
+ // node_modules/acp-kernel/dist/chunk-VPRKYTHW.js
25603
25603
  import { createRequire } from "module";
25604
25604
  var require2 = createRequire(import.meta.url);
25605
25605
  function thinkingTokenValue(thinking) {
@@ -25726,6 +25726,7 @@ var COMPRESS_TOOL_NAME = "compress";
25726
25726
  var DECOMPRESS_TOOL_NAME = "decompress";
25727
25727
  var SEARCH_CONTEXT_TOOL_NAME = "search_context";
25728
25728
  var ACP_STATUS_TOOL_NAME = "acp_status";
25729
+ var ACP_CACHE_TOOL_NAME = "acp_cache";
25729
25730
  var ABSORB_TOOL_NAME = "absorb";
25730
25731
  var ACP_TEXT_OPEN = "<acp_compress>";
25731
25732
  var ACP_TEXT_CLOSE = "</acp_compress>";
@@ -25829,10 +25830,15 @@ var COMPRESS_TOOL_OPENAI = {
25829
25830
  }
25830
25831
  };
25831
25832
  var TEXT_PROMPT_SECTIONS = [
25832
- ["acpTags", `ACP TAGS
25833
+ [
25834
+ "acpTags",
25835
+ `ACP TAGS
25833
25836
 
25834
- Each message in the conversation is annotated with a <acp tokens="2.1K" type="tool:bash">m00175</acp> tag showing its reference ID, approximate token size, and content type. These tags are system metadata. NEVER echo these history tags. Use only the ref ID (e.g. m00005), never the XML wrapper.`],
25835
- ["textProtocol", `COMPRESSION PROTOCOL (TEXT)
25837
+ Each message in the conversation is annotated with a <acp tokens="2.1K" type="tool:bash">m00175</acp> tag showing its reference ID, approximate token size, and content type. These tags are system metadata. NEVER echo these history tags. Use only the ref ID (e.g. m00005), never the XML wrapper.`
25838
+ ],
25839
+ [
25840
+ "textProtocol",
25841
+ `COMPRESSION PROTOCOL (TEXT)
25836
25842
 
25837
25843
  You manage context by emitting a special trigger in your text output. When you decide a range of conversation is genuinely consumed and should be compressed into a summary, output EXACTLY this marker (the proxy intercepts and executes it; the marker is stripped from what the user sees):
25838
25844
 
@@ -25843,8 +25849,11 @@ Rules for the trigger:
25843
25849
  - JSON shape matches the compress tool: {"content":[{startId,endId,summary,topic?}]}. Batch multiple ranges in one trigger.
25844
25850
  - After emitting the marker, STOP your turn. Do not continue with other text \u2014 the proxy will execute the compression and return the result, then you continue fresh.
25845
25851
  - Do NOT wrap the marker in code fences, quotes, or commentary.
25846
- - NEVER compress on short conversations or when context is small (well below the window limit). Only compress when context is genuinely large.`],
25847
- ["textTools", `ACP TOOLS (TEXT TRIGGERS)
25852
+ - NEVER compress on short conversations or when context is small (well below the window limit). Only compress when context is genuinely large.`
25853
+ ],
25854
+ [
25855
+ "textTools",
25856
+ `ACP TOOLS (TEXT TRIGGERS)
25848
25857
 
25849
25858
  Since host tools cannot coexist with a declared tools field, ALL ACP tools use text triggers. Emit the marker; the proxy intercepts and executes it; the marker is stripped from what the user sees.
25850
25859
 
@@ -25864,13 +25873,19 @@ Since host tools cannot coexist with a declared tools field, ALL ACP tools use t
25864
25873
  Rules for ALL triggers:
25865
25874
  - Output on its own, NO surrounding prose. Just the raw marker.
25866
25875
  - After emitting, STOP your turn. The proxy executes and returns the result.
25867
- - Do NOT wrap in code fences, quotes, or commentary.`]
25876
+ - Do NOT wrap in code fences, quotes, or commentary.`
25877
+ ]
25868
25878
  ];
25869
25879
  var HYBRID_PROMPT_SECTIONS = [
25870
- ["acpTags", `ACP TAGS
25880
+ [
25881
+ "acpTags",
25882
+ `ACP TAGS
25871
25883
 
25872
- Each message in the conversation is annotated with a <acp> tag showing its reference ID, approximate token size, and content type. These tags are system metadata. NEVER echo these history tags. Use only the ref ID (e.g. m00005), never the XML wrapper.`],
25873
- ["textProtocol", `COMPRESSION PROTOCOL (TEXT)
25884
+ Each message in the conversation is annotated with a <acp> tag showing its reference ID, approximate token size, and content type. These tags are system metadata. NEVER echo these history tags. Use only the ref ID (e.g. m00005), never the XML wrapper.`
25885
+ ],
25886
+ [
25887
+ "textProtocol",
25888
+ `COMPRESSION PROTOCOL (TEXT)
25874
25889
 
25875
25890
  You manage context by emitting a special trigger in your text output. When you decide a range of conversation is genuinely consumed and should be compressed into a summary, output EXACTLY this marker (the proxy intercepts and executes it; the marker is stripped from what the user sees):
25876
25891
 
@@ -25881,8 +25896,11 @@ Rules for the trigger:
25881
25896
  - JSON shape: {"content":[{startId,endId,summary,topic?}]}. Batch multiple ranges in one trigger.
25882
25897
  - After emitting the marker, STOP your turn. Do not continue with other text \u2014 the proxy will execute the compression and return the result, then you continue fresh.
25883
25898
  - Do NOT wrap the marker in code fences, quotes, or commentary.
25884
- - NEVER compress on short conversations or when context is small (well below the window limit). Only compress when context is genuinely large.`],
25885
- ["functionTools", `ACP TOOLS (FUNCTION CALLS)
25899
+ - NEVER compress on short conversations or when context is small (well below the window limit). Only compress when context is genuinely large.`
25900
+ ],
25901
+ [
25902
+ "functionTools",
25903
+ `ACP TOOLS (FUNCTION CALLS)
25886
25904
 
25887
25905
  The proxy also provides these as real function tools you can call directly (they appear in your tool list). Call them like any other function; the proxy executes them and returns the result, then you continue.
25888
25906
 
@@ -25890,7 +25908,8 @@ The proxy also provides these as real function tools you can call directly (they
25890
25908
  - search_context \u2014 search compressed block summaries by keyword. Arguments: {"query":"...","limit":5}.
25891
25909
  - decompress \u2014 restore compressed content for exact details. Arguments: {"blockId":"b5"} (optional "toFile":"/tmp/x.txt", "full":true).
25892
25910
 
25893
- Note: compress is ONLY available via the text marker above (it needs batch ranges + an immediate stop), NOT as a function tool.`]
25911
+ Note: compress is ONLY available via the text marker above (it needs batch ranges + an immediate stop), NOT as a function tool.`
25912
+ ]
25894
25913
  ];
25895
25914
  var DECOMPRESS_TOOL_OPENAI = {
25896
25915
  type: "function",
@@ -25943,11 +25962,24 @@ var ACP_STATUS_TOOL_OPENAI = {
25943
25962
  }
25944
25963
  }
25945
25964
  };
25965
+ var ACP_CACHE_TOOL_DESCRIPTION = "Prompt-cache reconciliation: grand ledger (total input/cached/output, overall hit rate) with every request's miss split into new content / compression re-pay / TTL expiry, plus per-fold economics (breakeven turns vs measured cadence). Read-only. Call when asked about cache hits, cache invalidation, or what compression costs.";
25966
+ var ACP_CACHE_TOOL_OPENAI = {
25967
+ type: "function",
25968
+ function: {
25969
+ name: ACP_CACHE_TOOL_NAME,
25970
+ description: ACP_CACHE_TOOL_DESCRIPTION,
25971
+ parameters: {
25972
+ type: "object",
25973
+ properties: {}
25974
+ }
25975
+ }
25976
+ };
25946
25977
  var ACP_TOOLS_OPENAI = [
25947
25978
  COMPRESS_TOOL_OPENAI,
25948
25979
  DECOMPRESS_TOOL_OPENAI,
25949
25980
  SEARCH_CONTEXT_TOOL_OPENAI,
25950
- ACP_STATUS_TOOL_OPENAI
25981
+ ACP_STATUS_TOOL_OPENAI,
25982
+ ACP_CACHE_TOOL_OPENAI
25951
25983
  ];
25952
25984
  var DECOMPRESS_TOOL = {
25953
25985
  name: DECOMPRESS_TOOL_NAME,
@@ -25964,6 +25996,18 @@ var ACP_STATUS_TOOL = {
25964
25996
  description: ACP_STATUS_TOOL_OPENAI.function.description,
25965
25997
  input_schema: ACP_STATUS_TOOL_OPENAI.function.parameters
25966
25998
  };
25999
+ var ACP_CACHE_TOOL = {
26000
+ name: ACP_CACHE_TOOL_NAME,
26001
+ description: ACP_CACHE_TOOL_DESCRIPTION,
26002
+ input_schema: ACP_CACHE_TOOL_OPENAI.function.parameters
26003
+ };
26004
+ var ACP_TOOLS_ANTHROPIC = [
26005
+ COMPRESS_TOOL,
26006
+ DECOMPRESS_TOOL,
26007
+ SEARCH_CONTEXT_TOOL,
26008
+ ACP_STATUS_TOOL,
26009
+ ACP_CACHE_TOOL
26010
+ ];
25967
26011
  var COMPRESS_TOOL_RESPONSES = {
25968
26012
  type: "function",
25969
26013
  name: COMPRESS_TOOL_NAME,
@@ -25988,6 +26032,32 @@ var ACP_STATUS_TOOL_RESPONSES = {
25988
26032
  description: ACP_STATUS_TOOL_OPENAI.function.description,
25989
26033
  parameters: ACP_STATUS_TOOL_OPENAI.function.parameters
25990
26034
  };
26035
+ var ACP_CACHE_TOOL_RESPONSES = {
26036
+ type: "function",
26037
+ name: ACP_CACHE_TOOL_NAME,
26038
+ description: ACP_CACHE_TOOL_DESCRIPTION,
26039
+ parameters: ACP_CACHE_TOOL_OPENAI.function.parameters
26040
+ };
26041
+ var ACP_TOOLS_RESPONSES = [
26042
+ COMPRESS_TOOL_RESPONSES,
26043
+ DECOMPRESS_TOOL_RESPONSES,
26044
+ SEARCH_CONTEXT_TOOL_RESPONSES,
26045
+ ACP_STATUS_TOOL_RESPONSES,
26046
+ ACP_CACHE_TOOL_RESPONSES
26047
+ ];
26048
+ var ACP_READONLY_TOOLS_RESPONSES = [
26049
+ DECOMPRESS_TOOL_RESPONSES,
26050
+ SEARCH_CONTEXT_TOOL_RESPONSES,
26051
+ ACP_STATUS_TOOL_RESPONSES,
26052
+ ACP_CACHE_TOOL_RESPONSES
26053
+ ];
26054
+ var ACP_TOOL_NAMES = /* @__PURE__ */ new Set([
26055
+ COMPRESS_TOOL_NAME,
26056
+ DECOMPRESS_TOOL_NAME,
26057
+ SEARCH_CONTEXT_TOOL_NAME,
26058
+ ACP_STATUS_TOOL_NAME,
26059
+ ACP_CACHE_TOOL_NAME
26060
+ ]);
25991
26061
  var ABSORB_TOOL_DESCRIPTION = "Distill a tool result into a compact summary you write. REQUIRED immediately after a tool result ends with an [ACP absorb] instruction: pass its ref and the distilled essentials (outcome, key values, paths:lines, errors, decisions). The original output is then removed from context; your summary is the durable record.";
25992
26062
  var ABSORB_PARAMETERS = {
25993
26063
  type: "object",
@@ -26011,6 +26081,62 @@ var ABSORB_TOOL_OPENAI = {
26011
26081
  parameters: ABSORB_PARAMETERS
26012
26082
  }
26013
26083
  };
26084
+ var COMPRESS_TOOL_GOOGLE = {
26085
+ name: COMPRESS_TOOL_NAME,
26086
+ description: COMPRESS_TOOL.description,
26087
+ // `anyOf` is rejected by older API revisions, so `content` declares the
26088
+ // object form; a JSON-encoded string of that array is still accepted by
26089
+ // parseCompressInput, and the line form (a summary whose first line is
26090
+ // 'm00150–m00220 optional topic') is documented in the description.
26091
+ parameters: {
26092
+ type: "object",
26093
+ properties: {
26094
+ topic: {
26095
+ type: "string",
26096
+ description: "Optional short title for the compressed range"
26097
+ },
26098
+ content: {
26099
+ type: "array",
26100
+ description: "One or more ranges to compress into separate summary blocks. Object form: {startId,endId,summary,topic?}. A JSON-encoded string of that array is also accepted; in the line form the summary begins with its own first line 'm00150\u2013m00220 optional topic', the rest being the summary markdown verbatim. REQUIRED \u2014 compress without content is invalid.",
26101
+ items: {
26102
+ type: "object",
26103
+ properties: {
26104
+ topic: { type: "string" },
26105
+ startId: {
26106
+ type: "string",
26107
+ description: "mNNNNN ref at the start of the range"
26108
+ },
26109
+ endId: {
26110
+ type: "string",
26111
+ description: "mNNNNN ref at the end of the range"
26112
+ },
26113
+ summary: {
26114
+ type: "string",
26115
+ description: "Self-contained summary replacing the range"
26116
+ }
26117
+ },
26118
+ required: ["startId", "endId", "summary"]
26119
+ }
26120
+ }
26121
+ },
26122
+ required: ["content"]
26123
+ }
26124
+ };
26125
+ var DECOMPRESS_TOOL_GOOGLE = {
26126
+ name: DECOMPRESS_TOOL_NAME,
26127
+ description: DECOMPRESS_TOOL_OPENAI.function.description,
26128
+ parameters: DECOMPRESS_TOOL_OPENAI.function.parameters
26129
+ };
26130
+ var SEARCH_CONTEXT_TOOL_GOOGLE = {
26131
+ name: SEARCH_CONTEXT_TOOL_NAME,
26132
+ description: SEARCH_CONTEXT_TOOL_OPENAI.function.description,
26133
+ parameters: SEARCH_CONTEXT_TOOL_OPENAI.function.parameters
26134
+ };
26135
+ var ACP_STATUS_TOOL_GOOGLE = {
26136
+ name: ACP_STATUS_TOOL_NAME,
26137
+ description: ACP_STATUS_TOOL_OPENAI.function.description,
26138
+ parameters: ACP_STATUS_TOOL_OPENAI.function.parameters
26139
+ };
26014
26140
  function formatTokens(tokens) {
26015
26141
  if (tokens < 1e3) return String(tokens);
26016
26142
  if (tokens < 1e4) return (tokens / 1e3).toFixed(1) + "K";
@@ -26368,6 +26494,38 @@ var MAX_REQUEST_BYTES = 100 * 1024 * 1024;
26368
26494
  var UPSTREAM_TIMEOUT_MS = 12 * 60 * 1e3;
26369
26495
 
26370
26496
  // src/compress-tool.ts
26497
+ var UNADOPTED_KERNEL_TOOLS = /* @__PURE__ */ new Set([ACP_CACHE_TOOL_NAME]);
26498
+ var PROXY_TOOL_NAMES = new Set([...ACP_TOOL_NAMES].filter((n) => !UNADOPTED_KERNEL_TOOLS.has(n)));
26499
+ var SEARCH_CONTEXT_CONVERSATION_ID_PARAM = {
26500
+ type: "string",
26501
+ description: "Target bili conversation id. Defaults to the current conversation. May reference another historical pfa-* session for read-only search."
26502
+ };
26503
+ function withConversationId(schema) {
26504
+ return { ...schema, properties: { ...schema.properties, conversation_id: SEARCH_CONTEXT_CONVERSATION_ID_PARAM } };
26505
+ }
26506
+ var BILI_SEARCH_CONTEXT_TOOL = {
26507
+ name: SEARCH_CONTEXT_TOOL.name,
26508
+ description: SEARCH_CONTEXT_TOOL.description,
26509
+ input_schema: withConversationId(SEARCH_CONTEXT_TOOL.input_schema)
26510
+ };
26511
+ var BILI_SEARCH_CONTEXT_TOOL_OPENAI = {
26512
+ type: "function",
26513
+ function: {
26514
+ name: SEARCH_CONTEXT_TOOL_OPENAI.function.name,
26515
+ description: SEARCH_CONTEXT_TOOL_OPENAI.function.description,
26516
+ parameters: withConversationId(SEARCH_CONTEXT_TOOL_OPENAI.function.parameters)
26517
+ }
26518
+ };
26519
+ var BILI_SEARCH_CONTEXT_TOOL_RESPONSES = {
26520
+ type: "function",
26521
+ name: SEARCH_CONTEXT_TOOL_RESPONSES.name,
26522
+ description: SEARCH_CONTEXT_TOOL_RESPONSES.description,
26523
+ parameters: withConversationId(SEARCH_CONTEXT_TOOL_RESPONSES.parameters)
26524
+ };
26525
+ var BILI_ACP_TOOLS_ANTHROPIC = ACP_TOOLS_ANTHROPIC.filter((t) => !UNADOPTED_KERNEL_TOOLS.has(t.name)).map((t) => t.name === SEARCH_CONTEXT_TOOL_NAME ? BILI_SEARCH_CONTEXT_TOOL : t);
26526
+ var BILI_ACP_TOOLS_OPENAI = ACP_TOOLS_OPENAI.filter((t) => !UNADOPTED_KERNEL_TOOLS.has(t.function.name)).map((t) => t.function.name === SEARCH_CONTEXT_TOOL_NAME ? BILI_SEARCH_CONTEXT_TOOL_OPENAI : t);
26527
+ var BILI_ACP_TOOLS_RESPONSES = ACP_TOOLS_RESPONSES.filter((t) => !UNADOPTED_KERNEL_TOOLS.has(t.name)).map((t) => t.name === SEARCH_CONTEXT_TOOL_NAME ? BILI_SEARCH_CONTEXT_TOOL_RESPONSES : t);
26528
+ var BILI_ACP_READONLY_TOOLS_RESPONSES = ACP_READONLY_TOOLS_RESPONSES.filter((t) => !UNADOPTED_KERNEL_TOOLS.has(t.name)).map((t) => t.name === SEARCH_CONTEXT_TOOL_NAME ? BILI_SEARCH_CONTEXT_TOOL_RESPONSES : t);
26371
26529
  var ABSORB_TOOL_RESPONSES = {
26372
26530
  type: "function",
26373
26531
  name: ABSORB_TOOL_OPENAI.function.name,