opencode-cache-engine 0.3.6 → 0.4.1

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.
@@ -4,12 +4,7 @@ import {
4
4
  DEFAULT_CONFIG_PATH,
5
5
  DIGEST_TEMPLATE,
6
6
  affinityTelemetryFields,
7
- POLICY_GLM53,
8
- POLICY_GPT56,
9
- POLICY_MIMO26,
10
- POLICY_NEUTRAL,
11
7
  createRecorder,
12
- detectPolicy,
13
8
  detectReasoningIssues,
14
9
  digestDecision,
15
10
  ensureMetricsDir,
@@ -37,6 +32,7 @@ import {
37
32
  toolFingerprint,
38
33
  toolWireFingerprint,
39
34
  } from "./cache-engine-core.mjs"
35
+ import { resolveRuntimePolicy } from "./cache-policy-core.mjs"
40
36
 
41
37
  // ---------------------------------------------------------------------------
42
38
  // cache-engine
@@ -101,7 +97,21 @@ type Shape = {
101
97
  toolCount: number | null
102
98
  }
103
99
 
104
- type ModelInfo = { family: string; providerID: string; modelID: string }
100
+ // Resolved runtime policy (the registry's runtime descriptor). `policy` is the
101
+ // legacy telemetry/state string emitted by the pre-v0.4.0 classifier.
102
+ type PolicyRuntime = {
103
+ policy: string
104
+ isNeutral: boolean
105
+ gptCacheMetadata: boolean
106
+ envRelocation: "glm" | "mimo" | null
107
+ thinkingIntegrity: boolean
108
+ cacheRatio: "glm" | "mimo" | null
109
+ providerChange: "glm" | "mimo" | null
110
+ prefixDiagnostics: boolean
111
+ openRouterAffinity: boolean
112
+ }
113
+
114
+ type ModelInfo = { family: string; providerID: string; modelID: string; caps: PolicyRuntime }
105
115
 
106
116
  type ToolCache = { semanticToolsHash: string | null; wireToolsHash: string | null }
107
117
 
@@ -258,19 +268,21 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
258
268
  // gpt56/glm53/deepseek classification once established.
259
269
  const rememberModel = (sid: string, model: ChatParamsModel | undefined): ModelInfo | null => {
260
270
  if (!model) return null
261
- const family = detectPolicy(model)
271
+ // Single runtime source of policy classification: the registry resolver.
272
+ const caps = resolveRuntimePolicy(model) as PolicyRuntime
262
273
  const s = get(sid)
263
274
  const info: ModelInfo = {
264
- family,
275
+ family: caps.policy,
265
276
  providerID: String(model.providerID ?? ""),
266
277
  modelID: String(model.api?.id ?? model.id ?? ""),
278
+ caps,
267
279
  }
268
- if (s.modelInfo == null || (s.modelInfo.family === POLICY_NEUTRAL && family !== POLICY_NEUTRAL)) {
280
+ if (s.modelInfo == null || (s.modelInfo.caps.isNeutral && !caps.isNeutral)) {
269
281
  s.modelInfo = info
270
282
  // A title/summary request (neutral small model) may have established the
271
283
  // system baseline first. Its "powered by the model named ..." env line
272
284
  // differs from the real model's, so re-baseline on upgrade.
273
- if (s.baselineSystem !== null && s.modelInfo.family !== POLICY_NEUTRAL) {
285
+ if (s.baselineSystem !== null && !caps.isNeutral) {
274
286
  s.baselineSystem = null
275
287
  s.shape = null
276
288
  }
@@ -350,11 +362,11 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
350
362
 
351
363
  s.lastProcessedMessageID = nextProcessedCursor(firstPage, startCursor)
352
364
 
353
- const family = s.modelInfo?.family
365
+ const caps = s.modelInfo?.caps
354
366
  const glmIntegrity =
355
- family === POLICY_GLM53 &&
356
- policyEnabled(cfg, POLICY_GLM53) &&
357
- cfg.policies?.[POLICY_GLM53]?.preserveThinkingIntegrity === true
367
+ caps?.thinkingIntegrity === true &&
368
+ policyEnabled(cfg, caps.policy) &&
369
+ cfg.policies?.[caps.policy]?.preserveThinkingIntegrity === true
358
370
 
359
371
  if (glmIntegrity && reasoningNewestFirst.length > 0) {
360
372
  // messages arrive newest-first; process oldest->newest so `seen` grows
@@ -417,11 +429,11 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
417
429
  recFields.model = s.modelInfo.modelID
418
430
  recFields.policy = s.modelInfo.family
419
431
  }
420
- if (family === POLICY_GLM53) {
432
+ if (caps?.cacheRatio === "glm") {
421
433
  recFields.promptTokens = read + write + input
422
434
  recFields.glmHitRate = glmHitRatio(read, write, input)
423
435
  }
424
- if (family === POLICY_MIMO26) {
436
+ if (caps?.cacheRatio === "mimo") {
425
437
  // Provider-reported cached tokens / total prompt tokens. The runtime's
426
438
  // `input` is the non-cached prompt input and `cache.read` is the
427
439
  // cached prompt input, so total prompt tokens are derived as read +
@@ -431,7 +443,7 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
431
443
  recFields.promptTokens = promptTokens
432
444
  recFields.cachedTokens = read
433
445
  recFields.cacheHitRate = mimoHitRate(read, promptTokens)
434
- if (cfg.policies?.[POLICY_MIMO26]?.stickySession === true) {
446
+ if (cfg.policies?.[caps.policy]?.stickySession === true) {
435
447
  recFields.stickySessionId = mimoSessionIdFor(sid)
436
448
  }
437
449
  // Prefer the latest live provider identity over the latched one.
@@ -440,10 +452,10 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
440
452
  recFields.model = s.mimoProvider.modelID
441
453
  }
442
454
  }
443
- if (family === POLICY_GPT56 && s.gptInjected) {
455
+ if (caps?.gptCacheMetadata === true && s.gptInjected) {
444
456
  recFields.keyStrategy = "session"
445
- recFields.mode = cfg.policies?.[POLICY_GPT56]?.mode
446
- recFields.ttl = cfg.policies?.[POLICY_GPT56]?.ttl
457
+ recFields.mode = cfg.policies?.[caps.policy]?.mode
458
+ recFields.ttl = cfg.policies?.[caps.policy]?.ttl
447
459
  }
448
460
  rec.record(recFields)
449
461
  } else {
@@ -495,8 +507,9 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
495
507
  try {
496
508
  const model = input.model as unknown as ChatParamsModel
497
509
  const providerID = String(model?.providerID ?? "")
498
- const family = detectPolicy(model)
499
- if (family !== POLICY_MIMO26 && family !== POLICY_GLM53) return
510
+ const caps = resolveRuntimePolicy(model) as PolicyRuntime
511
+ if (!caps.openRouterAffinity) return
512
+ const family = caps.policy
500
513
 
501
514
  const hasSessionIDHeader = (headers?: Record<string, string>) =>
502
515
  Object.keys(headers ?? {}).some((name) => name.toLowerCase() === "x-session-id")
@@ -536,6 +549,7 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
536
549
  try {
537
550
  const info = rememberModel(input.sessionID, input.model as unknown as ChatParamsModel)
538
551
  const family = info?.family
552
+ const caps = info?.caps
539
553
 
540
554
  // ---- MiMo-V2.6: provider-switch diagnostics (telemetry only) ---------
541
555
  // MiMo cache lives at the provider side, so a provider change within one
@@ -545,7 +559,7 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
545
559
  // NOTE: OpenRouter's *upstream* provider selection (e.g. xiaomi/fp8) is
546
560
  // not exposed to plugins; only the OpenCode providerID/modelID are
547
561
  // observable here.
548
- if (family === POLICY_MIMO26 && policyEnabled(cfg, POLICY_MIMO26) && info) {
562
+ if (caps?.providerChange === "mimo" && policyEnabled(cfg, caps.policy) && info) {
549
563
  // Use the LIVE model identity (not the latched one) so a provider
550
564
  // switch within the session is actually observable.
551
565
  const live = input.model as unknown as ChatParamsModel
@@ -558,7 +572,7 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
558
572
  const ev = providerChangeEvent(s.mimoProvider, cur)
559
573
  if (ev.changed) {
560
574
  const sticky =
561
- cfg.policies?.[POLICY_MIMO26]?.stickySession === true
575
+ cfg.policies?.[caps.policy]?.stickySession === true
562
576
  ? { stickySessionId: mimoSessionIdFor(input.sessionID) }
563
577
  : {}
564
578
  rec.record({
@@ -566,7 +580,7 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
566
580
  sid: input.sessionID,
567
581
  ts: Date.now(),
568
582
  reason: "mimo_provider_changed",
569
- policy: POLICY_MIMO26,
583
+ policy: caps.policy,
570
584
  from: ev.from,
571
585
  to: ev.to,
572
586
  ...sticky,
@@ -579,7 +593,7 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
579
593
  }
580
594
 
581
595
  // ---- GLM-5.3 provider identity observation (telemetry only) ----------
582
- if (family === POLICY_GLM53 && policyEnabled(cfg, POLICY_GLM53) && info) {
596
+ if (caps?.providerChange === "glm" && policyEnabled(cfg, caps.policy) && info) {
583
597
  const live = input.model as unknown as ChatParamsModel
584
598
  const cur = {
585
599
  providerID: String(live?.providerID ?? ""),
@@ -594,7 +608,7 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
594
608
  sid: input.sessionID,
595
609
  ts: Date.now(),
596
610
  reason: "glm_provider_changed",
597
- policy: POLICY_GLM53,
611
+ policy: caps.policy,
598
612
  from: ev.from,
599
613
  to: ev.to,
600
614
  note: "OpenCode providerID changed; provider-specific upstream routing is not plugin-visible",
@@ -605,12 +619,12 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
605
619
  return
606
620
  }
607
621
 
608
- if (!(family === POLICY_GPT56 && policyEnabled(cfg, POLICY_GPT56))) {
622
+ if (!(caps?.gptCacheMetadata === true && policyEnabled(cfg, caps.policy))) {
609
623
  // DeepSeek / GLM / neutral: nothing to inject. GLM has no cache-key API;
610
624
  // DeepSeek caching is fully passive; we never mutate requests for them.
611
625
  return
612
626
  }
613
- const gpol = cfg.policies?.[POLICY_GPT56]
627
+ const gpol = cfg.policies?.[caps.policy]
614
628
  const applyRoot = gpol?.cacheRootKey !== false
615
629
  const compaction = input.agent === "compaction"
616
630
  const isolated = compaction && gpol?.compactionCacheIsolation === true
@@ -678,7 +692,7 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
678
692
  kind: "cache-options",
679
693
  sid: input.sessionID,
680
694
  ts: Date.now(),
681
- policy: POLICY_GPT56,
695
+ policy: caps.policy,
682
696
  provider: info.providerID,
683
697
  model: info.modelID,
684
698
  keyStrategy: applyRoot ? "cache-root" : "session",
@@ -784,43 +798,40 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
784
798
  const model = input.model as unknown as ChatParamsModel
785
799
  rememberModel(sid, model)
786
800
  const s = get(sid)
787
- const family = s.modelInfo?.family
801
+ const caps = s.modelInfo?.caps
788
802
 
789
803
  // ---- GLM-5.3 / MiMo-V2.6 input-shape stabilization ------------------
790
804
  // Relocate the identifiable volatile env block (per-day date) to the
791
805
  // tail of the single system string, content-preserving, ONLY when the
792
- // family opts into system stabilization and the block markers are
793
- // present exactly. Never touches other content/order; never applied to
794
- // other families. The runtime passes a single-element system array
795
- // (verified against the installed runtime), so no generic reordering is
796
- // involved.
806
+ // resolved runtime policy opts into system stabilization and the block
807
+ // markers are present exactly. Never touches other content/order; never
808
+ // applied to other families. The runtime passes a single-element system
809
+ // array (verified against the installed runtime), so no generic
810
+ // reordering is involved.
797
811
  //
798
812
  // In-place mutation note: request.ts keeps using its own local `system`
799
813
  // array after the hook (the trigger's returned output is ignored), so
800
814
  // reassigning `output.system = [...]` would be lost. We rewrite the
801
815
  // single element in place instead.
802
816
  let systemText = output.system.join("\n")
803
- const glmStabilize =
804
- family === POLICY_GLM53 &&
805
- policyEnabled(cfg, POLICY_GLM53) &&
806
- cfg.policies?.[POLICY_GLM53]?.stabilizeSystem === true
807
- const mimoStabilize =
808
- family === POLICY_MIMO26 &&
809
- policyEnabled(cfg, POLICY_MIMO26) &&
810
- cfg.policies?.[POLICY_MIMO26]?.stabilizeSystem === true
811
- if ((glmStabilize || mimoStabilize) && output.system.length === 1) {
817
+ const envVariant = caps?.envRelocation ?? null
818
+ const stabilize =
819
+ envVariant !== null &&
820
+ policyEnabled(cfg, caps!.policy) &&
821
+ cfg.policies?.[caps!.policy]?.stabilizeSystem === true
822
+ if (stabilize && output.system.length === 1) {
812
823
  const rel = relocateVolatileEnvBlock(output.system[0])
813
824
  if (rel.changed) {
814
825
  output.system[0] = rel.text
815
826
  systemText = rel.text
816
- if (family === POLICY_MIMO26) {
827
+ if (envVariant === "mimo") {
817
828
  log("debug", "mimo system env block relocated to suffix", { sid })
818
829
  rec.record({
819
830
  kind: "boundary",
820
831
  sid,
821
832
  ts: Date.now(),
822
833
  reason: "mimo_system_env_relocated",
823
- policy: POLICY_MIMO26,
834
+ policy: caps!.policy,
824
835
  provider: s.modelInfo?.providerID,
825
836
  model: s.modelInfo?.modelID,
826
837
  })
@@ -906,13 +917,13 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
906
917
  // MiMo-specific explicit diagnostic: the STABLE prefix changed (not
907
918
  // just the relocated volatile env suffix). Reported only; the new
908
919
  // content is never overwritten with a stale snapshot.
909
- if (family === POLICY_MIMO26 && reasons.includes("system_stable_prefix_changed")) {
920
+ if (caps?.prefixDiagnostics === true && reasons.includes("system_stable_prefix_changed")) {
910
921
  rec.record({
911
922
  kind: "boundary",
912
923
  sid,
913
924
  ts: Date.now(),
914
925
  reason: "mimo_system_prefix_changed",
915
- policy: POLICY_MIMO26,
926
+ policy: caps!.policy,
916
927
  provider: s.modelInfo?.providerID,
917
928
  model: s.modelInfo?.modelID,
918
929
  changedFields: granular,