opencode-acp 1.16.0-pr.360.128 → 1.16.0-pr.365.125

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
@@ -890,7 +890,6 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
890
890
  "compress.modelMaxLimits",
891
891
  "compress.modelMinLimits",
892
892
  "compress.providers",
893
- "compress.contextLimitFallback",
894
893
  "compress.nudgeFrequency",
895
894
  "compress.minNudgeContextPercent",
896
895
  "compress.nudgeGrowthTokens",
@@ -914,7 +913,6 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
914
913
  "compress.reasoning",
915
914
  "compress.reasoning.drop",
916
915
  "compress.reasoning.threshold",
917
- "compress.completionReserveTokens",
918
916
  "gc",
919
917
  "gc.algorithm",
920
918
  "gc.promotionThreshold",
@@ -966,11 +964,7 @@ function validateConfigTypes(config) {
966
964
  errors.push({ key: "storagePath", expected: "string", actual: typeof config.storagePath });
967
965
  }
968
966
  if (config.allowSubAgents !== void 0 && typeof config.allowSubAgents !== "boolean") {
969
- errors.push({
970
- key: "allowSubAgents",
971
- expected: "boolean",
972
- actual: typeof config.allowSubAgents
973
- });
967
+ errors.push({ key: "allowSubAgents", expected: "boolean", actual: typeof config.allowSubAgents });
974
968
  }
975
969
  if (config.pruneNotification !== void 0) {
976
970
  const validValues = ["off", "minimal", "detailed"];
@@ -1299,20 +1293,6 @@ function validateConfigTypes(config) {
1299
1293
  }
1300
1294
  }
1301
1295
  }
