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 +1 -1
- package/src/cache-engine-core.mjs +19 -0
- package/src/cache-engine.ts +38 -11
- package/test/cache-engine.test.mjs +153 -0
package/package.json
CHANGED
|
@@ -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
|
package/src/cache-engine.ts
CHANGED
|
@@ -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 +
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
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 = {
|
|
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
|
+
})
|