opencode-acp 1.14.19-pr.314.26 → 1.14.19-pr.315.27

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AA2BjD,QAAA,MAAM,MAAM,EAAE,MAwHK,CAAA;AAEnB,eAAe,MAAM,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AA2BjD,QAAA,MAAM,MAAM,EAAE,MA6IK,CAAA;AAEnB,eAAe,MAAM,CAAA"}
package/dist/index.js CHANGED
@@ -2418,7 +2418,9 @@ async function saveSessionState(sessionState, logger, sessionName) {
2418
2418
  nextRef: sessionState.messageIds.nextRef
2419
2419
  },
2420
2420
  lastCompaction: sessionState.lastCompaction,
2421
- modelContextLimit: sessionState.modelContextLimit
2421
+ modelContextLimit: sessionState.modelContextLimit,
2422
+ modelProviderID: sessionState.modelProviderID,
2423
+ modelID: sessionState.modelID
2422
2424
  };
2423
2425
  await writePersistedSessionState(sessionState.sessionId, state, logger);
2424
2426
  }
@@ -2949,6 +2951,51 @@ function applyPendingCompressionDurations(state) {
2949
2951
  return updates;
2950
2952
  }
2951
2953
 
2954
+ // lib/state/model-limits.ts
2955
+ function createModelLimitCatalog() {
2956
+ const modelLimits = /* @__PURE__ */ new Map();
2957
+ return {
2958
+ record(providerId, modelId, limit) {
2959
+ if (!providerId || !modelId || typeof limit !== "number" || limit <= 0) return;
2960
+ modelLimits.set(`${providerId}/${modelId}`, limit);
2961
+ },
2962
+ resolve(providerId, modelId) {
2963
+ if (!providerId || !modelId) return void 0;
2964
+ return modelLimits.get(`${providerId}/${modelId}`);
2965
+ },
2966
+ /**
2967
+ * Best-effort one-time seed from the host's provider catalog
2968
+ * (`client.config.providers()` → GET /config/providers). Never throws;
2969
+ * returns the number of model-limit entries recorded.
2970
+ */
2971
+ async hydrateFromClient(client) {
2972
+ try {
2973
+ const config = client;
2974
+ const result = await config.config?.providers?.();
2975
+ const payload = result;
2976
+ const providers = payload?.data?.providers;
2977
+ if (!Array.isArray(providers)) return 0;
2978
+ let recorded = 0;
2979
+ for (const provider of providers) {
2980
+ const { id, models } = provider ?? {};
2981
+ if (typeof id !== "string" || !models) continue;
2982
+ for (const [modelId, model] of Object.entries(models)) {
2983
+ const limit = model?.limit;
2984
+ const context = limit?.context;
2985
+ if (typeof context === "number" && context > 0) {
2986
+ modelLimits.set(`${id}/${modelId}`, context);
2987
+ recorded++;
2988
+ }
2989
+ }
2990
+ }
2991
+ return recorded;
2992
+ } catch {
2993
+ return 0;
2994
+ }
2995
+ }
2996
+ };
2997
+ }
2998
+
2952
2999
  // lib/compress/search.ts
2953
3000
  import { tool } from "@opencode-ai/plugin";