1302
- if (compress.completionReserveTokens !== void 0 && typeof compress.completionReserveTokens !== "number") {
1303
- errors.push({
1304
- key: "compress.completionReserveTokens",
1305
- expected: "number",
1306
- actual: typeof compress.completionReserveTokens
1307
- });
1308
- }
1309
- if (typeof compress.completionReserveTokens === "number" && compress.completionReserveTokens < 0) {
1310
- errors.push({
1311
- key: "compress.completionReserveTokens",
1312
- expected: "non-negative number (>= 0)",
1313
- actual: `${compress.completionReserveTokens}`
1314
- });
1315
- }
1316
1296
  if (typeof compress.iterationNudgeThreshold === "number" && compress.iterationNudgeThreshold < 1) {
1317
1297
  errors.push({
1318
1298
  key: "compress.iterationNudgeThreshold",
@@ -1431,20 +1411,12 @@ function validateConfigTypes(config) {
1431
1411
  break;
1432
1412
  case "nudgeForce":
1433
1413
  if (value !== "strong" && value !== "soft") {
1434
- errors.push({
1435
- key,
1436
- expected: "'strong' | 'soft'",
1437
- actual: JSON.stringify(value)
1438
- });
1414
+ errors.push({ key, expected: "'strong' | 'soft'", actual: JSON.stringify(value) });
1439
1415
  }
1440
1416
  break;
1441
1417
  case "stringArray":
1442
1418
  if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string")) {
1443
- errors.push({
1444
- key,
1445
- expected: "string[]",
1446
- actual: JSON.stringify(value)
1447
- });
1419
+ errors.push({ key, expected: "string[]", actual: JSON.stringify(value) });
1448
1420
  }
1449
1421
  break;
1450
1422
  case "reasoningConfig":
@@ -1479,11 +1451,7 @@ function validateConfigTypes(config) {
1479
1451
  return;
1480
1452
  }
1481
1453
  if (typeof overrides !== "object" || overrides === null || Array.isArray(overrides)) {
1482
- errors.push({
1483
- key: prefix,
1484
- expected: "CompressModelOverrides",
1485
- actual: typeof overrides
1486
- });
1454
+ errors.push({ key: prefix, expected: "CompressModelOverrides", actual: typeof overrides });
1487
1455
  return;
1488
1456
  }
1489
1457
  const model = overrides;
@@ -1516,11 +1484,7 @@ function validateConfigTypes(config) {
1516
1484
  for (const [providerId, providerValue] of Object.entries(providers)) {
1517
1485
  const prefix = `compress.providers.${providerId}`;
1518
1486
  if (typeof providerValue !== "object" || providerValue === null || Array.isArray(providerValue)) {
1519
- errors.push({
1520
- key: prefix,
1521
- expected: "ProviderOverrides",
1522
- actual: typeof providerValue
1523
- });
1487
+ errors.push({ key: prefix, expected: "ProviderOverrides", actual: typeof providerValue });
1524
1488
  continue;
1525
1489
  }
1526
1490
  const provider = providerValue;
@@ -1557,20 +1521,6 @@ function validateConfigTypes(config) {
1557
1521
  }
1558
1522
  };
1559
1523
  validateProviderOverrides(compress.providers);
1560
- if (compress.contextLimitFallback !== void 0 && typeof compress.contextLimitFallback !== "number") {
1561
- errors.push({
1562
- key: "compress.contextLimitFallback",
1563
- expected: "number",
1564
- actual: typeof compress.contextLimitFallback
1565
- });
1566
- }
1567
- if (typeof compress.contextLimitFallback === "number" && compress.contextLimitFallback < 0) {
1568
- errors.push({
1569
- key: "compress.contextLimitFallback",
1570
- expected: "non-negative number (0 disables the fallback)",
1571
- actual: `${compress.contextLimitFallback}`
1572
- });
1573
- }
1574
1524
  const validValues = ["ask", "allow", "deny"];
1575
1525
  if (compress.permission !== void 0 && !validValues.includes(compress.permission)) {
1576
1526
  errors.push({
@@ -1656,22 +1606,13 @@ function validateConfigTypes(config) {
1656
1606
  });
1657
1607
  } else {
1658
1608
  if (gc.batchCleanup.lowThreshold !== void 0) {
1659
- validateBatchThreshold(
1660
- "gc.batchCleanup.lowThreshold",
1661
- gc.batchCleanup.lowThreshold
1662
- );
1609
+ validateBatchThreshold("gc.batchCleanup.lowThreshold", gc.batchCleanup.lowThreshold);
1663
1610
  }
1664
1611
  if (gc.batchCleanup.highThreshold !== void 0) {
1665
- validateBatchThreshold(
1666
- "gc.batchCleanup.highThreshold",
1667
- gc.batchCleanup.highThreshold
1668
- );
1612
+ validateBatchThreshold("gc.batchCleanup.highThreshold", gc.batchCleanup.highThreshold);
1669
1613
  }
1670
1614
  if (gc.batchCleanup.forceThreshold !== void 0) {
1671
- validateBatchThreshold(
1672
- "gc.batchCleanup.forceThreshold",
1673
- gc.batchCleanup.forceThreshold
1674
- );
1615
+ validateBatchThreshold("gc.batchCleanup.forceThreshold", gc.batchCleanup.forceThreshold);
1675
1616
  }
1676
1617
  }
1677
1618
  }
@@ -1761,7 +1702,6 @@ var defaultConfig = {
1761
1702
  summaryBuffer: true,
1762
1703
  maxContextLimit: "80%",
1763
1704
  minContextLimit: "80%",
1764
- contextLimitFallback: 128e3,
1765
1705
  nudgeFrequency: 5,
1766
1706
  minNudgeContextPercent: 5,
1767
1707
  iterationNudgeThreshold: 15,
@@ -1932,7 +1872,6 @@ function mergeCompress(base, override) {
1932
1872
  modelMaxLimits: override.modelMaxLimits ?? base.modelMaxLimits,
1933
1873
  modelMinLimits: override.modelMinLimits ?? base.modelMinLimits,
1934
1874
  providers: mergeProviderOverrides(base.providers, override.providers),
1935
- contextLimitFallback: override.contextLimitFallback ?? base.contextLimitFallback,
1936
1875
  nudgeFrequency: override.nudgeFrequency ?? base.nudgeFrequency,
1937
1876
  minNudgeContextPercent: override.minNudgeContextPercent ?? base.minNudgeContextPercent,
1938
1877
  nudgeGrowthTokens: override.nudgeGrowthTokens,
@@ -1956,8 +1895,7 @@ function mergeCompress(base, override) {
1956
1895
  reasoning: {
1957
1896
  drop: override.reasoning?.drop ?? base.reasoning?.drop ?? DEFAULT_COMPRESS_REASONING.drop,
1958
1897
  threshold: override.reasoning?.threshold ?? base.reasoning?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
1959
- },
1960
- completionReserveTokens: override.completionReserveTokens ?? base.completionReserveTokens
1898
+ }
1961
1899
  };
1962
1900
  }
1963
1901
  function mergeCommands(base, override) {
@@ -1995,9 +1933,10 @@ function deepCloneConfig(config) {
1995
1933
  ...provider,
1996
1934
  ...provider.models ? {
1997
1935
  models: Object.fromEntries(
1998
- Object.entries(provider.models).map(
1999
- ([modelId, model]) => [modelId, { ...model }]
2000
- )
1936
+ Object.entries(provider.models).map(([modelId, model]) => [
1937
+ modelId,
1938
+ { ...model }
1939
+ ])
2001
1940
  )
2002
1941
  } : {}
2003
1942
  }
@@ -2071,14 +2010,8 @@ function mergeLayer(config, data) {
2071
2010
  ],
2072
2011
  compress: mergeCompress(config.compress, data.compress),
2073
2012
  gc: mergeGC(config.gc, data.gc),
2074
- qualityGate: mergeQualityGate(
2075
- config.qualityGate,
2076
- data.qualityGate
2077
- ),
2078
- messageFilters: mergeMessageFilters(
2079
- config.messageFilters,
2080
- data.messageFilters
2081
- )
2013
+ qualityGate: mergeQualityGate(config.qualityGate, data.qualityGate),
2014
+ messageFilters: mergeMessageFilters(config.messageFilters, data.messageFilters)
2082
2015
  };
2083
2016
  }
2084
2017
  function scheduleParseWarning(ctx, title, message) {
@@ -2752,16 +2685,6 @@ function resetOnCompaction(state) {
2752
2685
  nextRef: 1
2753
2686
  };
2754
2687
  }
2755
- function resolveEffectiveContextLimit(state, config) {
2756
- if (typeof state.modelContextLimit === "number" && state.modelContextLimit > 0) {
2757
- return { limit: state.modelContextLimit, source: "model" };
2758
- }
2759
- const fallback = config.compress.contextLimitFallback;
2760
- if (typeof fallback === "number" && fallback > 0) {
2761
- return { limit: fallback, source: "fallback" };
2762
- }
2763
- return void 0;
2764
- }
2765
2688
 
2766
2689
  // lib/state/persistence.ts
2767
2690
  function getDefaultStorageDir() {
@@ -4633,24 +4556,6 @@ var SessionStateRegistry = class {
4633
4556
  hydrateModelLimitsFromClient(client) {
4634
4557
  return this.catalog.hydrateFromClient(client);
4635
4558
  }
4636
- // [FIX #346] The init-time seed (above) is fire-and-forget and races
4637
- // server readiness: in headless spawn+resume mode the provider-config
4638
- // call can fail before the server is up, leaving the catalog empty for
4639
- // the process's lifetime. During a request the server is guaranteed up
4640
- // (we are inside its pipeline), so on a catalog miss we retry hydration
4641
- // once per process before giving up (the fallback limit then applies).
4642
- // The in-flight promise (not a boolean) lets concurrent callers await the
4643
- // same hydration instead of skipping it.
4644
- lazyHydration;
4645
- async hydrateAndResolve(client, providerId, modelId) {
4646
- const existing = this.catalog.resolve(providerId, modelId);
4647
- if (existing !== void 0) {
4648
- return existing;
4649
- }
4650
- this.lazyHydration ??= this.catalog.hydrateFromClient(client);
4651
- await this.lazyHydration;
4652
- return this.catalog.resolve(providerId, modelId);
4653
- }
4654
4559
  get(sessionId) {
4655
4560
  return this.states.get(sessionId);
4656
4561
  }
@@ -4744,8 +4649,7 @@ function createSessionState() {
4744
4649
  modelID: void 0,
4745
4650
  systemPromptTokens: void 0,
4746
4651
  storageDir: void 0,
4747
- qualityGateRetryPending: false,
4748
- noContextLimitWarned: false
4652
+ qualityGateRetryPending: false
4749
4653
  };
4750
4654
  }
4751
4655
  function resetSessionState(state) {
@@ -4788,7 +4692,6 @@ function resetSessionState(state) {
4788
4692
  state.systemPromptTokens = void 0;
4789
4693
  state.storageDir = void 0;
4790
4694
  state.qualityGateRetryPending = false;
4791
- state.noContextLimitWarned = false;
4792
4695
  }
4793
4696
  async function ensureSessionInitialized(client, state, sessionId, logger, messages, config, projectDir) {
4794
4697
  if (state.sessionId === sessionId) {
@@ -6804,7 +6707,6 @@ function getModelInfo(messages) {
6804
6707
  };
6805
6708
  }
6806
6709
  function resolveContextTokenLimit(config, state, providerId, modelId, threshold) {
6807
- const effectiveLimit = resolveEffectiveContextLimit(state, config);
6808
6710
  const parseLimitValue = (limit) => {
6809
6711
  if (limit === void 0) {
6810
6712
  return void 0;
@@ -6812,7 +6714,7 @@ function resolveContextTokenLimit(config, state, providerId, modelId, threshold)
6812
6714
  if (typeof limit === "number") {
6813
6715
  return limit;
6814
6716
  }
6815
- if (!limit.endsWith("%") || effectiveLimit === void 0) {
6717
+ if (!limit.endsWith("%") || state.modelContextLimit === void 0) {
6816
6718
  return void 0;
6817
6719
  }
6818
6720
  const parsedPercent = parseFloat(limit.slice(0, -1));
@@ -6821,7 +6723,7 @@ function resolveContextTokenLimit(config, state, providerId, modelId, threshold)
6821
6723
  }
6822
6724
  const roundedPercent = Math.round(parsedPercent);
6823
6725
  const clampedPercent = Math.max(0, Math.min(100, roundedPercent));
6824
- return Math.round(clampedPercent / 100 * effectiveLimit.limit);
6726
+ return Math.round(clampedPercent / 100 * state.modelContextLimit);
6825
6727
  };
6826
6728
  if (threshold === "max") {
6827
6729
  const nestedLimit = resolveCompressOverrides(config, providerId, modelId).maxContextLimit;
@@ -6872,12 +6774,11 @@ function isContextOverLimits(config, state, providerId, modelId, messages) {
6872
6774
  if (!overMaxLimit) break;
6873
6775
  }
6874
6776
  }
6875
- const effectiveLimit = resolveEffectiveContextLimit(state, config);
6876
6777
  return {
6877
6778
  overMaxLimit,
6878
6779
  overMinLimit,
6879
6780
  currentTokens,
6880
- modelContextLimit: effectiveLimit?.limit
6781
+ modelContextLimit: state.modelContextLimit
6881
6782
  };
6882
6783
  }
6883
6784
  ensureBuiltinTriggerPolicyRegistered();
@@ -7183,10 +7084,13 @@ function buildCompressibleRanges(messages, state, protectedTools = [], protected
7183
7084
  if (!ref) continue;
7184
7085
  const rn = parseInt(ref.slice(1), 10);
7185
7086
  if ((protectedTools.length > 0 || protectedFilePatterns.length > 0) && messageContainsProtectedTool(msg, protectedTools, protectedFilePatterns)) {
7186
- const tokens2 = Math.round(countMessageCharacters(msg) / 4);
7087
+ let tokens2 = 0;
7187
7088
  const tools = /* @__PURE__ */ new Set();
7188
7089
  for (const part of msg.parts || []) {
7189
- if (part.type !== "text" && part.type !== "reasoning") {
7090
+ if (part.type === "text" && typeof part.text === "string") {
7091
+ tokens2 += Math.round(part.text.length / 4);
7092
+ } else if (part.type !== "text" && part.type !== "reasoning") {
7093
+ tokens2 += Math.round(JSON.stringify(part).length / 4);
7190
7094
  const toolName = part?.tool;
7191
7095
  const callID = part?.callID;
7192
7096
  if (toolName && callID) {
@@ -7207,13 +7111,15 @@ function buildCompressibleRanges(messages, state, protectedTools = [], protected
7207
7111
  protectedMsgInfo.push({ ref, refNum: rn, tokens: tokens2, tools: [...tools] });
7208
7112
  continue;
7209
7113
  }
7210
- const tokens = Math.round(countMessageCharacters(msg) / 4);
7114
+ let tokens = 0;
7211
7115
  let isTool = false;
7212
7116
  let hasMeaningfulPart = false;
7213
7117
  for (const part of msg.parts || []) {
7214
7118
  if (part.type === "text" && typeof part.text === "string") {
7119
+ tokens += Math.round(part.text.length / 4);
7215
7120
  if (part.text.trim().length > 0) hasMeaningfulPart = true;
7216
7121
  } else if (part.type !== "text" && part.type !== "reasoning") {
7122
+ tokens += Math.round(JSON.stringify(part).length / 4);
7217
7123
  isTool = true;
7218
7124
  hasMeaningfulPart = true;
7219
7125
  }
@@ -8954,9 +8860,8 @@ function createDecompressTool(factoryCtx) {
8954
8860
  async execute(args, toolCtx) {
8955
8861
  const ctx = resolveToolContext(factoryCtx, toolCtx.sessionID);
8956
8862
  const { rawMessages } = await prepareDecompressSession(ctx, toolCtx);
8957
- const effectiveLimitBefore = resolveEffectiveContextLimit(ctx.state, ctx.config);
8958
- const contextUsageBefore = effectiveLimitBefore ? Math.round(
8959
- getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitBefore.limit * 100
8863
+ const contextUsageBefore = ctx.state.modelContextLimit ? Math.round(
8864
+ getCurrentTokenUsage(ctx.state, rawMessages) / ctx.state.modelContextLimit * 100
8960
8865
  ) : void 0;
8961
8866
  const resolved = resolveTargets(args, ctx.state, rawMessages, ctx.logger);
8962
8867
  if (!resolved.ok) {
@@ -9020,9 +8925,8 @@ function createDecompressTool(factoryCtx) {
9020
8925
  0,
9021
8926
  ctx.state.stats.totalPruneTokens - restoredTokens
9022
8927
  );
9023
- const effectiveLimitAfter = resolveEffectiveContextLimit(ctx.state, ctx.config);
9024
- const contextUsageAfter = effectiveLimitAfter ? Math.round(
9025
- getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitAfter.limit * 100
8928
+ const contextUsageAfter = ctx.state.modelContextLimit ? Math.round(
8929
+ getCurrentTokenUsage(ctx.state, rawMessages) / ctx.state.modelContextLimit * 100
9026
8930
  ) : void 0;
9027
8931
  await finalizeDecompressSession(ctx);
9028
8932
  const restoredContentPreview = buildRestoredContentPreview(
@@ -9679,7 +9583,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
9679
9583
  import { join as join4 } from "path";
9680
9584
  import { existsSync as existsSync4 } from "fs";
9681
9585
  import { homedir as homedir3 } from "os";
9682
- var LOG_VERSION = true ? "1.16.0-pr.360.128" : "dev";
9586
+ var LOG_VERSION = true ? "1.16.0-pr.365.125" : "dev";
9683
9587
  var LEVEL_RANK = {
9684
9588
  debug: 10,
9685
9589
  info: 20,
@@ -10503,8 +10407,6 @@ var MIN_OUTPUT_TOKENS = 1e3;
10503
10407
  var KEEP_PREFIX_CHARS = 2e3;
10504
10408
  var KEEP_SUFFIX_CHARS = 2e3;
10505
10409
  var PROTECT_RECENT_MESSAGES = 3;
10506
- var OUTPUT_RESERVE_TOKENS = 16384;
10507
- var overheadErrorLogged = /* @__PURE__ */ new Set();
10508
10410
  function parseGcThreshold(threshold, modelContextLimit) {
10509
10411
  if (typeof threshold === "number") return threshold;
10510
10412
  const str = threshold ?? "100%";
@@ -10513,26 +10415,10 @@ function parseGcThreshold(threshold, modelContextLimit) {
10513
10415
  return modelContextLimit;
10514
10416
  }
10515
10417
  function truncateLargeToolOutputs(state, config, logger, messages) {
10516
- const effective = resolveEffectiveContextLimit(state, config);
10517
- if (!effective) return;
10418
+ if (!state.modelContextLimit) return;
10518
10419
  const currentTokens = getCurrentTokenUsage(state, messages);
10519
10420
  if (currentTokens === 0) return;
10520
- const configuredThreshold = parseGcThreshold(config.gc?.majorGcThresholdPercent, effective.limit);
10521
- const overhead = (state.systemPromptTokens ?? 0) + OUTPUT_RESERVE_TOKENS;
10522
- const threshold = Math.min(configuredThreshold, effective.limit - overhead);
10523
- if (threshold <= 0) {
10524
- const sessionKey = state.sessionId ?? "unknown";
10525
- if (!overheadErrorLogged.has(sessionKey)) {
10526
- overheadErrorLogged.add(sessionKey);
10527
- logger.error("ACP: model context window too small to fit overhead", {
10528
- session: state.sessionId,
10529
- limit: effective.limit,
10530
- contextLimitSource: effective.source,
10531
- overhead
10532
- });
10533
- }
10534
- return;
10535
- }
10421
+ const threshold = parseGcThreshold(config.gc?.majorGcThresholdPercent, state.modelContextLimit);
10536
10422
  if (currentTokens < threshold) return;
10537
10423
  const protectedIndex = messages.length - PROTECT_RECENT_MESSAGES;
10538
10424
  const candidates = [];
@@ -10577,160 +10463,11 @@ function truncateLargeToolOutputs(state, config, logger, messages) {
10577
10463
  truncatedCount,
10578
10464
  estimatedSavedTokens: Math.round(savedTokens),
10579
10465
  currentTokens,
10580
- threshold,
10581
- contextLimit: effective.limit,
10582
- contextLimitSource: effective.source
10466
+ threshold
10583
10467
  });
10584
10468
  }
10585
10469
  }
10586
10470
 
10587
- // lib/messages/enforce-budget.ts
10588
- var DEFAULT_COMPLETION_RESERVE_TOKENS = 32768;
10589
- var TRUNCATION_MARKER2 = "[truncated for context space";
10590
- var KEEP_PREFIX_CHARS2 = 2e3;
10591
- var KEEP_SUFFIX_CHARS2 = 2e3;
10592
- var PROTECT_RECENT_MESSAGES2 = 3;
10593
- var MIN_CLEAR_TOKENS = 200;
10594
- function resolveContextWindow(state) {
10595
- if (typeof state.modelContextLimit === "number" && state.modelContextLimit > 0) {
10596
- return state.modelContextLimit;
10597
- }
10598
- return void 0;
10599
- }
10600
- function estimateWireTokens(state, messages) {
10601
- const base = getCurrentTokenUsage(state, messages);
10602
- if (base > 0) {
10603
- let baseAssistant = -1;
10604
- for (let i = messages.length - 1; i >= 0; i--) {
10605
- if (messages[i].info.role !== "assistant") continue;
10606
- const tokens = messages[i].info.tokens;
10607
- if ((tokens?.input || 0) <= 0 && (tokens?.output || 0) <= 0) continue;
10608
- baseAssistant = i;
10609
- break;
10610
- }
10611
- if (baseAssistant >= 0) {
10612
- let additions = 0;
10613
- for (let i = baseAssistant + 1; i < messages.length; i++) {
10614
- additions += countAllMessageTokens(messages[i]);
10615
- }
10616
- return base + additions;
10617
- }
10618
- }
10619
- let total = 0;
10620
- for (const m of messages) total += countAllMessageTokens(m);
10621
- return total + (state.systemPromptTokens ?? 0);
10622
- }
10623
- function enforceContextBudget(state, config, logger, messages) {
10624
- const window = resolveContextWindow(state);
10625
- if (window === void 0) return void 0;
10626
- const configuredReserve = config.compress?.completionReserveTokens;
10627
- const reserve = typeof configuredReserve === "number" && Number.isFinite(configuredReserve) && configuredReserve >= 0 ? configuredReserve : DEFAULT_COMPLETION_RESERVE_TOKENS;
10628
- const budget = window - reserve;
10629
- if (budget <= 0) return void 0;
10630
- const estimatedTokens = estimateWireTokens(state, messages);
10631
- if (estimatedTokens <= budget) {
10632
- return {
10633
- applied: false,
10634
- window,
10635
- reserve,
10636
- budget,
10637
- estimatedTokens,
10638
- finalEstimate: estimatedTokens,
10639
- truncatedCount: 0,
10640
- clearedCount: 0
10641
- };
10642
- }
10643
- const protectedIndex = messages.length - PROTECT_RECENT_MESSAGES2;
10644
- const protectedTools = new Set(config.compress?.protectedTools ?? []);
10645
- const candidates = [];
10646
- for (let mi = 0; mi < protectedIndex; mi++) {
10647
- if (mi === 0 && messages[mi].info.role === "user") continue;
10648
- const msg = messages[mi];
10649
- const parts = Array.isArray(msg.parts) ? msg.parts : [];
10650
- for (const part of parts) {
10651
- if (part?.type !== "tool") continue;
10652
- if (part.state?.status !== "completed") continue;
10653
- if (part.tool === "compress") continue;
10654
- if (protectedTools.has(part.tool)) continue;
10655
- const content = extractCompletedToolOutput(part);
10656
- if (content === void 0) continue;
10657
- if (content === COMPACTED_TOOL_OUTPUT_PLACEHOLDER) continue;
10658
- const tokens = countTokens2(content);
10659
- if (tokens <= 0) continue;
10660
- candidates.push({ part, content, tokens, index: mi });
10661
- }
10662
- }
10663
- let saved = 0;
10664
- let truncatedCount = 0;
10665
- let clearedCount = 0;
10666
- const truncatable = candidates.filter(
10667
- (c) => !c.content.includes(TRUNCATION_MARKER2) && c.content.length > KEEP_PREFIX_CHARS2 + KEEP_SUFFIX_CHARS2
10668
- ).sort((a, b) => b.tokens - a.tokens);
10669
- for (const c of truncatable) {
10670
- if (estimatedTokens - saved <= budget) break;
10671
- const prefix = c.content.slice(0, KEEP_PREFIX_CHARS2);
10672
- const suffix = c.content.slice(-KEEP_SUFFIX_CHARS2);
10673
- const truncated = prefix + `
10674
-
10675
- ...${TRUNCATION_MARKER2} \u2014 original ~${c.tokens} tokens]...
10676
-
10677
- ` + suffix;
10678
- if (truncated.length >= c.content.length) continue;
10679
- c.part.state.output = truncated;
10680
- saved += c.tokens - countTokens2(truncated);
10681
- truncatedCount++;
10682
- }
10683
- if (estimatedTokens - saved > budget) {
10684
- const clearable = candidates.filter((c) => {
10685
- const out = extractCompletedToolOutput(c.part);
10686
- return out !== void 0 && out !== COMPACTED_TOOL_OUTPUT_PLACEHOLDER && countTokens2(out) > MIN_CLEAR_TOKENS;
10687
- }).sort((a, b) => a.index - b.index);
10688
- for (const c of clearable) {
10689
- if (estimatedTokens - saved <= budget) break;
10690
- const current = extractCompletedToolOutput(c.part);
10691
- if (current === void 0 || current === COMPACTED_TOOL_OUTPUT_PLACEHOLDER) continue;
10692
- c.part.state.output = COMPACTED_TOOL_OUTPUT_PLACEHOLDER;
10693
- saved += countTokens2(current) - countTokens2(COMPACTED_TOOL_OUTPUT_PLACEHOLDER);
10694
- clearedCount++;
10695
- }
10696
- }
10697
- const finalEstimate = Math.max(0, estimatedTokens - saved);
10698
- if (truncatedCount > 0 || clearedCount > 0) {
10699
- logger.warn("Context budget guard: pruned tool outputs to fit the request", {
10700
- session: state.sessionId,
10701
- estimatedTokens: Math.round(estimatedTokens),
10702
- budget,
10703
- window,
10704
- reserve,
10705
- truncatedCount,
10706
- clearedCount,
10707
- estimatedSavedTokens: Math.round(saved),
10708
- finalEstimate: Math.round(finalEstimate)
10709
- });
10710
- }
10711
- if (finalEstimate > budget) {
10712
- logger.warn(
10713
- "Context budget guard: still over budget after pruning all compressible tool outputs; the request may be rejected by the model",
10714
- {
10715
- session: state.sessionId,
10716
- finalEstimate: Math.round(finalEstimate),
10717
- budget,
10718
- window
10719
- }
10720
- );
10721
- }
10722
- return {
10723
- applied: truncatedCount > 0 || clearedCount > 0,
10724
- window,
10725
- reserve,
10726
- budget,
10727
- estimatedTokens,
10728
- finalEstimate,
10729
- truncatedCount,
10730
- clearedCount
10731
- };
10732
- }
10733
-
10734
10471
  // lib/commands/context.ts
10735
10472
  function analyzeTokens(state, messages) {
10736
10473
  const breakdown = {
@@ -11687,12 +11424,11 @@ function runBatchCleanup(state, config, logger, messages) {
11687
11424
  mergedCount: 0,
11688
11425
  savedTokens: 0
11689
11426
  };
11690
- const effective = resolveEffectiveContextLimit(state, config);
11691
- if (!effective) {
11427
+ if (!state.modelContextLimit || state.modelContextLimit <= 0) {
11692
11428
  return noop;
11693
11429
  }
11694
11430
  const currentTokens = getCurrentTokenUsage(state, messages);
11695
- if (currentTokens < effective.limit) {
11431
+ if (currentTokens < state.modelContextLimit) {
11696
11432
  return noop;
11697
11433
  }
11698
11434
  const maxMergedLength = config.gc.maxOldGenSummaryLength;
@@ -11709,8 +11445,7 @@ function runBatchCleanup(state, config, logger, messages) {
11709
11445
  mergedCount: result.mergedCount,
11710
11446
  savedTokens: result.savedTokens,
11711
11447
  currentTokens,
11712
- contextLimit: effective.limit,
11713
- contextLimitSource: effective.source
11448
+ contextLimit: state.modelContextLimit
11714
11449
  });
11715
11450
  return {
11716
11451
  tier: 3,
@@ -11744,6 +11479,11 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
11744
11479
  input.model?.limit?.context
11745
11480
  );
11746
11481
  const state = input.sessionID ? registry4.get(input.sessionID) : void 0;
11482
+ if (state && input.model?.limit?.context) {
11483
+ state.modelContextLimit = input.model.limit.context;
11484
+ state.modelProviderID = input.model?.providerID;
11485
+ state.modelID = input.model?.id;
11486
+ }
11747
11487
  if (!state || state.isSubAgent && !config.allowSubAgents) {
11748
11488
  return;
11749
11489
  }
@@ -11752,23 +11492,6 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
11752
11492
  logger.info("Skipping DCP system prompt injection for internal agent");
11753
11493
  return;
11754
11494
  }
11755
- if (input.model?.limit?.context) {
11756
- const limit = input.model.limit.context;
11757
- const providerID = input.model?.providerID;
11758
- const modelID = input.model?.id;
11759
- const changed = state.modelContextLimit !== limit || providerID !== void 0 && state.modelProviderID !== providerID || modelID !== void 0 && state.modelID !== modelID;
11760
- state.modelContextLimit = limit;
11761
- if (providerID !== void 0) {
11762
- state.modelProviderID = providerID;
11763
- }
11764
- if (modelID !== void 0) {
11765
- state.modelID = modelID;
11766
- }
11767
- if (changed) {
11768
- saveSessionState(state, logger).catch(() => {
11769
- });
11770
- }
11771
- }
11772
11495
  const effectivePermission = compressPermission(state, config);
11773
11496
  if (effectivePermission === "deny") {
11774
11497
  return;
@@ -11813,17 +11536,10 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
11813
11536
  config
11814
11537
  );
11815
11538
  const requestModel = lastUserMessage.info.model;
11816
- let requestModelLimit = registry4.resolveModelLimit(
11539
+ const requestModelLimit = registry4.resolveModelLimit(
11817
11540
  requestModel?.providerID,
11818
11541
  requestModel?.modelID
11819
11542
  );
11820
- if (requestModelLimit === void 0 && requestModel?.providerID && requestModel?.modelID) {
11821
- requestModelLimit = await registry4.hydrateAndResolve(
11822
- client,
11823
- requestModel.providerID,
11824
- requestModel.modelID
11825
- );
11826
- }
11827
11543
  const prevModelID = state.modelID;
11828
11544
  if (requestModelLimit !== void 0) {
11829
11545
  state.modelContextLimit = requestModelLimit;
@@ -11853,16 +11569,6 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
11853
11569
  });
11854
11570
  }
11855
11571
  await updatePerTurnState(state, logger, messages);
11856
- if (state.modelContextLimit === void 0 && !state.noContextLimitWarned && requestModel?.providerID && requestModel?.modelID && registry4.resolveModelLimit(requestModel.providerID, requestModel.modelID) === void 0) {
11857
- state.noContextLimitWarned = true;
11858
- logger.warn(
11859
- 'Model reports no context window and the catalog has no entry for it; all percentage thresholds (min/max/emergency, GC) and the context-budget guard are disabled. Set the model limit in opencode.json (e.g. "limit": {"context": 262144, "output": 16384}) to enable them (also fixes the 32000 max_tokens fallback); an absolute compress.maxContextLimit in acp.jsonc only enables proactive nudges, not the guard.',
11860
- {
11861
- session: state.sessionId,
11862
- model: `${requestModel.providerID}/${requestModel.modelID}`
11863
- }
11864
- );
11865
- }
11866
11572
  }
11867
11573
  syncCompressPermissionState(state, config, hostPermissions, output.messages);
11868
11574
  if (state.isSubAgent && !config.allowSubAgents) {
@@ -11888,11 +11594,10 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
11888
11594
  }
11889
11595
  }
11890
11596
  ensureBuiltinFiltersRegistered();
11891
- const effectiveLimit = resolveEffectiveContextLimit(state, config);
11892
11597
  applyMessageFilters(output.messages, config.messageFilters, logger, {
11893
11598
  sessionId: state.sessionId ?? "",
11894
11599
  isSubAgent: state.isSubAgent,
11895
- modelContextLimit: effectiveLimit?.limit
11600
+ modelContextLimit: state.modelContextLimit
11896
11601
  });
11897
11602
  cacheSystemPromptTokens(state, output.messages);
11898
11603
  assignMessageRefs(state, output.messages);
@@ -11912,7 +11617,6 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
11912
11617
  const prePruneTokens = getCurrentTokenUsage(state, output.messages);
11913
11618
  prune(state, logger, config, output.messages);
11914
11619
  truncateLargeToolOutputs(state, config, logger, output.messages);
11915
- enforceContextBudget(state, config, logger, output.messages);
11916
11620
  hideConsumedCompressCalls(state, output.messages);
11917
11621
  assignMessageRefs(state, output.messages);
11918
11622
  const compressionPriorities = buildPriorityMap(config, state, output.messages);
@@ -11944,31 +11648,14 @@ ${text}`);
11944
11648
  stripStaleMetadata(output.messages);
11945
11649
  dropEmptyMessages(output.messages);
11946
11650
  const postTokens = getCurrentTokenUsage(state, output.messages);
11947
- if (postTokens !== void 0 && effectiveLimit) {
11948
- const budget = effectiveLimit.limit - (state.systemPromptTokens ?? 0) - OUTPUT_RESERVE_TOKENS;
11949
- if (postTokens > budget) {
11950
- logger.error(
11951
- "ACP hard guard: context exceeds model budget after in-flight reduction",
11952
- {
11953
- session: state.sessionId,
11954
- postTokens,
11955
- budget,
11956
- contextLimit: effectiveLimit.limit,
11957
- contextLimitSource: effectiveLimit.source,
11958
- hint: "request will likely be rejected; run /compact or start a new session"
11959
- }
11960
- );
11961
- }
11962
- }
11963
11651
  logger.info("Chat transform complete", {
11964
11652
  session: state.sessionId,
11965
11653
  model: state.modelID,
11966
11654
  messages: output.messages.length,
11967
11655
  prePruneTokens,
11968
11656
  postTokens,
11969
- contextLimit: effectiveLimit?.limit,
11970
- contextLimitSource: effectiveLimit?.source,
11971
- usagePct: postTokens !== void 0 && effectiveLimit ? `${(postTokens / effectiveLimit.limit * 100).toFixed(1)}%` : void 0,
11657
+ contextLimit: state.modelContextLimit,
11658
+ usagePct: postTokens !== void 0 && state.modelContextLimit ? `${(postTokens / state.modelContextLimit * 100).toFixed(1)}%` : void 0,
11972
11659
  nudged: state.nudges.shouldInjectThisTurn
11973
11660
  });
11974
11661
  if (state.sessionId) {
@@ -12000,7 +11687,12 @@ function createCommandExecuteHandler(client, registry4, logger, config, workingD
12000
11687
  path: { id: input.sessionID }
12001
11688
  });
12002
11689
  const messages = filterMessages(messagesResponse.data || messagesResponse);
12003
- const state = await registry4.getOrCreate(client, input.sessionID, messages, config);
11690
+ const state = await registry4.getOrCreate(
11691
+ client,
11692
+ input.sessionID,
11693
+ messages,
11694
+ config
11695
+ );
12004
11696
  syncCompressPermissionState(state, config, hostPermissions, messages);
12005
11697
  const commandCtx = {
12006
11698
  client,
@@ -12014,7 +11706,7 @@ function createCommandExecuteHandler(client, registry4, logger, config, workingD
12014
11706
  const sub = input.arguments?.trim().toLowerCase();
12015
11707
  if (sub === "stats" || sub === "status" || sub === "") {
12016
11708
  await handleStatsCommand(commandCtx);
12017
- return;
11709
+ throw new Error("__DCP_CONTEXT_HANDLED__");
12018
11710
  }
12019
11711
  if (sub === "export" || sub.startsWith("export ")) {
12020
11712
  const exportArgs = input.arguments?.trim().slice("export".length).trim() || "";
@@ -12022,10 +11714,17 @@ function createCommandExecuteHandler(client, registry4, logger, config, workingD
12022
11714
  throw new Error("__DCP_CONTEXT_HANDLED__");
12023
11715
  }
12024
11716
  if (sub === "help") {
12025
- await sendIgnoredMessage(client, input.sessionID, buildHelpText(), {}, logger);
11717
+ await sendIgnoredMessage(
11718
+ client,
11719
+ input.sessionID,
11720
+ buildHelpText(),
11721
+ {},
11722
+ logger
11723
+ );
12026
11724
  throw new Error("__DCP_CONTEXT_HANDLED__");
12027
11725
  }
12028
11726
  await handleContextCommand(commandCtx);
11727
+ throw new Error("__DCP_CONTEXT_HANDLED__");
12029
11728
  }
12030
11729
  };
12031
11730
  }
@@ -12096,7 +11795,9 @@ function createEventHandler(registry4, logger) {
12096
11795
  return;
12097
11796
  }
12098
11797
  if (typeof part.callID === "string" && typeof part.messageID === "string") {
12099
- timing.startsByCallId.delete(buildCompressionTimingKey(part.messageID, part.callID));
11798
+ timing.startsByCallId.delete(
11799
+ buildCompressionTimingKey(part.messageID, part.callID)
11800
+ );
12100
11801
  }
12101
11802
  };
12102
11803
  }
@@ -12373,7 +12074,7 @@ var server = (async (ctx) => {
12373
12074
  }
12374
12075
  const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
12375
12076
  logger.info("ACP plugin initialized", {
12376
- version: true ? "1.16.0-pr.360.128" : "dev",
12077
+ version: true ? "1.16.0-pr.365.125" : "dev",
12377
12078
  workspace: ctx.directory,
12378
12079
  logLevel: logger.level,
12379
12080
  debug: config.debug,