opencode-acp 1.18.2 → 1.18.3-pr.475.210

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
@@ -10,6 +10,21 @@ The model decides <em>when</em> and <em>what</em> to compress — not a hard lim
10
10
 
11
11
  ---
12
12
 
13
+ ## Community
14
+
15
+ QQ Group:
16
+ 1056132097 (full)
17
+ 1108730198 (open)
18
+
19
+ ---
20
+
21
+ > ⚠️ **Using OpenCode 2.x?** This extension uses the OpenCode **V1** plugin
22
+ > API and does **not** load on OpenCode 2.x. For OpenCode 2.x the recommended
23
+ > context manager is [billion-context](https://github.com/ranxianglei/billion-context):
24
+ > `bili opencode` (launcher) or `bili plugin install opencode` (native plugin,
25
+ > no launcher). This extension remains fully supported on OpenCode **1.x**.
26
+
27
+ ---
13
28
 
14
29
  ## 📄 Paper / Preprint
15
30
 
@@ -33,14 +48,6 @@ A production-scale longitudinal study: 4.5 months, three hosts, 174,327 model ca
33
48
 
34
49
  ---
35
50
 
36
- ## Community
37
-
38
- Discussion, help, and updates on QQ — one group covers all three projects (`billion-context`, `billion-context-pi`, `opencode-acp`):
39
-
40
- **QQ Group: 1056132097**
41
-
42
- ---
43
-
44
51
  ## Why ACP
45
52
 
46
53
  ACP hands all context-management authority to the model itself — not relying on
@@ -67,7 +74,8 @@ Pick by your client:
67
74
  | Client | Use |
68
75
  |---|---|
69
76
  | **pi** | [`billion-context-pi`](https://github.com/ranxianglei/billion-context-pi) (in-process extension) |
70
- | **opencode** | [`opencode-acp`](https://github.com/ranxianglei/opencode-acp) (in-process extension) |
77
+ | **opencode 1.x** | [`opencode-acp`](https://github.com/ranxianglei/opencode-acp) (in-process extension) |
78
+ | **opencode 2.0+** | [`billion-context`](https://github.com/ranxianglei/billion-context) — `bili opencode` (built-in V2 plugin) or `bili plugin install opencode` (self-spawning native plugin, no launcher); `opencode-acp` does not load on 2.x |
71
79
  | **omp** | [`billion-context`](https://github.com/ranxianglei/billion-context) via `bili omp` (built-in plugin) |
72
80
  | **everything else** (no context hook) | [`billion-context`](https://github.com/ranxianglei/billion-context) — `bili <client>` (launcher, preferred) or `/bili/` prefix |
73
81
 
@@ -101,6 +109,11 @@ across all other sessions.
101
109
 
102
110
  ## Installation
103
111
 
112
+ > ⚠️ This installs the **V1** plugin — it targets OpenCode **1.x**. On
113
+ > OpenCode **2.x** the plugin will not load; use
114
+ > [billion-context](https://github.com/ranxianglei/billion-context) instead
115
+ > (`bili opencode` or `bili plugin install opencode`).
116
+
104
117
  ```bash
105
118
  opencode plugin opencode-acp@stable --global
106
119
  ```
package/README.zh-CN.md CHANGED
@@ -10,6 +10,17 @@
10
10
 
11
11
  ---
12
12
 
13
+ ## 社区
14
+
15
+ QQ群:
16
+ 1056132097(已满)
17
+ 1108730198(未满)
18
+
19
+ ---
20
+
21
+ > ⚠️ **你用的是 OpenCode 2.x?** 本扩展使用 OpenCode **V1** 插件 API,在 OpenCode 2.x 上**不会加载**。OpenCode 2.x 推荐的上下文管理方案是 [billion-context](https://github.com/ranxianglei/billion-context):`bili opencode`(启动器)或 `bili plugin install opencode`(原生插件,免启动器)。本扩展在 OpenCode **1.x** 上继续完整可用。
22
+
23
+ ---
13
24
 
14
25
  ## 📄 论文 / 预印本
15
26
 
@@ -33,14 +44,6 @@
33
44
 
34
45
  ---
35
46
 
36
- ## 社区
37
-
38
- 交流、求助与更新都在 QQ——同一个群覆盖三个项目(`billion-context`、`billion-context-pi`、`opencode-acp`):
39
-
40
- **QQ 群:1056132097**
41
-
42
- ---
43
-
44
47
  ## 为什么选择 ACP
45
48
 
46
49
  ACP 将上下文管理的所有权限全部交给模型自己,而不依靠外部模型或各种复杂的机制去做上下文管理。它是迄今为止,市面上对上下文管理最好的实现。
@@ -59,7 +62,8 @@ ACP 将上下文管理的所有权限全部交给模型自己,而不依靠外
59
62
  | 客户端 | 用这个 |
60
63
  |---|---|
61
64
  | **pi** | [`billion-context-pi`](https://github.com/ranxianglei/billion-context-pi)(进程内扩展) |
62
- | **opencode** | [`opencode-acp`](https://github.com/ranxianglei/opencode-acp)(进程内扩展) |
65
+ | **opencode 1.x** | [`opencode-acp`](https://github.com/ranxianglei/opencode-acp)(进程内扩展) |
66
+ | **opencode 2.0+** | [`billion-context`](https://github.com/ranxianglei/billion-context) —— `bili opencode`(内置 V2 插件)或 `bili plugin install opencode`(自拉起原生插件,免启动器);`opencode-acp` 在 2.x 上不加载 |
63
67
  | **omp** | [`billion-context`](https://github.com/ranxianglei/billion-context),`bili omp`(内置插件) |
64
68
  | **其余所有**(没有上下文 hook) | [`billion-context`](https://github.com/ranxianglei/billion-context) —— `bili <client>`(启动器,优先)或 `/bili/` 前缀 |
65
69
 
@@ -88,6 +92,8 @@ ACP 将上下文管理的所有权限全部交给模型自己,而不依靠外
88
92
 
89
93
  ## 安装
90
94
 
95
+ > ⚠️ 这里安装的是 **V1** 插件 —— 面向 OpenCode **1.x**。在 OpenCode **2.x** 上该插件不会加载;请改用 [billion-context](https://github.com/ranxianglei/billion-context)(`bili opencode` 或 `bili plugin install opencode`)。
96
+
91
97
  ```bash
92
98
  opencode plugin opencode-acp@stable --global
93
99
  ```
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AA4BjD,QAAA,MAAM,MAAM,EAAE,MAwMK,CAAA;AAEnB,eAAe,MAAM,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,qBAAqB,CAAA;AA6BzD,QAAA,MAAM,MAAM,EAAE,MA4OK,CAAA;AAEnB,eAAe,MAAM,CAAA"}
package/dist/index.js CHANGED
@@ -2148,8 +2148,57 @@ Using previous/default values`
2148
2148
  // lib/compress/range.ts
2149
2149
  import { tool as tool2 } from "@opencode-ai/plugin";
2150
2150
 
2151
+ // lib/bili-proxy.ts
2152
+ var BILI_PROXY_MARKER = "/bili/";
2153
+ function extractBaseURL(entry) {
2154
+ if (!entry || typeof entry !== "object") return void 0;
2155
+ const record = entry;
2156
+ const options = record.options;
2157
+ if (options && typeof options === "object") {
2158
+ const optionBaseURL = options.baseURL;
2159
+ if (typeof optionBaseURL === "string" && optionBaseURL.length > 0) {
2160
+ return optionBaseURL;
2161
+ }
2162
+ }
2163
+ const topLevelBaseURL = record.baseURL;
2164
+ if (typeof topLevelBaseURL === "string" && topLevelBaseURL.length > 0) {
2165
+ return topLevelBaseURL;
2166
+ }
2167
+ return void 0;
2168
+ }
2169
+ function findBiliProxyProviders(provider) {
2170
+ if (!provider || typeof provider !== "object") return [];
2171
+ const matches = [];
2172
+ for (const [name, entry] of Object.entries(provider)) {
2173
+ const baseURL = extractBaseURL(entry);
2174
+ if (baseURL !== void 0 && baseURL.includes(BILI_PROXY_MARKER)) {
2175
+ matches.push({ provider: name, baseURL });
2176
+ }
2177
+ }
2178
+ return matches;
2179
+ }
2180
+ var BILI_PROXY_ENV_VAR = "BILLION_CONTEXT_PROXY";
2181
+ var BILI_NATIVE_ENV_VAR = "BILLION_CONTEXT_NATIVE";
2182
+ function detectBiliEnvYield(env = process.env) {
2183
+ if (env[BILI_PROXY_ENV_VAR]) return "launcher";
2184
+ if (env[BILI_NATIVE_ENV_VAR]) return "native";
2185
+ return null;
2186
+ }
2187
+ function describeBiliEnvYield(source) {
2188
+ return source === "native" ? "BILLION_CONTEXT_NATIVE set (billion-context native mode)" : "BILLION_CONTEXT_PROXY set (billion-context launcher mode)";
2189
+ }
2190
+ function biliYieldLogMessage(source) {
2191
+ return source === "native" ? "[opencode-acp] disabled: BILLION_CONTEXT_NATIVE detected \u2014 billion-context native mode handles compression" : "[opencode-acp] disabled: BILLION_CONTEXT_PROXY detected \u2014 proxy handles compression";
2192
+ }
2193
+
2151
2194
  // lib/compress/types.ts
2152
2195
  function resolveToolContext(factoryCtx, sessionID) {
2196
+ const biliYield = detectBiliEnvYield();
2197
+ if (biliYield !== null) {
2198
+ throw new Error(
2199
+ `ACP is disabled in this process \u2014 ${describeBiliEnvYield(biliYield)}. billion-context owns context compression; do not call ACP tools again.`
2200
+ );
2201
+ }
2153
2202
  const state = factoryCtx.registry.get(sessionID);
2154
2203
  if (!state) {
2155
2204
  throw new Error(
@@ -6384,24 +6433,32 @@ function computeStats(plan) {
6384
6433
  const retentionPct = originalTokens > 0 ? (summaryChars / (originalTokens * 4) * 100).toFixed(2) : "?";
6385
6434
  return { originalTokens, summaryChars, ratio, retentionPct };
6386
6435
  }
6387
- function buildQualityRejectionError(plan, result) {
6436
+ function coreReason(reason) {
6437
+ if (!reason) return "unknown";
6438
+ const candidates = [reason.indexOf(":"), reason.indexOf("(")].filter((i) => i >= 0);
6439
+ const cut = Math.min(...candidates);
6440
+ return (cut > 0 ? reason.slice(0, cut) : reason).trim() || "unknown";
6441
+ }
6442
+ function buildQualityRejectionError(plan, result, logger) {
6388
6443
  const stats = computeStats(plan);
6389
- const metrics = [
6390
- `Reason: ${result.reason || "unknown"}`,
6391
- `Original: ~${stats.originalTokens} tokens`,
6392
- `Summary: ${stats.summaryChars} chars`,
6393
- `Ratio: ${stats.ratio}:1`,
6394
- `Retention: ${stats.retentionPct}%`,
6395
- `Gate layer: ${result.layer ?? "unknown"}`,
6396
- `rougeF1: ${formatMetric(result, "rougeF1")}`,
6397
- `top20Recall: ${formatMetric(result, "top20Recall")}`
6398
- ];
6444
+ logger?.warn("quality gate rejected compression", {
6445
+ range: `${plan.startId}\u2013${plan.endId}`,
6446
+ reason: result.reason || "unknown",
6447
+ layer: result.layer ?? "unknown",
6448
+ originalTokens: stats.originalTokens,
6449
+ summaryChars: stats.summaryChars,
6450
+ ratio: stats.ratio,
6451
+ retentionPct: stats.retentionPct,
6452
+ rougeF1: formatMetric(result, "rougeF1"),
6453
+ top20Recall: formatMetric(result, "top20Recall")
6454
+ });
6399
6455
  const message = `\u26A0\uFE0F COMPRESSION REJECTED \u2014 QUALITY GATE FAILURE
6400
6456
 
6401
6457
  Range: ${plan.startId}\u2013${plan.endId}
6402
- ${metrics.join("\n")}
6458
+ Reason: ${coreReason(result.reason)}
6459
+ Original: ~${stats.originalTokens} tokens \xB7 Summary: ${stats.summaryChars} chars \xB7 Retention: ${stats.retentionPct}%
6403
6460
 
6404
- Retry: rewrite a more complete summary that preserves critical details (file paths, decisions, exact values, errors) and call compress again on the same range \u2014 the gate re-evaluates automatically. Full compression rules are already in your system prompt. If you are confident the summary is correct despite the metrics, add "acknowledgeRisk": true to bypass the quality gate on your next compress call.`;
6461
+ Retry: write a more complete summary preserving critical details (file paths, decisions, exact values, errors), then call compress again on the same range. If you are confident the summary is adequate despite the metrics, pass "acknowledgeRisk": true.`;
6405
6462
  return new Error(message);
6406
6463
  }
6407
6464
 
@@ -8063,7 +8120,8 @@ function createCompressRangeTool(factoryCtx) {
8063
8120
  messageIds: plan.selection.messageIds,
8064
8121
  messageTokenById: plan.selection.messageTokenById
8065
8122
  },
8066
- result
8123
+ result,
8124
+ ctx.logger
8067
8125
  );
8068
8126
  }
8069
8127
  }
@@ -8381,6 +8439,29 @@ var resolveEffectiveCompressPermission = (basePermission, hostPermissions, agent
8381
8439
  var hasExplicitToolPermission = (permissionConfig, tool6) => {
8382
8440
  return permissionConfig ? Object.prototype.hasOwnProperty.call(permissionConfig, tool6) : false;
8383
8441
  };
8442
+ var anyRuleResolves = (permissionConfig, tool6) => {
8443
+ if (hasExplicitToolPermission(permissionConfig, tool6)) {
8444
+ return true;
8445
+ }
8446
+ const match = findLastMatchingRule(
8447
+ getPermissionRules([permissionConfig]),
8448
+ (rule) => wildcardMatch(tool6, rule.permission)
8449
+ );
8450
+ return match !== void 0;
8451
+ };
8452
+ var applyAcpToolPermissions = (permissionConfig, compressPermission2) => {
8453
+ if (permissionConfig && hasExplicitToolPermission(permissionConfig, "compress")) {
8454
+ return permissionConfig;
8455
+ }
8456
+ const next = {
8457
+ ...permissionConfig ?? {},
8458
+ compress: compressPermission2
8459
+ };
8460
+ if (!anyRuleResolves(permissionConfig, "acp_status")) {
8461
+ next.acp_status = "allow";
8462
+ }
8463
+ return next;
8464
+ };
8384
8465
 
8385
8466
  // lib/compress-permission.ts
8386
8467
  var compressPermission = (state, config) => {
@@ -9823,6 +9904,81 @@ function computeRestoredMessages(messagesState, activeMessagesBefore) {
9823
9904
  }
9824
9905
  return { restoredMessageCount, restoredTokens };
9825
9906
  }
9907
+ function collectRequiredDecompressMessageIds(messagesState, targets, options = {}) {
9908
+ const targetBlockIds = /* @__PURE__ */ new Set();
9909
+ for (const target of targets) {
9910
+ for (const block of target.blocks) {
9911
+ targetBlockIds.add(block.blockId);
9912
+ }
9913
+ }
9914
+ if (targetBlockIds.size === 0) {
9915
+ return [];
9916
+ }
9917
+ const consumedClosure = /* @__PURE__ */ new Set();
9918
+ const queue = [];
9919
+ for (const target of targets) {
9920
+ for (const block of target.blocks) {
9921
+ for (const id of block.consumedBlockIds ?? []) {
9922
+ if (!consumedClosure.has(id)) {
9923
+ queue.push(id);
9924
+ }
9925
+ }
9926
+ }
9927
+ }
9928
+ while (queue.length > 0) {
9929
+ const id = queue.shift();
9930
+ if (consumedClosure.has(id)) continue;
9931
+ consumedClosure.add(id);
9932
+ const consumed = messagesState.blocksById.get(id);
9933
+ if (consumed) {
9934
+ for (const nested of consumed.consumedBlockIds ?? []) {
9935
+ if (!consumedClosure.has(nested)) {
9936
+ queue.push(nested);
9937
+ }
9938
+ }
9939
+ }
9940
+ }
9941
+ let shieldedMessageIds = null;
9942
+ if (!options.full) {
9943
+ shieldedMessageIds = /* @__PURE__ */ new Set();
9944
+ for (const id of consumedClosure) {
9945
+ const block = messagesState.blocksById.get(id);
9946
+ if (!block || block.deactivatedByUser || block.deactivatedByUserDeep) continue;
9947
+ for (const messageId of block.effectiveMessageIds ?? []) {
9948
+ shieldedMessageIds.add(messageId);
9949
+ }
9950
+ }
9951
+ }
9952
+ const required = [];
9953
+ for (const [messageId, entry] of messagesState.byMessageId) {
9954
+ if (entry.activeBlockIds.length === 0) continue;
9955
+ let exclusivelyCoveredByTargets = true;
9956
+ for (const blockId of entry.activeBlockIds) {
9957
+ if (!targetBlockIds.has(blockId)) {
9958
+ exclusivelyCoveredByTargets = false;
9959
+ break;
9960
+ }
9961
+ }
9962
+ if (!exclusivelyCoveredByTargets) continue;
9963
+ if (shieldedMessageIds?.has(messageId)) continue;
9964
+ required.push(messageId);
9965
+ }
9966
+ required.sort();
9967
+ return required;
9968
+ }
9969
+ function checkDecompressSourceAvailability(messagesState, targets, options, presentInHistory) {
9970
+ const requiredMessageIds = collectRequiredDecompressMessageIds(messagesState, targets, options);
9971
+ const available = [];
9972
+ const missing = [];
9973
+ for (const messageId of requiredMessageIds) {
9974
+ if (presentInHistory.has(messageId)) {
9975
+ available.push(messageId);
9976
+ } else {
9977
+ missing.push(messageId);
9978
+ }
9979
+ }
9980
+ return { requiredMessageIds, availableMessageIds: available, missingMessageIds: missing };
9981
+ }
9826
9982
  function computeReactivatedBlockIds(messagesState, activeBlockIdsBefore) {
9827
9983
  return Array.from(messagesState.activeBlockIds).filter((blockId) => !activeBlockIdsBefore.has(blockId)).sort((a, b) => a - b);
9828
9984
  }
@@ -10031,14 +10187,31 @@ function buildSchema2() {
10031
10187
  };
10032
10188
  }
10033
10189
  function extractMessageId(m) {
10034
- return m.id ?? m.messageId ?? "";
10190
+ return m?.info?.id ?? "";
10191
+ }
10192
+ function serializeMessagePart(part) {
10193
+ if (!part || typeof part !== "object") return null;
10194
+ if (part.type === "text") {
10195
+ return typeof part.text === "string" && part.text.length > 0 ? part.text : null;
10196
+ }
10197
+ if (part.type === "tool") {
10198
+ const contents = extractToolContent(part);
10199
+ if (contents.length === 0) return null;
10200
+ const toolName = "tool" in part && typeof part.tool === "string" && part.tool.length > 0 ? part.tool : "tool";
10201
+ return `[${toolName}] ${contents.join("\n")}`;
10202
+ }
10203
+ return null;
10035
10204
  }
10036
10205
  function extractMessageText2(m) {
10037
- const msg = m;
10038
- const role = msg.role || msg.type || "unknown";
10039
- const content = typeof msg.content === "string" ? msg.content : typeof msg.text === "string" ? msg.text : JSON.stringify(msg.content || msg.text || "");
10206
+ const role = m?.info?.role ?? "unknown";
10207
+ const parts = Array.isArray(m.parts) ? m.parts : [];
10208
+ const segments = [];
10209
+ for (const part of parts) {
10210
+ const serialized = serializeMessagePart(part);
10211
+ if (serialized !== null) segments.push(serialized);
10212
+ }
10040
10213
  return `[${role}]
10041
- ${content}`;
10214
+ ${segments.join("\n\n")}`;
10042
10215
  }
10043
10216
  function createDecompressTool(factoryCtx) {
10044
10217
  return tool3({
@@ -10090,10 +10263,40 @@ function createDecompressTool(factoryCtx) {
10090
10263
  const blockMessages = rawMessages.filter((m) => msgIdSet.has(extractMessageId(m)));
10091
10264
  const lines2 = blockMessages.map(extractMessageText2);
10092
10265
  const { writeFile: writeFile4 } = await import("fs/promises");
10093
- const fileContent = lines2.length > 0 ? lines2.join("\n\n---\n\n") : targets[0]?.blocks[0]?.summary ?? "(no content available)";
10266
+ let fileContent;
10267
+ let sourceNote;
10268
+ if (lines2.length > 0) {
10269
+ fileContent = lines2.join("\n\n---\n\n");
10270
+ sourceNote = `${blockMessages.length} original message${blockMessages.length === 1 ? "" : "s"}`;
10271
+ } else {
10272
+ fileContent = targets[0]?.blocks[0]?.summary ?? "(no content available)";
10273
+ sourceNote = `no original messages found; wrote block summary (${targets.length} block${targets.length === 1 ? "" : "s"})`;
10274
+ }
10094
10275
  await writeFile4(targetPath, fileContent, "utf-8");
10095
10276
  const displayIds2 = targets.map((t) => `b${t.displayId}`).join(", ");
10096
- return `Block(s) ${displayIds2} content (${blockMessages.length} messages, ${fileContent.length} chars) written to ${targetPath}. Block(s) stay compressed \u2014 context unchanged. Use read tool to access specific parts.`;
10277
+ return `Block(s) ${displayIds2} content (${sourceNote}, ${fileContent.length} chars) written to ${targetPath}. Block(s) stay compressed \u2014 context unchanged. Use read tool to access specific parts.`;
10278
+ }
10279
+ const availability = checkDecompressSourceAvailability(
10280
+ messagesState,
10281
+ targets,
10282
+ { full: args.full === true },
10283
+ new Set(rawMessages.map((m) => m.info.id))
10284
+ );
10285
+ if (availability.missingMessageIds.length > 0) {
10286
+ const displayIds2 = targets.map((t) => `b${t.displayId}`).join(", ");
10287
+ const missingRefs = availability.missingMessageIds.map(
10288
+ (id) => ctx.state.messageIds.byRawId.get(id) ?? id
10289
+ );
10290
+ const shownRefs = missingRefs.slice(0, 10).join(", ");
10291
+ const extraCount = missingRefs.length - Math.min(missingRefs.length, 10);
10292
+ ctx.logger.warn("Decompress aborted: source messages missing from host history", {
10293
+ targetDisplayIds: displayIds2,
10294
+ full: args.full === true,
10295
+ requiredCount: availability.requiredMessageIds.length,
10296
+ missingCount: availability.missingMessageIds.length,
10297
+ missingRefs
10298
+ });
10299
+ return `Error: Cannot decompress ${displayIds2}: ${availability.missingMessageIds.length} of ${availability.requiredMessageIds.length} source message(s) are no longer present in session history (removed by native compaction or external deletion). Missing: ${shownRefs}` + (extraCount > 0 ? ` (+${extraCount} more)` : "") + `. Block(s) stay compressed \u2014 the summary remains available and nothing was changed. This is a missing-source condition, not a transient fetch failure.`;
10097
10300
  }
10098
10301
  const activeMessagesBefore = snapshotActiveMessages(messagesState);
10099
10302
  const activeBlockIdsBefore = new Set(messagesState.activeBlockIds);
@@ -10906,7 +11109,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
10906
11109
  import { join as join4 } from "path";
10907
11110
  import { existsSync as existsSync4 } from "fs";
10908
11111
  import { homedir as homedir3 } from "os";
10909
- var LOG_VERSION = true ? "1.18.2" : "dev";
11112
+ var LOG_VERSION = true ? "1.18.3-pr.475.210" : "dev";
10910
11113
  var LEVEL_RANK = {
10911
11114
  debug: 10,
10912
11115
  info: 20,
@@ -11712,7 +11915,17 @@ var KEEP_PREFIX_CHARS = 2e3;
11712
11915
  var KEEP_SUFFIX_CHARS = 2e3;
11713
11916
  var PROTECT_RECENT_MESSAGES = 3;
11714
11917
  var OUTPUT_RESERVE_TOKENS = 16384;
11918
+ var MAX_OVERHEAD_ERROR_SESSIONS = 1024;
11715
11919
  var overheadErrorLogged = /* @__PURE__ */ new Set();
11920
+ function recordOverheadError(sessionKey) {
11921
+ if (overheadErrorLogged.has(sessionKey)) return false;
11922
+ if (overheadErrorLogged.size >= MAX_OVERHEAD_ERROR_SESSIONS) {
11923
+ const oldest = overheadErrorLogged.values().next().value;
11924
+ if (oldest !== void 0) overheadErrorLogged.delete(oldest);
11925
+ }
11926
+ overheadErrorLogged.add(sessionKey);
11927
+ return true;
11928
+ }
11716
11929
  function parseGcThreshold(threshold, modelContextLimit) {
11717
11930
  if (typeof threshold === "number") return threshold;
11718
11931
  const str = threshold ?? "100%";
@@ -11730,8 +11943,7 @@ function truncateLargeToolOutputs(state, config, logger, messages) {
11730
11943
  const threshold = Math.min(configuredThreshold, effective.limit - overhead);
11731
11944
  if (threshold <= 0) {
11732
11945
  const sessionKey = state.sessionId ?? "unknown";
11733
- if (!overheadErrorLogged.has(sessionKey)) {
11734
- overheadErrorLogged.add(sessionKey);
11946
+ if (recordOverheadError(sessionKey)) {
11735
11947
  logger.error("ACP: model context window too small to fit overhead", {
11736
11948
  session: state.sessionId,
11737
11949
  limit: effective.limit,
@@ -13380,36 +13592,6 @@ function configureClientAuth(client) {
13380
13592
  return client;
13381
13593
  }
13382
13594
 
13383
- // lib/bili-proxy.ts
13384
- var BILI_PROXY_MARKER = "/bili/";
13385
- function extractBaseURL(entry) {
13386
- if (!entry || typeof entry !== "object") return void 0;
13387
- const record = entry;
13388
- const options = record.options;
13389
- if (options && typeof options === "object") {
13390
- const optionBaseURL = options.baseURL;
13391
- if (typeof optionBaseURL === "string" && optionBaseURL.length > 0) {
13392
- return optionBaseURL;
13393
- }
13394
- }
13395
- const topLevelBaseURL = record.baseURL;
13396
- if (typeof topLevelBaseURL === "string" && topLevelBaseURL.length > 0) {
13397
- return topLevelBaseURL;
13398
- }
13399
- return void 0;
13400
- }
13401
- function findBiliProxyProviders(provider) {
13402
- if (!provider || typeof provider !== "object") return [];
13403
- const matches = [];
13404
- for (const [name, entry] of Object.entries(provider)) {
13405
- const baseURL = extractBaseURL(entry);
13406
- if (baseURL !== void 0 && baseURL.includes(BILI_PROXY_MARKER)) {
13407
- matches.push({ provider: name, baseURL });
13408
- }
13409
- }
13410
- return matches;
13411
- }
13412
-
13413
13595
  // lib/update.ts
13414
13596
  import { readFile as readFile2, rm } from "fs/promises";
13415
13597
  import { basename, dirname as dirname4, join as join7 } from "path";
@@ -13616,15 +13798,14 @@ var server = (async (ctx) => {
13616
13798
  if (!config.enabled) {
13617
13799
  return {};
13618
13800
  }
13619
- if (process.env.BILLION_CONTEXT_PROXY) {
13620
- console.log(
13621
- "[opencode-acp] disabled: BILLION_CONTEXT_PROXY detected \u2014 proxy handles compression"
13622
- );
13801
+ const setupBiliYield = detectBiliEnvYield();
13802
+ if (setupBiliYield) {
13803
+ console.log(biliYieldLogMessage(setupBiliYield));
13623
13804
  return {};
13624
13805
  }
13625
13806
  const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
13626
13807
  logger.info("ACP plugin initialized", {
13627
- version: true ? "1.18.2" : "dev",
13808
+ version: true ? "1.18.3-pr.475.210" : "dev",
13628
13809
  workspace: ctx.directory,
13629
13810
  logLevel: logger.level,
13630
13811
  debug: config.debug,
@@ -13674,7 +13855,21 @@ var server = (async (ctx) => {
13674
13855
  prompts
13675
13856
  };
13676
13857
  let disabledByBiliProxy = false;
13677
- const guard = (fn) => (...args) => disabledByBiliProxy ? Promise.resolve() : fn(...args);
13858
+ let announcedEnvYieldSource;
13859
+ const noteEnvYield = (source) => {
13860
+ if (source === null || announcedEnvYieldSource === source) return;
13861
+ announcedEnvYieldSource = source;
13862
+ console.log(biliYieldLogMessage(source));
13863
+ };
13864
+ const guard = (fn) => (...args) => {
13865
+ if (disabledByBiliProxy) return Promise.resolve();
13866
+ const envYield = detectBiliEnvYield();
13867
+ if (envYield !== null) {
13868
+ noteEnvYield(envYield);
13869
+ return Promise.resolve();
13870
+ }
13871
+ return fn(...args);
13872
+ };
13678
13873
  return {
13679
13874
  "experimental.chat.system.transform": guard(
13680
13875
  createSystemPromptHandler(registry4, logger, config, prompts)
@@ -13711,14 +13906,9 @@ var server = (async (ctx) => {
13711
13906
  }
13712
13907
  },
13713
13908
  config: async (opencodeConfig) => {
13714
- const biliMatches = findBiliProxyProviders(opencodeConfig.provider);
13715
- disabledByBiliProxy = biliMatches.length > 0;
13716
- if (biliMatches.length > 0) {
13717
- console.log(
13718
- "[opencode-acp] disabled: /bili/ proxy detected in provider baseURL (" + biliMatches.map((m) => m.provider).join(", ") + ") \u2014 proxy handles compression"
13719
- );
13720
- const permission = opencodeConfig.permission ?? {};
13721
- opencodeConfig.permission = {
13909
+ const denyAcpTools = (opencodeConfig2) => {
13910
+ const permission = opencodeConfig2.permission ?? {};
13911
+ opencodeConfig2.permission = {
13722
13912
  ...permission,
13723
13913
  compress: "deny",
13724
13914
  decompress: "deny",
@@ -13726,6 +13916,20 @@ var server = (async (ctx) => {
13726
13916
  acp_status: "deny",
13727
13917
  acp_context_recap: "deny"
13728
13918
  };
13919
+ };
13920
+ const biliMatches = findBiliProxyProviders(opencodeConfig.provider);
13921
+ disabledByBiliProxy = biliMatches.length > 0;
13922
+ const envYield = detectBiliEnvYield();
13923
+ if (envYield !== null) {
13924
+ noteEnvYield(envYield);
13925
+ denyAcpTools(opencodeConfig);
13926
+ return;
13927
+ }
13928
+ if (biliMatches.length > 0) {
13929
+ console.log(
13930
+ "[opencode-acp] disabled: /bili/ proxy detected in provider baseURL (" + biliMatches.map((m) => m.provider).join(", ") + ") \u2014 proxy handles compression"
13931
+ );
13932
+ denyAcpTools(opencodeConfig);
13729
13933
  return;
13730
13934
  }
13731
13935
  if (config.compress.permission !== "deny" && compressDisabledByOpencode(opencodeConfig.permission)) {
@@ -13749,14 +13953,10 @@ var server = (async (ctx) => {
13749
13953
  primary_tools: [...existingPrimaryTools, ...toolsToAdd]
13750
13954
  };
13751
13955
  }
13752
- if (!hasExplicitToolPermission(opencodeConfig.permission, "compress")) {
13753
- const permission = opencodeConfig.permission ?? {};
13754
- opencodeConfig.permission = {
13755
- ...permission,
13756
- compress: config.compress.permission,
13757
- acp_status: "allow"
13758
- };
13759
- }
13956
+ opencodeConfig.permission = applyAcpToolPermissions(
13957
+ opencodeConfig.permission,
13958
+ config.compress.permission
13959
+ );
13760
13960
  hostPermissions.global = opencodeConfig.permission;
13761
13961
  hostPermissions.agents = Object.fromEntries(
13762
13962
  Object.entries(opencodeConfig.agent ?? {}).map(([name, agent]) => [