2954
3001
  async function fetchSessionMessages(client, sessionId) {
@@ -4140,56 +4187,25 @@ var SessionStateRegistry = class {
4140
4187
  startsByCallId: /* @__PURE__ */ new Map(),
4141
4188
  pendingByCallId: /* @__PURE__ */ new Map()
4142
4189
  };
4143
- // [FIX #312] Catalog of per-model context limits, keyed `${providerID}/${modelID}`.
4144
- // Within one LLM request the host fires experimental.chat.messages.transform
4145
- // BEFORE experimental.chat.system.transform (sst/opencode: session/prompt.ts
4146
- // triggers messages.transform, then llm/request.ts triggers system.transform
4147
- // during handle.process). state.modelContextLimit is written only by the
4148
- // system hook, so on the first request after a model switch every percentage
4149
- // threshold (emergencyThresholdPercent, min/maxContextLimit "%", adaptive
4150
- // nudge growth, GC tiers) is still computed against the PREVIOUS model's
4151
- // limit. This catalog lets the messages hook reconcile against the model
4152
- // named on the request's user message instead of waiting one turn.
4153
- // Entries are recorded live by the system hook every request and seeded once
4154
- // at plugin init from the host's /config/providers catalog.
4155
- modelLimits = /* @__PURE__ */ new Map();
4190
+ // [FIX #312] Model-limit catalog (full rationale in ./model-limits.ts):
4191
+ // lets the messages hook reconcile state.modelContextLimit against the
4192
+ // model named on the request's user message instead of waiting one turn
4193
+ // for the system hook. Shared implementation — the test registry stub
4194
+ // composes the same factory.
4195
+ catalog = createModelLimitCatalog();
4156
4196
  recordModelLimit(providerId, modelId, limit) {
4157
- if (!providerId || !modelId || typeof limit !== "number" || limit <= 0) return;
4158
- this.modelLimits.set(`${providerId}/${modelId}`, limit);
4197
+ this.catalog.record(providerId, modelId, limit);
4159
4198
  }
4160
4199
  resolveModelLimit(providerId, modelId) {
4161
- if (!providerId || !modelId) return void 0;
4162
- return this.modelLimits.get(`${providerId}/${modelId}`);
4200
+ return this.catalog.resolve(providerId, modelId);
4163
4201
  }
4164
4202
  /**
4165
4203
  * Best-effort one-time seed from the host's provider catalog
4166
4204
  * (`client.config.providers()` → GET /config/providers). Never throws;
4167
4205
  * returns the number of model-limit entries recorded.
4168
4206
  */
4169
- async hydrateModelLimitsFromClient(client) {
4170
- try {
4171
- const config = client;
4172
- const result = await config.config?.providers?.();
4173
- const payload = result;
4174
- const providers = payload?.data?.providers;
4175
- if (!Array.isArray(providers)) return 0;
4176
- let recorded = 0;
4177
- for (const provider of providers) {
4178
- const { id, models } = provider ?? {};
4179
- if (typeof id !== "string" || !models) continue;
4180
- for (const [modelId, model] of Object.entries(models)) {
4181
- const limit = model?.limit;
4182
- const context = limit?.context;
4183
- if (typeof context === "number" && context > 0) {
4184
- this.modelLimits.set(`${id}/${modelId}`, context);
4185
- recorded++;
4186
- }
4187
- }
4188
- }
4189
- return recorded;
4190
- } catch {
4191
- return 0;
4192
- }
4207
+ hydrateModelLimitsFromClient(client) {
4208
+ return this.catalog.hydrateFromClient(client);
4193
4209
  }
4194
4210
  get(sessionId) {
4195
4211
  return this.states.get(sessionId);
@@ -4279,6 +4295,8 @@ function createSessionState() {
4279
4295
  lastCompaction: 0,
4280
4296
  currentTurn: 0,
4281
4297
  modelContextLimit: void 0,
4298
+ modelProviderID: void 0,
4299
+ modelID: void 0,
4282
4300
  systemPromptTokens: void 0,
4283
4301
  qualityGateRetryPending: false
4284
4302
  };
@@ -4318,6 +4336,8 @@ function resetSessionState(state) {
4318
4336
  state.lastCompaction = 0;
4319
4337
  state.currentTurn = 0;
4320
4338
  state.modelContextLimit = void 0;
4339
+ state.modelProviderID = void 0;
4340
+ state.modelID = void 0;
4321
4341
  state.systemPromptTokens = void 0;
4322
4342
  state.qualityGateRetryPending = false;
4323
4343
  }
@@ -4392,6 +4412,8 @@ async function ensureSessionInitialized(client, state, sessionId, logger, messag
4392
4412
  }
4393
4413
  if (typeof persisted.modelContextLimit === "number" && persisted.modelContextLimit > 0) {
4394
4414
  state.modelContextLimit = persisted.modelContextLimit;
4415
+ state.modelProviderID = persisted.modelProviderID;
4416
+ state.modelID = persisted.modelID;
4395
4417
  }
4396
4418
  const applied = applyPendingCompressionDurations(state);
4397
4419
  if (applied > 0) {
@@ -9000,7 +9022,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
9000
9022
  import { join as join3 } from "path";
9001
9023
  import { existsSync as existsSync3 } from "fs";
9002
9024
  import { homedir as homedir3 } from "os";
9003
- var LOG_VERSION = true ? "1.14.19-pr.314.26" : "dev";
9025
+ var LOG_VERSION = true ? "1.14.19-pr.315.27" : "dev";
9004
9026
  var Logger = class {
9005
9027
  logDir;
9006
9028
  enabled;
@@ -10575,6 +10597,8 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
10575
10597
  const state = input.sessionID ? registry4.get(input.sessionID) : void 0;
10576
10598
  if (state && input.model?.limit?.context) {
10577
10599
  state.modelContextLimit = input.model.limit.context;
10600
+ state.modelProviderID = input.model?.providerID;
10601
+ state.modelID = input.model?.id;
10578
10602
  }
10579
10603
  if (!state || state.isSubAgent && !config.allowSubAgents) {
10580
10604
  return;
@@ -10634,6 +10658,22 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
10634
10658
  );
10635
10659
  if (requestModelLimit !== void 0) {
10636
10660
  state.modelContextLimit = requestModelLimit;
10661
+ state.modelProviderID = requestModel?.providerID;
10662
+ state.modelID = requestModel?.modelID;
10663
+ } else if (
10664
+ // [FIX #312 fallback] Catalog miss: we cannot CORRECT the
10665
+ // limit, but we can tell when it belongs to a DIFFERENT model.
10666
+ // Invalidate instead of letting every percentage threshold
10667
+ // below run against the wrong window (#312's false positive).
10668
+ // States persisted before this identity pair existed carry no
10669
+ // identity and are treated as stale for the same reason.
10670
+ // Consumers already tolerate undefined — fresh sessions run
10671
+ // with it until the first system.transform sets the pair.
10672
+ requestModel?.providerID && requestModel?.modelID && state.modelContextLimit !== void 0 && (state.modelProviderID !== requestModel.providerID || state.modelID !== requestModel.modelID)
10673
+ ) {
10674
+ state.modelContextLimit = void 0;
10675
+ state.modelProviderID = requestModel.providerID;
10676
+ state.modelID = requestModel.modelID;
10637
10677
  }
10638
10678
  await updatePerTurnState(state, logger, messages);
10639
10679
  }
@@ -11020,8 +11060,25 @@ var server = (async (ctx) => {
11020
11060
  if (isSecureMode()) {
11021
11061
  configureClientAuth(ctx.client);
11022
11062
  }
11023
- registry4.hydrateModelLimitsFromClient(ctx.client).catch(() => {
11024
- });
11063
+ registry4.hydrateModelLimitsFromClient(ctx.client).then(
11064
+ (recorded) => {
11065
+ if (recorded > 0) {
11066
+ logger.info("Model limit catalog seeded from provider config", {
11067
+ models: recorded
11068
+ });
11069
+ } else {
11070
+ logger.warn(
11071
+ "Model limit catalog seeding recorded no entries \u2014 falling back to per-request refresh (system.transform)"
11072
+ );
11073
+ }
11074
+ },
11075
+ (error) => {
11076
+ logger.warn(
11077
+ "Model limit catalog seeding failed \u2014 falling back to per-request refresh (system.transform)",
11078
+ { error: error instanceof Error ? error.message : String(error) }
11079
+ );
11080
+ }
11081
+ );
11025
11082
  logger.info("DCP initialized");
11026
11083
  startAutoUpdate(ctx, config.autoUpdate);
11027
11084
  const compressToolContext = {