opencode-acp 1.18.2 → 1.18.3-pr.477.211

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/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(
@@ -2859,7 +2908,8 @@ function saveSessionState(sessionState, logger, sessionName) {
2859
2908
  lastCompaction: sessionState.lastCompaction,
2860
2909
  modelContextLimit: sessionState.modelContextLimit,
2861
2910
  modelProviderID: sessionState.modelProviderID,
2862
- modelID: sessionState.modelID
2911
+ modelID: sessionState.modelID,
2912
+ usageSanity: sessionState.usageSanity
2863
2913
  };
2864
2914
  const key = saveQueueKey(sessionState.sessionId, sessionState.storageDir);
2865
2915
  let queue = saveQueues.get(key);
@@ -3714,18 +3764,32 @@ function adjustBoundariesForToolPairs(context, startIdx, endIdx) {
3714
3764
  return { startIndex: newStartIdx, endIndex: newEndIdx };
3715
3765
  }
3716
3766
  function buildBoundaryRecoveryHint(context, state) {
3717
- const visibleRefs = [];
3767
+ const visibleIndexes = [];
3718
3768
  for (const [messageRef, messageId] of state.messageIds.byRef) {
3719
- if (context.rawMessagesById.has(messageId)) {
3720
- visibleRefs.push(messageRef);
3721
- }
3769
+ if (!context.rawMessagesById.has(messageId)) continue;
3770
+ const idx = parseMessageRef(messageRef);
3771
+ if (idx !== null) visibleIndexes.push(idx);
3722
3772
  }
3773
+ visibleIndexes.sort((a, b) => a - b);
3774
+ const renderSpan = (start, end) => start === end ? formatMessageRef(start) : `${formatMessageRef(start)}\u2013${formatMessageRef(end)}`;
3723
3775
  const parts = [];
3724
- if (visibleRefs.length > 0) {
3725
- visibleRefs.sort();
3726
- const first = visibleRefs[0];
3727
- const last = visibleRefs[visibleRefs.length - 1];
3728
- parts.push(`Current visible: ${first}\u2013${last} (${visibleRefs.length} msgs).`);
3776
+ if (visibleIndexes.length > 0) {
3777
+ const spans = [];
3778
+ let spanStart = visibleIndexes[0];
3779
+ let spanPrev = visibleIndexes[0];
3780
+ for (let i = 1; i < visibleIndexes.length; i++) {
3781
+ const idx = visibleIndexes[i];
3782
+ if (idx !== spanPrev + 1) {
3783
+ spans.push(renderSpan(spanStart, spanPrev));
3784
+ spanStart = idx;
3785
+ }
3786
+ spanPrev = idx;
3787
+ }
3788
+ spans.push(renderSpan(spanStart, spanPrev));
3789
+ const MAX_SPANS = 8;
3790
+ const shown = spans.slice(0, MAX_SPANS);
3791
+ const overflow = spans.length - shown.length;
3792
+ parts.push(`Free ranges: ${shown.join(", ")}${overflow > 0 ? ` (+${overflow} more)` : ""}.`);
3729
3793
  }
3730
3794
  const blockCount = context.summaryByBlockId.size;
3731
3795
  if (blockCount > 0) {
@@ -3734,7 +3798,7 @@ function buildBoundaryRecoveryHint(context, state) {
3734
3798
  if (parts.length === 0) {
3735
3799
  return "";
3736
3800
  }
3737
- return `${parts.join(" ")} Call acp_status() to see which blocks consumed which IDs, then retry with valid IDs.`;
3801
+ return `${parts.join(" ")} Retry with IDs inside the free ranges.`;
3738
3802
  }
3739
3803
  function clampMessageRef(requested, context, state) {
3740
3804
  if (state.messageIds.byRef.has(requested.ref)) return null;
@@ -5183,6 +5247,14 @@ async function ensureSessionInitialized(client, state, sessionId, logger, messag
5183
5247
  state.modelProviderID = persisted.modelProviderID;
5184
5248
  state.modelID = persisted.modelID;
5185
5249
  }
5250
+ if (persisted.usageSanity) {
5251
+ const { reported, estimated } = persisted.usageSanity;
5252
+ if (Number.isFinite(reported) && Number.isFinite(estimated) && reported >= 0 && estimated >= 0) {
5253
+ state.usageSanity = { reported, estimated };
5254
+ } else {
5255
+ state.usageSanity = void 0;
5256
+ }
5257
+ }
5186
5258
  const applied = applyPendingCompressionDurations(state);
5187
5259
  if (applied > 0) {
5188
5260
  await saveSessionState(state, logger);
@@ -6384,24 +6456,32 @@ function computeStats(plan) {
6384
6456
  const retentionPct = originalTokens > 0 ? (summaryChars / (originalTokens * 4) * 100).toFixed(2) : "?";
6385
6457
  return { originalTokens, summaryChars, ratio, retentionPct };
6386
6458
  }
6387
- function buildQualityRejectionError(plan, result) {
6459
+ function coreReason(reason) {
6460
+ if (!reason) return "unknown";
6461
+ const candidates = [reason.indexOf(":"), reason.indexOf("(")].filter((i) => i >= 0);
6462
+ const cut = Math.min(...candidates);
6463
+ return (cut > 0 ? reason.slice(0, cut) : reason).trim() || "unknown";
6464
+ }
6465
+ function buildQualityRejectionError(plan, result, logger) {
6388
6466
  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
- ];
6467
+ logger?.warn("quality gate rejected compression", {
6468
+ range: `${plan.startId}\u2013${plan.endId}`,
6469
+ reason: result.reason || "unknown",
6470
+ layer: result.layer ?? "unknown",
6471
+ originalTokens: stats.originalTokens,
6472
+ summaryChars: stats.summaryChars,
6473
+ ratio: stats.ratio,
6474
+ retentionPct: stats.retentionPct,
6475
+ rougeF1: formatMetric(result, "rougeF1"),
6476
+ top20Recall: formatMetric(result, "top20Recall")
6477
+ });
6399
6478
  const message = `\u26A0\uFE0F COMPRESSION REJECTED \u2014 QUALITY GATE FAILURE
6400
6479
 
6401
6480
  Range: ${plan.startId}\u2013${plan.endId}
6402
- ${metrics.join("\n")}
6481
+ Reason: ${coreReason(result.reason)}
6482
+ Original: ~${stats.originalTokens} tokens \xB7 Summary: ${stats.summaryChars} chars \xB7 Retention: ${stats.retentionPct}%
6403
6483
 
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.`;
6484
+ 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
6485
  return new Error(message);
6406
6486
  }
6407
6487
 
@@ -6727,6 +6807,49 @@ ${guidance}
6727
6807
  ${afterClose}`;
6728
6808
  }
6729
6809
 
6810
+ // lib/usage-sanity.ts
6811
+ var RELAY_INFLATION_RATIO = 1.5;
6812
+ var PAYLOAD_FLOOR_TOKENS = 5e3;
6813
+ function estimatePayloadTokens(messages) {
6814
+ let total = 0;
6815
+ for (const msg of messages) {
6816
+ if (isSyntheticMessage(msg)) continue;
6817
+ if (isIgnoredUserMessage(msg)) continue;
6818
+ total += estimateAllMessageTokensFast(msg);
6819
+ }
6820
+ return total;
6821
+ }
6822
+ function resolveCurrentUsage(state, messages, logger) {
6823
+ const reported = getCurrentTokenUsage(state, messages);
6824
+ if (reported === 0) {
6825
+ return { tokens: 0, source: "reported", inflationDetected: false };
6826
+ }
6827
+ const estimated = estimatePayloadTokens(messages) + (state.systemPromptTokens ?? 0);
6828
+ const prev = state.usageSanity;
6829
+ let inflationDetected = false;
6830
+ if (prev !== void 0 && prev.reported > 0 && prev.estimated >= PAYLOAD_FLOOR_TOKENS && estimated >= PAYLOAD_FLOOR_TOKENS) {
6831
+ const reportRatio = reported / prev.reported;
6832
+ const payloadRatio = estimated / prev.estimated;
6833
+ if (reportRatio > RELAY_INFLATION_RATIO && payloadRatio <= RELAY_INFLATION_RATIO) {
6834
+ inflationDetected = true;
6835
+ }
6836
+ }
6837
+ state.usageSanity = { reported, estimated };
6838
+ if (inflationDetected) {
6839
+ logger?.warn("Relay usage inflation detected \u2014 re-estimating context size", {
6840
+ session: state.sessionId,
6841
+ reported,
6842
+ previousReported: prev.reported,
6843
+ reportRatio: +(reported / prev.reported).toFixed(3),
6844
+ estimated,
6845
+ previousEstimated: prev.estimated,
6846
+ payloadRatio: +(estimated / prev.estimated).toFixed(3)
6847
+ });
6848
+ return { tokens: estimated, source: "reestimated", inflationDetected: true };
6849
+ }
6850
+ return { tokens: reported, source: "reported", inflationDetected: false };
6851
+ }
6852
+
6730
6853
  // lib/messages/utils.ts
6731
6854
  import { createHash as createHash2 } from "crypto";
6732
6855
  var SUMMARY_ID_HASH_LENGTH = 16;
@@ -7077,7 +7200,7 @@ function isContextOverLimits(config, state, providerId, modelId, messages) {
7077
7200
  );
7078
7201
  const maxContextLimit = resolvedMaxContextLimit === void 0 ? void 0 : resolvedMaxContextLimit + summaryTokenExtension;
7079
7202
  const minContextLimit = resolveContextTokenLimit(config, state, providerId, modelId, "min");
7080
- const currentTokens = getCurrentTokenUsage(state, messages);
7203
+ const currentTokens = resolveCurrentUsage(state, messages).tokens;
7081
7204
  let overMaxLimit = maxContextLimit === void 0 ? false : currentTokens > maxContextLimit;
7082
7205
  const overMinLimit = minContextLimit === void 0 ? false : currentTokens >= minContextLimit;
7083
7206
  if (overMaxLimit) {
@@ -8063,7 +8186,8 @@ function createCompressRangeTool(factoryCtx) {
8063
8186
  messageIds: plan.selection.messageIds,
8064
8187
  messageTokenById: plan.selection.messageTokenById
8065
8188
  },
8066
- result
8189
+ result,
8190
+ ctx.logger
8067
8191
  );
8068
8192
  }
8069
8193
  }
@@ -8381,6 +8505,29 @@ var resolveEffectiveCompressPermission = (basePermission, hostPermissions, agent
8381
8505
  var hasExplicitToolPermission = (permissionConfig, tool6) => {
8382
8506
  return permissionConfig ? Object.prototype.hasOwnProperty.call(permissionConfig, tool6) : false;
8383
8507
  };
8508
+ var anyRuleResolves = (permissionConfig, tool6) => {
8509
+ if (hasExplicitToolPermission(permissionConfig, tool6)) {
8510
+ return true;
8511
+ }
8512
+ const match = findLastMatchingRule(
8513
+ getPermissionRules([permissionConfig]),
8514
+ (rule) => wildcardMatch(tool6, rule.permission)
8515
+ );
8516
+ return match !== void 0;
8517
+ };
8518
+ var applyAcpToolPermissions = (permissionConfig, compressPermission2) => {
8519
+ if (permissionConfig && hasExplicitToolPermission(permissionConfig, "compress")) {
8520
+ return permissionConfig;
8521
+ }
8522
+ const next = {
8523
+ ...permissionConfig ?? {},
8524
+ compress: compressPermission2
8525
+ };
8526
+ if (!anyRuleResolves(permissionConfig, "acp_status")) {
8527
+ next.acp_status = "allow";
8528
+ }
8529
+ return next;
8530
+ };
8384
8531
 
8385
8532
  // lib/compress-permission.ts
8386
8533
  var compressPermission = (state, config) => {
@@ -9205,7 +9352,8 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
9205
9352
  const allInProtectedZone = protectedRefs.size > 0 && unprotectedCompressible.length === 0;
9206
9353
  const allBelowMin = contextRanges.compressible.length > 0 && recommendedRanges.length === 0;
9207
9354
  const noExecutableCandidates = candidatesEnabled && nudgeAllowed && candidateMessages !== void 0 && candidatePlan.candidates.length === 0;
9208
- const nothingToCompress = candidatesEnabled && candidateMessages !== void 0 ? noExecutableCandidates : allProtected || allInProtectedZone || allBelowMin;
9355
+ const noRangesAtAll = contextRanges.compressible.length === 0 && contextRanges.protected.length === 0;
9356
+ const nothingToCompress = (candidatesEnabled && candidateMessages !== void 0 ? noExecutableCandidates : allProtected || allInProtectedZone || allBelowMin) || noRangesAtAll;
9209
9357
  const emergencyNoTargets = emergencyOverride && nothingToCompress;
9210
9358
  const noticeCadenceMet = state.nudges.lastNudgeShownTokens === void 0 || growthSinceBaseline !== void 0 && growthSinceBaseline >= growthFloor;
9211
9359
  let shouldInjectNudge = nudgeAllowed && !nothingToCompress;
@@ -9410,7 +9558,7 @@ ${rules}`;
9410
9558
  } else if (nudgeAllowed && nothingToCompress) {
9411
9559
  logger.info("Nudge suppressed: nothing to compress", {
9412
9560
  session: state.sessionId,
9413
- reason: allProtected ? "all_protected" : allInProtectedZone ? "in_protected_zone" : allBelowMin ? "below_effective_floor" : "no_executable_candidates",
9561
+ reason: allProtected ? "all_protected" : allInProtectedZone ? "in_protected_zone" : allBelowMin ? "below_effective_floor" : noRangesAtAll ? "no_compressible_ranges" : "no_executable_candidates",
9414
9562
  currentTokens,
9415
9563
  usagePct,
9416
9564
  compressibleRanges: contextRanges.compressible.length
@@ -9823,6 +9971,81 @@ function computeRestoredMessages(messagesState, activeMessagesBefore) {
9823
9971
  }
9824
9972
  return { restoredMessageCount, restoredTokens };
9825
9973
  }
9974
+ function collectRequiredDecompressMessageIds(messagesState, targets, options = {}) {
9975
+ const targetBlockIds = /* @__PURE__ */ new Set();
9976
+ for (const target of targets) {
9977
+ for (const block of target.blocks) {
9978
+ targetBlockIds.add(block.blockId);
9979
+ }
9980
+ }
9981
+ if (targetBlockIds.size === 0) {
9982
+ return [];
9983
+ }
9984
+ const consumedClosure = /* @__PURE__ */ new Set();
9985
+ const queue = [];
9986
+ for (const target of targets) {
9987
+ for (const block of target.blocks) {
9988
+ for (const id of block.consumedBlockIds ?? []) {
9989
+ if (!consumedClosure.has(id)) {
9990
+ queue.push(id);
9991
+ }
9992
+ }
9993
+ }
9994
+ }
9995
+ while (queue.length > 0) {
9996
+ const id = queue.shift();
9997
+ if (consumedClosure.has(id)) continue;
9998
+ consumedClosure.add(id);
9999
+ const consumed = messagesState.blocksById.get(id);
10000
+ if (consumed) {
10001
+ for (const nested of consumed.consumedBlockIds ?? []) {
10002
+ if (!consumedClosure.has(nested)) {
10003
+ queue.push(nested);
10004
+ }
10005
+ }
10006
+ }
10007
+ }
10008
+ let shieldedMessageIds = null;
10009
+ if (!options.full) {
10010
+ shieldedMessageIds = /* @__PURE__ */ new Set();
10011
+ for (const id of consumedClosure) {
10012
+ const block = messagesState.blocksById.get(id);
10013
+ if (!block || block.deactivatedByUser || block.deactivatedByUserDeep) continue;
10014
+ for (const messageId of block.effectiveMessageIds ?? []) {
10015
+ shieldedMessageIds.add(messageId);
10016
+ }
10017
+ }
10018
+ }
10019
+ const required = [];
10020
+ for (const [messageId, entry] of messagesState.byMessageId) {
10021
+ if (entry.activeBlockIds.length === 0) continue;
10022
+ let exclusivelyCoveredByTargets = true;
10023
+ for (const blockId of entry.activeBlockIds) {
10024
+ if (!targetBlockIds.has(blockId)) {
10025
+ exclusivelyCoveredByTargets = false;
10026
+ break;
10027
+ }
10028
+ }
10029
+ if (!exclusivelyCoveredByTargets) continue;
10030
+ if (shieldedMessageIds?.has(messageId)) continue;
10031
+ required.push(messageId);
10032
+ }
10033
+ required.sort();
10034
+ return required;
10035
+ }
10036
+ function checkDecompressSourceAvailability(messagesState, targets, options, presentInHistory) {
10037
+ const requiredMessageIds = collectRequiredDecompressMessageIds(messagesState, targets, options);
10038
+ const available = [];
10039
+ const missing = [];
10040
+ for (const messageId of requiredMessageIds) {
10041
+ if (presentInHistory.has(messageId)) {
10042
+ available.push(messageId);
10043
+ } else {
10044
+ missing.push(messageId);
10045
+ }
10046
+ }
10047
+ return { requiredMessageIds, availableMessageIds: available, missingMessageIds: missing };
10048
+ }
9826
10049
  function computeReactivatedBlockIds(messagesState, activeBlockIdsBefore) {
9827
10050
  return Array.from(messagesState.activeBlockIds).filter((blockId) => !activeBlockIdsBefore.has(blockId)).sort((a, b) => a - b);
9828
10051
  }
@@ -10031,14 +10254,31 @@ function buildSchema2() {
10031
10254
  };
10032
10255
  }
10033
10256
  function extractMessageId(m) {
10034
- return m.id ?? m.messageId ?? "";
10257
+ return m?.info?.id ?? "";
10258
+ }
10259
+ function serializeMessagePart(part) {
10260
+ if (!part || typeof part !== "object") return null;
10261
+ if (part.type === "text") {
10262
+ return typeof part.text === "string" && part.text.length > 0 ? part.text : null;
10263
+ }
10264
+ if (part.type === "tool") {
10265
+ const contents = extractToolContent(part);
10266
+ if (contents.length === 0) return null;
10267
+ const toolName = "tool" in part && typeof part.tool === "string" && part.tool.length > 0 ? part.tool : "tool";
10268
+ return `[${toolName}] ${contents.join("\n")}`;
10269
+ }
10270
+ return null;
10035
10271
  }
10036
10272
  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 || "");
10273
+ const role = m?.info?.role ?? "unknown";
10274
+ const parts = Array.isArray(m.parts) ? m.parts : [];
10275
+ const segments = [];
10276
+ for (const part of parts) {
10277
+ const serialized = serializeMessagePart(part);
10278
+ if (serialized !== null) segments.push(serialized);
10279
+ }
10040
10280
  return `[${role}]
10041
- ${content}`;
10281
+ ${segments.join("\n\n")}`;
10042
10282
  }
10043
10283
  function createDecompressTool(factoryCtx) {
10044
10284
  return tool3({
@@ -10090,10 +10330,40 @@ function createDecompressTool(factoryCtx) {
10090
10330
  const blockMessages = rawMessages.filter((m) => msgIdSet.has(extractMessageId(m)));
10091
10331
  const lines2 = blockMessages.map(extractMessageText2);
10092
10332
  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)";
10333
+ let fileContent;
10334
+ let sourceNote;
10335
+ if (lines2.length > 0) {
10336
+ fileContent = lines2.join("\n\n---\n\n");
10337
+ sourceNote = `${blockMessages.length} original message${blockMessages.length === 1 ? "" : "s"}`;
10338
+ } else {
10339
+ fileContent = targets[0]?.blocks[0]?.summary ?? "(no content available)";
10340
+ sourceNote = `no original messages found; wrote block summary (${targets.length} block${targets.length === 1 ? "" : "s"})`;
10341
+ }
10094
10342
  await writeFile4(targetPath, fileContent, "utf-8");
10095
10343
  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.`;
10344
+ 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.`;
10345
+ }
10346
+ const availability = checkDecompressSourceAvailability(
10347
+ messagesState,
10348
+ targets,
10349
+ { full: args.full === true },
10350
+ new Set(rawMessages.map((m) => m.info.id))
10351
+ );
10352
+ if (availability.missingMessageIds.length > 0) {
10353
+ const displayIds2 = targets.map((t) => `b${t.displayId}`).join(", ");
10354
+ const missingRefs = availability.missingMessageIds.map(
10355
+ (id) => ctx.state.messageIds.byRawId.get(id) ?? id
10356
+ );
10357
+ const shownRefs = missingRefs.slice(0, 10).join(", ");
10358
+ const extraCount = missingRefs.length - Math.min(missingRefs.length, 10);
10359
+ ctx.logger.warn("Decompress aborted: source messages missing from host history", {
10360
+ targetDisplayIds: displayIds2,
10361
+ full: args.full === true,
10362
+ requiredCount: availability.requiredMessageIds.length,
10363
+ missingCount: availability.missingMessageIds.length,
10364
+ missingRefs
10365
+ });
10366
+ 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
10367
  }
10098
10368
  const activeMessagesBefore = snapshotActiveMessages(messagesState);
10099
10369
  const activeBlockIdsBefore = new Set(messagesState.activeBlockIds);
@@ -10906,7 +11176,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
10906
11176
  import { join as join4 } from "path";
10907
11177
  import { existsSync as existsSync4 } from "fs";
10908
11178
  import { homedir as homedir3 } from "os";
10909
- var LOG_VERSION = true ? "1.18.2" : "dev";
11179
+ var LOG_VERSION = true ? "1.18.3-pr.477.211" : "dev";
10910
11180
  var LEVEL_RANK = {
10911
11181
  debug: 10,
10912
11182
  info: 20,
@@ -11712,7 +11982,17 @@ var KEEP_PREFIX_CHARS = 2e3;
11712
11982
  var KEEP_SUFFIX_CHARS = 2e3;
11713
11983
  var PROTECT_RECENT_MESSAGES = 3;
11714
11984
  var OUTPUT_RESERVE_TOKENS = 16384;
11985
+ var MAX_OVERHEAD_ERROR_SESSIONS = 1024;
11715
11986
  var overheadErrorLogged = /* @__PURE__ */ new Set();
11987
+ function recordOverheadError(sessionKey) {
11988
+ if (overheadErrorLogged.has(sessionKey)) return false;
11989
+ if (overheadErrorLogged.size >= MAX_OVERHEAD_ERROR_SESSIONS) {
11990
+ const oldest = overheadErrorLogged.values().next().value;
11991
+ if (oldest !== void 0) overheadErrorLogged.delete(oldest);
11992
+ }
11993
+ overheadErrorLogged.add(sessionKey);
11994
+ return true;
11995
+ }
11716
11996
  function parseGcThreshold(threshold, modelContextLimit) {
11717
11997
  if (typeof threshold === "number") return threshold;
11718
11998
  const str = threshold ?? "100%";
@@ -11730,8 +12010,7 @@ function truncateLargeToolOutputs(state, config, logger, messages) {
11730
12010
  const threshold = Math.min(configuredThreshold, effective.limit - overhead);
11731
12011
  if (threshold <= 0) {
11732
12012
  const sessionKey = state.sessionId ?? "unknown";
11733
- if (!overheadErrorLogged.has(sessionKey)) {
11734
- overheadErrorLogged.add(sessionKey);
12013
+ if (recordOverheadError(sessionKey)) {
11735
12014
  logger.error("ACP: model context window too small to fit overhead", {
11736
12015
  session: state.sessionId,
11737
12016
  limit: effective.limit,
@@ -13118,7 +13397,7 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
13118
13397
  saveSessionState(state, logger).catch(() => {
13119
13398
  });
13120
13399
  }
13121
- const prePruneTokens = getCurrentTokenUsage(state, output.messages);
13400
+ const prePruneTokens = resolveCurrentUsage(state, output.messages, logger).tokens;
13122
13401
  const candidateMessages = config.compress.candidates === true ? output.messages.slice() : void 0;
13123
13402
  prune(state, logger, config, output.messages);
13124
13403
  hideConsumedCompressCalls(state, output.messages);
@@ -13167,7 +13446,7 @@ ${text}`);
13167
13446
  if (state.sessionId && smartPlanEnabled === true) {
13168
13447
  recordVisibleMessages(state.sessionId, output.messages);
13169
13448
  }
13170
- const postTokens = getCurrentTokenUsage(state, output.messages);
13449
+ const postTokens = resolveCurrentUsage(state, output.messages, logger).tokens;
13171
13450
  if (postTokens !== void 0 && effectiveLimit) {
13172
13451
  const budget = effectiveLimit.limit - (state.systemPromptTokens ?? 0) - OUTPUT_RESERVE_TOKENS;
13173
13452
  if (postTokens > budget) {
@@ -13380,36 +13659,6 @@ function configureClientAuth(client) {
13380
13659
  return client;
13381
13660
  }
13382
13661
 
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
13662
  // lib/update.ts
13414
13663
  import { readFile as readFile2, rm } from "fs/promises";
13415
13664
  import { basename, dirname as dirname4, join as join7 } from "path";
@@ -13616,15 +13865,14 @@ var server = (async (ctx) => {
13616
13865
  if (!config.enabled) {
13617
13866
  return {};
13618
13867
  }
13619
- if (process.env.BILLION_CONTEXT_PROXY) {
13620
- console.log(
13621
- "[opencode-acp] disabled: BILLION_CONTEXT_PROXY detected \u2014 proxy handles compression"
13622
- );
13868
+ const setupBiliYield = detectBiliEnvYield();
13869
+ if (setupBiliYield) {
13870
+ console.log(biliYieldLogMessage(setupBiliYield));
13623
13871
  return {};
13624
13872
  }
13625
13873
  const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
13626
13874
  logger.info("ACP plugin initialized", {
13627
- version: true ? "1.18.2" : "dev",
13875
+ version: true ? "1.18.3-pr.477.211" : "dev",
13628
13876
  workspace: ctx.directory,
13629
13877
  logLevel: logger.level,
13630
13878
  debug: config.debug,
@@ -13674,7 +13922,21 @@ var server = (async (ctx) => {
13674
13922
  prompts
13675
13923
  };
13676
13924
  let disabledByBiliProxy = false;
13677
- const guard = (fn) => (...args) => disabledByBiliProxy ? Promise.resolve() : fn(...args);
13925
+ let announcedEnvYieldSource;
13926
+ const noteEnvYield = (source) => {
13927
+ if (source === null || announcedEnvYieldSource === source) return;
13928
+ announcedEnvYieldSource = source;
13929
+ console.log(biliYieldLogMessage(source));
13930
+ };
13931
+ const guard = (fn) => (...args) => {
13932
+ if (disabledByBiliProxy) return Promise.resolve();
13933
+ const envYield = detectBiliEnvYield();
13934
+ if (envYield !== null) {
13935
+ noteEnvYield(envYield);
13936
+ return Promise.resolve();
13937
+ }
13938
+ return fn(...args);
13939
+ };
13678
13940
  return {
13679
13941
  "experimental.chat.system.transform": guard(
13680
13942
  createSystemPromptHandler(registry4, logger, config, prompts)
@@ -13711,14 +13973,9 @@ var server = (async (ctx) => {
13711
13973
  }
13712
13974
  },
13713
13975
  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 = {
13976
+ const denyAcpTools = (opencodeConfig2) => {
13977
+ const permission = opencodeConfig2.permission ?? {};
13978
+ opencodeConfig2.permission = {
13722
13979
  ...permission,
13723
13980
  compress: "deny",
13724
13981
  decompress: "deny",
@@ -13726,6 +13983,20 @@ var server = (async (ctx) => {
13726
13983
  acp_status: "deny",
13727
13984
  acp_context_recap: "deny"
13728
13985
  };
13986
+ };
13987
+ const biliMatches = findBiliProxyProviders(opencodeConfig.provider);
13988
+ disabledByBiliProxy = biliMatches.length > 0;
13989
+ const envYield = detectBiliEnvYield();
13990
+ if (envYield !== null) {
13991
+ noteEnvYield(envYield);
13992
+ denyAcpTools(opencodeConfig);
13993
+ return;
13994
+ }
13995
+ if (biliMatches.length > 0) {
13996
+ console.log(
13997
+ "[opencode-acp] disabled: /bili/ proxy detected in provider baseURL (" + biliMatches.map((m) => m.provider).join(", ") + ") \u2014 proxy handles compression"
13998
+ );
13999
+ denyAcpTools(opencodeConfig);
13729
14000
  return;
13730
14001
  }
13731
14002
  if (config.compress.permission !== "deny" && compressDisabledByOpencode(opencodeConfig.permission)) {
@@ -13749,14 +14020,10 @@ var server = (async (ctx) => {
13749
14020
  primary_tools: [...existingPrimaryTools, ...toolsToAdd]
13750
14021
  };
13751
14022
  }
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
- }
14023
+ opencodeConfig.permission = applyAcpToolPermissions(
14024
+ opencodeConfig.permission,
14025
+ config.compress.permission
14026
+ );
13760
14027
  hostPermissions.global = opencodeConfig.permission;
13761
14028
  hostPermissions.agents = Object.fromEntries(
13762
14029
  Object.entries(opencodeConfig.agent ?? {}).map(([name, agent]) => [