opencode-cache-engine 0.3.1 → 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.1",
3
+ "version": "0.3.2",
4
4
  "private": false,
5
5
  "description": "Provider-aware prompt-cache optimization and telemetry for OpenCode",
6
6
  "keywords": [
@@ -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"
@@ -1007,3 +1008,116 @@ test("MiMo policy performs no tool mutation (fingerprints are pure inputs)", ()
1007
1008
  // semantic fingerprint stays order-insensitive regardless of the policy
1008
1009
  assert.equal(toolFingerprint([...TOOLS].reverse()), before.sem)
1009
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
+ })
@@ -1,6 +0,0 @@
1
- {
2
- "$schema": "https://opencode.ai/config.json",
3
- "plugin": [
4
- "opencode-cache-engine"
5
- ]
6
- }
@@ -1,5 +0,0 @@
1
- {
2
- "plugin": [
3
- "opencode-cache-engine"
4
- ]
5
- }