opencode-cache-engine 0.3.0 → 0.3.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-cache-engine",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "private": false,
5
5
  "description": "Provider-aware prompt-cache optimization and telemetry for OpenCode",
6
6
  "keywords": [
@@ -481,6 +481,25 @@ export function mimoHitRate(cachedTokens, promptTokens) {
481
481
  return Math.round((100 * cachedTokens) / promptTokens)
482
482
  }
483
483
 
484
+ // Derive a provider-neutral stable ID from the logical OpenCode session ID.
485
+ // The full SHA-256 digest avoids intentional truncation/collapse of distinct
486
+ // sessions. This helper only generates an identifier; it does not select a
487
+ // provider or inject the identifier into any request transport.
488
+ export function stableSessionIdFor(sessionID) {
489
+ if (typeof sessionID !== "string" || sessionID.length === 0) return null
490
+ return `oc-ses-${createHash("sha256").update(sessionID).digest("hex")}`
491
+ }
492
+
493
+ // OpenRouter session affinity is currently eligible only for the two policy
494
+ // families with documented affinity use: MiMo-V2.6 and GLM-5.3. This pure
495
+ // policy decision is separate from ID generation and transport/header injection.
496
+ export function isOpenRouterAffinityEligible(policyFamily, providerID) {
497
+ return (
498
+ providerID === "openrouter" &&
499
+ (policyFamily === POLICY_MIMO26 || policyFamily === POLICY_GLM53)
500
+ )
501
+ }
502
+
484
503
  // Derive a stable, session-scoped identifier suitable for OpenRouter's
485
504
  // documented `session_id` sticky-routing key. Pure function of the OpenCode
486
505
  // session id only: identical sessions map to identical ids, distinct sessions
@@ -56,16 +56,13 @@ import {
56
56
  // history stable, and INSTRUMENT preserved-thinking
57
57
  // integrity (duplicate/reorder/modified reasoning). No
58
58
  // invented cache key (Z.ai exposes none).
59
- // MiMo-V2.6 -> prefix stability + OpenRouter session affinity. The
60
- // safe env-block relocation is applied (stabilizeSystem)
61
- // and provider switches within a session are diagnosed.
62
- // MiMo caching is provider-managed implicit context
63
- // caching; no cache key/breakpoint/TTL is invented. The
64
- // OpenRouter sticky-session id is derived but currently
65
- // NOT injected: this runtime's OpenRouter request
66
- // adapter forwards only usage/reasoning/prompt_cache_key
67
- // and exposes no top-level `session_id` path (verified
68
- // against the installed runtime; see README).
59
+ // MiMo-V2.6 -> prefix stability + provider diagnostics. The safe
60
+ // env-block relocation is applied (stabilizeSystem).
61
+ // For actual OpenRouter requests only, chat.headers
62
+ // adds the deterministic x-session-id unless an
63
+ // explicit case-insensitive value already exists in
64
+ // model/plugin headers. No MiMo cache key/breakpoint/
65
+ // TTL is invented.
69
66
  //
70
67
  // The engine remains conservative: it observes, hashes, compares, records,
71
68
  // appends a compaction continuation template, and (for GPT-5.6 only) injects
@@ -141,7 +138,13 @@ const emptyShape = (): Shape => ({
141
138
  toolCount: null,
142
139
  })
143
140
 
144
- type ChatParamsModel = { providerID: string; id?: string; api?: { id?: string; npm?: string }; name?: string }
141
+ type ChatParamsModel = {
142
+ providerID: string
143
+ id?: string
144
+ api?: { id?: string; npm?: string }
145
+ headers?: Record<string, string>
146
+ name?: string
147
+ }
145
148
 
146
149
  export const CacheEngine: Plugin = async ({ client, directory }) => {
147
150
  const cfg = loadConfig({ configPath: DEFAULT_CONFIG_PATH, env: process.env })
@@ -478,6 +481,30 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
478
481
  // GPT cache. Compaction requests (agent === "compaction") for the same root
479
482
  // use a deterministic separate namespace (<root>:compact) so a compaction
480
483
  // cache write never interferes with the useful live-session cache.
484
+ //
485
+ // MiMo-V2.6 + OpenRouter session affinity. Gate on the actual OpenCode
486
+ // provider identity as well as the detected family; a matching model slug
487
+ // on a direct endpoint is not sufficient. The runtime merges model.headers
488
+ // before this hook's output, so preserve a case-insensitive user/model or
489
+ // earlier-plugin x-session-id rather than silently overwriting it. Otherwise
490
+ // add the deterministic ID derived from the logical OpenCode session.
491
+ "chat.headers": async (input, output) => {
492
+ try {
493
+ const model = input.model as unknown as ChatParamsModel
494
+ const providerID = String(model?.providerID ?? "")
495
+ if (providerID !== "openrouter" || detectPolicy(model) !== POLICY_MIMO26) return
496
+
497
+ const hasSessionIDHeader = (headers?: Record<string, string>) =>
498
+ Object.keys(headers ?? {}).some((name) => name.toLowerCase() === "x-session-id")
499
+ if (hasSessionIDHeader(model?.headers) || hasSessionIDHeader(output.headers)) return
500
+
501
+ const sessionID = mimoSessionIdFor(input.sessionID)
502
+ if (sessionID) output.headers["x-session-id"] = sessionID
503
+ } catch (e) {
504
+ rec.record({ kind: "telemetry-error", ts: Date.now(), error: String(e) })
505
+ }
506
+ },
507
+
481
508
  "chat.params": async (input, output) => {
482
509
  try {
483
510
  const info = rememberModel(input.sessionID, input.model as unknown as ChatParamsModel)
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { test } from "node:test"
11
11
  import assert from "node:assert/strict"
12
+ import { execFileSync } from "node:child_process"
12
13
  import { mkdtempSync, readFileSync, writeFileSync } from "node:fs"
13
14
  import { homedir, tmpdir } from "node:os"
14
15
  import { join } from "node:path"
@@ -28,6 +29,7 @@ import {
28
29
  glmHitRatio,
29
30
  gptCacheOptionsDelta,
30
31
  hitRatePct,
32
+ isOpenRouterAffinityEligible,
31
33
  loadConfig,
32
34
  mimoHitRate,
33
35
  mimoSessionIdFor,
@@ -40,6 +42,7 @@ import {
40
42
  shapeFieldDiffs,
41
43
  shorthash,
42
44
  shouldAggregate,
45
+ stableSessionIdFor,
43
46
  systemShapeHashes,
44
47
  toolFingerprint,
45
48
  toolWireFingerprint,
@@ -871,10 +874,22 @@ test("MiMo relocation is a no-op when start/end markers are missing", () => {
871
874
  // MiMo-V2.6: sticky-session identity (pure, derived but not injected)
872
875
  // ===========================================================================
873
876
 
877
+ test("stableSessionIdFor: deterministic for the same logical session", () => {
878
+ assert.equal(stableSessionIdFor("ses_abc123"), stableSessionIdFor("ses_abc123"))
879
+ })
880
+
881
+ test("stableSessionIdFor: different logical sessions produce different ids", () => {
882
+ assert.notEqual(stableSessionIdFor("ses_abc"), stableSessionIdFor("ses_xyz"))
883
+ })
884
+
874
885
  test("mimoSessionIdFor: deterministic + stable for the same session", () => {
875
886
  assert.equal(mimoSessionIdFor("ses_abc123"), mimoSessionIdFor("ses_abc123"))
876
887
  })
877
888
 
889
+ test("mimoSessionIdFor preserves its existing id format", () => {
890
+ assert.equal(mimoSessionIdFor("ses_abc123"), `mimo-ses-${shorthash("ses_abc123")}`)
891
+ })
892
+
878
893
  test("mimoSessionIdFor: distinct sessions produce distinct ids", () => {
879
894
  assert.notEqual(mimoSessionIdFor("ses_abc"), mimoSessionIdFor("ses_xyz"))
880
895
  })
@@ -898,6 +913,31 @@ test("mimoSessionIdFor: transient request fields cannot alter the id", () => {
898
913
  assert.equal(mimoSessionIdFor("ses_stable"), mimoSessionIdFor("ses_stable", { turn: 7, temperature: 0.9 }))
899
914
  })
900
915
 
916
+ // ===========================================================================
917
+ // OpenRouter session-affinity eligibility (pure policy decision only)
918
+ // ===========================================================================
919
+
920
+ test("OpenRouter affinity is eligible for MiMo and GLM only", () => {
921
+ assert.equal(isOpenRouterAffinityEligible(POLICY_MIMO26, "openrouter"), true)
922
+ assert.equal(isOpenRouterAffinityEligible(POLICY_GLM53, "openrouter"), true)
923
+ })
924
+
925
+ test("OpenRouter affinity is ineligible for non-OpenRouter providers", () => {
926
+ assert.equal(isOpenRouterAffinityEligible(POLICY_MIMO26, "xiaomi"), false)
927
+ assert.equal(isOpenRouterAffinityEligible(POLICY_GLM53, "zai"), false)
928
+ assert.equal(isOpenRouterAffinityEligible(POLICY_MIMO26, "unknown-provider"), false)
929
+ })
930
+
931
+ test("OpenRouter affinity is ineligible for DeepSeek and GPT", () => {
932
+ assert.equal(isOpenRouterAffinityEligible(POLICY_DEEPSEEK, "openrouter"), false)
933
+ assert.equal(isOpenRouterAffinityEligible(POLICY_GPT56, "openrouter"), false)
934
+ })
935
+
936
+ test("OpenRouter affinity is ineligible for unknown families and providers", () => {
937
+ assert.equal(isOpenRouterAffinityEligible("unknown-family", "openrouter"), false)
938
+ assert.equal(isOpenRouterAffinityEligible(POLICY_MIMO26, undefined), false)
939
+ })
940
+
901
941
  // ===========================================================================
902
942
  // MiMo-V2.6: cached/prompt token metrics
903
943
  // ===========================================================================
@@ -968,3 +1008,116 @@ test("MiMo policy performs no tool mutation (fingerprints are pure inputs)", ()
968
1008
  // semantic fingerprint stays order-insensitive regardless of the policy
969
1009
  assert.equal(toolFingerprint([...TOOLS].reverse()), before.sem)
970
1010
  })
1011
+
1012
+ // ===========================================================================
1013
+ // MiMo/OpenRouter session affinity (real chat.headers hook, no model calls)
1014
+ // ===========================================================================
1015
+
1016
+ async function runMiMoHeaderHookProbe() {
1017
+ const home = mkdtempSync(join(tmpdir(), "ce-mimo-headers-"))
1018
+ const pluginURL = new URL("../src/cache-engine.ts", import.meta.url).href
1019
+ const coreURL = new URL("../src/cache-engine-core.mjs", import.meta.url).href
1020
+ const script = `
1021
+ import assert from "node:assert/strict"
1022
+ const { CacheEngine } = await import(${JSON.stringify(pluginURL)})
1023
+ const { mimoSessionIdFor } = await import(${JSON.stringify(coreURL)})
1024
+ const client = {
1025
+ app: { log: async () => ({}) },
1026
+ session: { get: async () => ({ data: { parentID: null } }) },
1027
+ tool: { list: async () => ({ data: [] }) },
1028
+ }
1029
+ const hooks = await CacheEngine({ client, directory: process.env.HOME })
1030
+ assert.equal(typeof hooks["chat.headers"], "function")
1031
+ const invoke = async (model, sessionID, existingHeaders = {}) => {
1032
+ const output = { headers: { ...existingHeaders } }
1033
+ const headersRef = output.headers
1034
+ await hooks["chat.headers"]({
1035
+ sessionID,
1036
+ agent: "build",
1037
+ model,
1038
+ provider: { id: model.providerID },
1039
+ message: { id: "msg", sessionID, role: "user", content: "probe" },
1040
+ }, output)
1041
+ return { headers: output.headers, sameObject: output.headers === headersRef, modelHeaders: model.headers }
1042
+ }
1043
+ const mimoOpenRouter = {
1044
+ providerID: "openrouter",
1045
+ id: "xiaomi/mimo-v2.6-flash",
1046
+ api: { id: "xiaomi/mimo-v2.6-flash" },
1047
+ headers: {},
1048
+ }
1049
+ const stable1 = await invoke(mimoOpenRouter, "ses_same", { "User-Agent": "preserve-me", "x-custom": "also-preserve" })
1050
+ const stable2 = await invoke(mimoOpenRouter, "ses_same")
1051
+ const different = await invoke(mimoOpenRouter, "ses_other")
1052
+ const direct = await invoke({ ...mimoOpenRouter, providerID: "xiaomi" }, "ses_direct", { "User-Agent": "preserve-me" })
1053
+ const unknown = await invoke({ ...mimoOpenRouter, providerID: "unknown-provider" }, "ses_unknown")
1054
+ const missingProvider = await invoke({ ...mimoOpenRouter, providerID: undefined }, "ses_missing_provider")
1055
+ const glm = await invoke({ providerID: "openrouter", id: "z-ai/glm-5.3", api: { id: "z-ai/glm-5.3" }, headers: {} }, "ses_glm")
1056
+ const configured = await invoke({
1057
+ ...mimoOpenRouter,
1058
+ headers: { "X-Session-Id": "user-configured-value" },
1059
+ }, "ses_configured")
1060
+ const earlierPlugin = await invoke(mimoOpenRouter, "ses_plugin", { "X-SESSION-ID": "earlier-plugin-value" })
1061
+ const result = {
1062
+ stable1,
1063
+ stable2,
1064
+ different,
1065
+ direct,
1066
+ unknown,
1067
+ missingProvider,
1068
+ glm,
1069
+ configured,
1070
+ configuredModelHeaders: configured.modelHeaders,
1071
+ earlierPlugin,
1072
+ expectedSame: mimoSessionIdFor("ses_same"),
1073
+ expectedOther: mimoSessionIdFor("ses_other"),
1074
+ }
1075
+ assert.equal(configured.headers["x-session-id"], undefined)
1076
+ assert.equal(earlierPlugin.headers["X-SESSION-ID"], "earlier-plugin-value")
1077
+ process.stdout.write(JSON.stringify(result))
1078
+ `
1079
+ const stdout = execFileSync(process.execPath, ["--experimental-strip-types", "--input-type=module", "-e", script], {
1080
+ cwd: process.cwd(),
1081
+ env: { ...process.env, HOME: home },
1082
+ encoding: "utf8",
1083
+ })
1084
+ return JSON.parse(stdout.trim())
1085
+ }
1086
+
1087
+ let miMoHeaderProbe
1088
+ const miMoHeaderResults = async () => (miMoHeaderProbe ??= runMiMoHeaderHookProbe())
1089
+
1090
+ test("MiMo/OpenRouter attaches the existing deterministic MiMo session ID", async () => {
1091
+ const result = await miMoHeaderResults()
1092
+ assert.equal(result.stable1.headers["x-session-id"], result.expectedSame)
1093
+ assert.equal(result.stable1.sameObject, true)
1094
+ assert.deepEqual(result.stable1.headers["User-Agent"], "preserve-me")
1095
+ assert.deepEqual(result.stable1.headers["x-custom"], "also-preserve")
1096
+ })
1097
+
1098
+ test("MiMo/OpenRouter session ID is stable across turns and distinct across sessions", async () => {
1099
+ const result = await miMoHeaderResults()
1100
+ assert.equal(result.stable2.headers["x-session-id"], result.stable1.headers["x-session-id"])
1101
+ assert.equal(result.different.headers["x-session-id"], result.expectedOther)
1102
+ assert.notEqual(result.different.headers["x-session-id"], result.stable1.headers["x-session-id"])
1103
+ })
1104
+
1105
+ test("MiMo direct and unknown providers do not receive x-session-id", async () => {
1106
+ const result = await miMoHeaderResults()
1107
+ assert.equal(result.direct.headers["x-session-id"], undefined)
1108
+ assert.equal(result.unknown.headers["x-session-id"], undefined)
1109
+ assert.equal(result.missingProvider.headers["x-session-id"], undefined)
1110
+ assert.deepEqual(result.direct.headers, { "User-Agent": "preserve-me" })
1111
+ })
1112
+
1113
+ test("MiMo affinity preserves existing x-session-id values case-insensitively", async () => {
1114
+ const result = await miMoHeaderResults()
1115
+ assert.equal(result.configured.headers["x-session-id"], undefined)
1116
+ assert.deepEqual(result.configuredModelHeaders, { "X-Session-Id": "user-configured-value" })
1117
+ assert.deepEqual(result.earlierPlugin.headers, { "X-SESSION-ID": "earlier-plugin-value" })
1118
+ })
1119
+
1120
+ test("MiMo-only affinity does not add x-session-id to GLM/OpenRouter", async () => {
1121
+ const result = await miMoHeaderResults()
1122
+ assert.equal(result.glm.headers["x-session-id"], undefined)
1123
+ })