opencode-cache-engine 0.3.4 → 0.3.6
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/README.md +129 -79
- package/package.json +1 -1
- package/src/cache-engine-core.mjs +42 -5
- package/src/cache-engine.ts +57 -4
- package/test/cache-engine.test.mjs +131 -1
package/README.md
CHANGED
|
@@ -3,21 +3,35 @@
|
|
|
3
3
|
Provider-aware prompt-cache optimization and observability for
|
|
4
4
|
[OpenCode](https://opencode.ai).
|
|
5
5
|
|
|
6
|
-
`opencode-cache-engine` is an
|
|
6
|
+
`opencode-cache-engine` is distributed as an npm package. The Git repository is
|
|
7
|
+
the development source of truth; the published package is a release artifact.
|
|
8
|
+
The package exposes two separate targets required by OpenCode's installed
|
|
9
|
+
plugin model:
|
|
7
10
|
|
|
8
|
-
-
|
|
9
|
-
-
|
|
11
|
+
- **`./server`** — the CacheEngine runtime, hooks, provider policies, and telemetry.
|
|
12
|
+
- **`./tui`** — plugin-manager registration and enable/disable integration; it
|
|
13
|
+
has no CacheEngine-specific UI.
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
For local development, use the package from this Git checkout through the
|
|
16
|
+
repository's OpenCode/package development path. Do not maintain or edit a copied
|
|
17
|
+
plugin under `~/.config/opencode/plugins/`. For released installs where
|
|
18
|
+
reproducibility matters, pin an exact package version rather than relying on
|
|
19
|
+
`@latest` resolution or a moving cache entry; see [Installation](#installation).
|
|
12
20
|
|
|
13
21
|
`CacheEngine` is an OpenCode plugin designed for long-running agent sessions where prompt-cache efficiency affects both latency and cost. It keeps the harness conservative for providers whose cache behavior is already automatic, while applying provider-specific optimizations where the provider exposes useful cache controls or where prompt structure can be safely improved.
|
|
14
22
|
|
|
15
23
|
The plugin currently has four cache-policy families:
|
|
16
24
|
|
|
17
|
-
* **DeepSeek
|
|
18
|
-
* **GPT-5.6
|
|
19
|
-
* **GLM-5.3
|
|
20
|
-
* **MiMo-V2.6
|
|
25
|
+
* **DeepSeek** — passive cache observability; request structure is preserved.
|
|
26
|
+
* **GPT-5.6** — documented cache-key/options metadata, with prompt text unchanged.
|
|
27
|
+
* **GLM-5.3** — narrow, content-preserving `<env>` relocation and diagnostics.
|
|
28
|
+
* **MiMo-V2.6** — narrow, content-preserving `<env>` relocation and diagnostics.
|
|
29
|
+
|
|
30
|
+
For both MiMo-V2.6 and GLM-5.3, CacheEngine adds its deterministic
|
|
31
|
+
`x-session-id` request header only when OpenCode identifies the actual provider
|
|
32
|
+
as `openrouter`. It does not add that OpenRouter-specific header for
|
|
33
|
+
non-OpenRouter providers; direct provider endpoints retain their provider-native
|
|
34
|
+
caching behavior.
|
|
21
35
|
|
|
22
36
|
The central design principle is:
|
|
23
37
|
|
|
@@ -38,7 +52,7 @@ It:
|
|
|
38
52
|
6. Applies GPT-5.6 cache-control metadata.
|
|
39
53
|
7. Applies the GLM-5.3 and MiMo-V2.6 volatile-environment relocation.
|
|
40
54
|
8. Records diagnostics that help determine whether prompt-shape changes correlate with cache behavior.
|
|
41
|
-
9. Records MiMo
|
|
55
|
+
9. Records MiMo/GLM affinity outcomes and provider-identity changes.
|
|
42
56
|
|
|
43
57
|
The plugin deliberately avoids pretending that a local hash is proof of a provider cache hit. Provider-reported token usage remains the authoritative signal.
|
|
44
58
|
|
|
@@ -165,7 +179,9 @@ This prevents a compaction-specific prompt from sharing the same GPT cache names
|
|
|
165
179
|
|
|
166
180
|
### Policy: input-shape optimization
|
|
167
181
|
|
|
168
|
-
GLM-5.3
|
|
182
|
+
GLM-5.3 and MiMo-V2.6 use the only prompt-text transformation in the current
|
|
183
|
+
plugin: a narrow, content-preserving relocation of the identifiable `<env>`
|
|
184
|
+
block for the eligible model family.
|
|
169
185
|
|
|
170
186
|
The plugin identifies OpenCode's volatile `<env>` section and moves it to the **tail of the system prompt**.
|
|
171
187
|
|
|
@@ -225,6 +241,22 @@ It only occurs when:
|
|
|
225
241
|
|
|
226
242
|
The plugin does not arbitrarily rearrange unrelated prompt content.
|
|
227
243
|
|
|
244
|
+
### OpenRouter session affinity
|
|
245
|
+
|
|
246
|
+
For GLM-5.3 requests whose actual OpenCode provider identity is `openrouter`,
|
|
247
|
+
CacheEngine adds its deterministic `x-session-id` request header unless a
|
|
248
|
+
case-insensitive `x-session-id` is already present in model or plugin headers.
|
|
249
|
+
The existing value is preserved. This header is affinity metadata, not a prompt
|
|
250
|
+
transformation or cache-control field.
|
|
251
|
+
|
|
252
|
+
For direct Z.AI or any other non-OpenRouter endpoint, CacheEngine does not add
|
|
253
|
+
the OpenRouter-specific affinity header. It leaves the endpoint's native cache
|
|
254
|
+
behavior intact.
|
|
255
|
+
|
|
256
|
+
Affinity observations record eligibility, the observed provider identity,
|
|
257
|
+
whether a header was already present or added, and provider-identity changes
|
|
258
|
+
(`glm_provider_changed`). They do not record the header value.
|
|
259
|
+
|
|
228
260
|
|
|
229
261
|
## MiMo-V2.6 (Flash / Pro)
|
|
230
262
|
|
|
@@ -287,28 +319,24 @@ Explicit telemetry events:
|
|
|
287
319
|
* `mimo_system_env_relocated`
|
|
288
320
|
* `mimo_system_prefix_changed`
|
|
289
321
|
|
|
290
|
-
### OpenRouter
|
|
291
|
-
|
|
292
|
-
OpenRouter documents a top-level `session_id` request field for sticky provider
|
|
293
|
-
routing, which keeps a session's requests on the same upstream provider so
|
|
294
|
-
provider-side prompt caches stay warm.
|
|
322
|
+
### OpenRouter session affinity
|
|
295
323
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
324
|
+
For MiMo-V2.6 requests whose actual OpenCode provider identity is `openrouter`,
|
|
325
|
+
CacheEngine adds its existing deterministic, session-scoped `x-session-id`
|
|
326
|
+
request header. If a case-insensitive `x-session-id` already exists in model or
|
|
327
|
+
plugin headers, CacheEngine preserves it and does not replace it. Eligibility
|
|
328
|
+
uses both the MiMo-V2.6 family and the actual provider identity; a matching model
|
|
329
|
+
slug on another endpoint is not enough.
|
|
299
330
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
and `mimo26.stickySession` currently gates that recording. If a future runtime
|
|
305
|
-
gains a verified `session_id` path, the helper is already in place.
|
|
331
|
+
For Xiaomi's direct endpoint and every other non-OpenRouter provider, CacheEngine
|
|
332
|
+
does not add its OpenRouter-specific `x-session-id`. Direct provider endpoints
|
|
333
|
+
retain their provider-native caching behavior. Any header already supplied by
|
|
334
|
+
the user or runtime is left untouched.
|
|
306
335
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
documented body `session_id`.
|
|
336
|
+
The `x-session-id` header is not a top-level request-body `session_id`, a
|
|
337
|
+
`promptCacheKey`, or a cache-control option. MiMo uses provider-managed implicit
|
|
338
|
+
caching: CacheEngine sends no undocumented `promptCacheKey`, `cacheControl`,
|
|
339
|
+
cache breakpoint, or TTL.
|
|
312
340
|
|
|
313
341
|
### MiMo cache metrics
|
|
314
342
|
|
|
@@ -374,12 +402,16 @@ and are reported diagnostically; the message content is left untouched.
|
|
|
374
402
|
|
|
375
403
|
# Provider comparison
|
|
376
404
|
|
|
377
|
-
|
|
|
378
|
-
|
|
|
379
|
-
| DeepSeek
|
|
380
|
-
| GPT-5.6
|
|
381
|
-
| GLM-5.3
|
|
382
|
-
| MiMo-V2.6
|
|
405
|
+
| Policy family | Detection | Prompt text changed? | Cache metadata changed? | OpenRouter affinity header | Primary cache signal |
|
|
406
|
+
| ------------- | --------- | ------------------- | ----------------------- | -------------------------- | -------------------- |
|
|
407
|
+
| DeepSeek | `deepseek` | No | No | None | provider `cache.read` / `cache.write` |
|
|
408
|
+
| GPT-5.6 | `gpt-5.6*` on OpenAI-ish endpoints | No | Yes: `prompt_cache_key` + options | None | provider cache tokens |
|
|
409
|
+
| GLM-5.3 | `glm-5.3*` | Yes, narrowly (`<env>` tail) | No provider cache key | `x-session-id` on OpenRouter only | provider cache tokens (GLM ratio) |
|
|
410
|
+
| MiMo-V2.6 | Flash / Pro only | Yes, narrowly (`<env>` tail) | No: implicit caching only | `x-session-id` on OpenRouter only | `cached_tokens / prompt_tokens` |
|
|
411
|
+
|
|
412
|
+
`x-session-id` is an HTTP affinity header, not a provider cache key or
|
|
413
|
+
cache-control field. Non-OpenRouter endpoints do not receive CacheEngine's
|
|
414
|
+
OpenRouter-specific affinity value; their native cache behavior is unchanged.
|
|
383
415
|
|
|
384
416
|
|
|
385
417
|
# Prompt-cache strategy
|
|
@@ -614,9 +646,17 @@ Telemetry is intended to answer questions such as:
|
|
|
614
646
|
* Did the GLM system stabilization actually change the observed prompt shape?
|
|
615
647
|
* Did MiMo's environment relocation fire (`mimo_system_env_relocated`)?
|
|
616
648
|
* Did MiMo's stable system prefix change (`mimo_system_prefix_changed`)?
|
|
617
|
-
*
|
|
649
|
+
* Was MiMo/GLM affinity eligible, and did CacheEngine add its header?
|
|
650
|
+
* Was affinity bypassed for a non-OpenRouter or missing provider identity?
|
|
651
|
+
* Did the MiMo provider change (`mimo_provider_changed`) or the GLM provider
|
|
652
|
+
change (`glm_provider_changed`) within a session?
|
|
618
653
|
* What was MiMo's provider-reported cache hit rate (`cacheHitRate`)?
|
|
619
654
|
|
|
655
|
+
Affinity observations are `boundary` records. They contain provider/model
|
|
656
|
+
identity and booleans/source classification such as `eligible`,
|
|
657
|
+
`headerPresent`, `headerAttached`, and `headerSource`; they do not include the
|
|
658
|
+
`x-session-id` header value or full request headers.
|
|
659
|
+
|
|
620
660
|
A MiMo usage record adds the provider-reported cache fields:
|
|
621
661
|
|
|
622
662
|
```json
|
|
@@ -792,6 +832,10 @@ Defaults to:
|
|
|
792
832
|
|
|
793
833
|
Existing request options are not overwritten by the plugin.
|
|
794
834
|
|
|
835
|
+
The established **272K pricing boundary** is controlled by user/harness-side
|
|
836
|
+
configuration and remains unchanged. CacheEngine does not set or raise GPT
|
|
837
|
+
context or output limits; apply the existing harness/user-side limits.
|
|
838
|
+
|
|
795
839
|
|
|
796
840
|
# GLM-5.3 configuration
|
|
797
841
|
|
|
@@ -844,10 +888,11 @@ Enables relocation of the volatile `<env>` section to the system-prompt tail
|
|
|
844
888
|
|
|
845
889
|
### `stickySession`
|
|
846
890
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
891
|
+
Controls whether MiMo usage/provider-change telemetry includes the derived
|
|
892
|
+
`stickySessionId` field. It does not control the existing `x-session-id` header
|
|
893
|
+
injection, which is gated by MiMo family plus actual `openrouter` provider
|
|
894
|
+
identity. The telemetry field contains the derived identifier, not request
|
|
895
|
+
headers or prompt data.
|
|
851
896
|
|
|
852
897
|
### `preserveThinkingIntegrity`
|
|
853
898
|
|
|
@@ -898,7 +943,10 @@ This plugin is compatible with OpenRouter because the cache policy is based on t
|
|
|
898
943
|
|
|
899
944
|
For cache-sensitive workloads, provider stability remains important.
|
|
900
945
|
|
|
901
|
-
The plugin does not attempt to compensate for provider switching by rewriting
|
|
946
|
+
The plugin does not attempt to compensate for provider switching by rewriting
|
|
947
|
+
prompts. For MiMo and GLM it records observed provider identity and provider
|
|
948
|
+
changes so routing instability is observable; it never overrides the selected
|
|
949
|
+
provider or inspects OpenRouter's hidden upstream provider selection.
|
|
902
950
|
|
|
903
951
|
For that reason, a stable provider route is preferable when your goal is to measure and maximize prefix reuse.
|
|
904
952
|
|
|
@@ -929,7 +977,9 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
|
|
|
929
977
|
}
|
|
930
978
|
```
|
|
931
979
|
|
|
932
|
-
|
|
980
|
+
`CacheEngine` is the exported plugin factory. The npm package name remains
|
|
981
|
+
`opencode-cache-engine`; the server and TUI package exports are listed in
|
|
982
|
+
[File layout](#file-layout).
|
|
933
983
|
|
|
934
984
|
---
|
|
935
985
|
|
|
@@ -1118,7 +1168,8 @@ For reliable cache measurements:
|
|
|
1118
1168
|
|
|
1119
1169
|
# File layout
|
|
1120
1170
|
|
|
1121
|
-
|
|
1171
|
+
This Git repository is the canonical development source. The npm package is
|
|
1172
|
+
built from this tree and exposes the runtime entry points separately:
|
|
1122
1173
|
|
|
1123
1174
|
```text
|
|
1124
1175
|
opencode-cache-engine/
|
|
@@ -1135,51 +1186,49 @@ opencode-cache-engine/
|
|
|
1135
1186
|
└── LICENSE
|
|
1136
1187
|
```
|
|
1137
1188
|
|
|
1138
|
-
The
|
|
1139
|
-
|
|
1140
|
-
```ts
|
|
1141
|
-
export const CacheEngine
|
|
1142
|
-
```
|
|
1143
|
-
|
|
1144
|
-
regardless of the eventual npm package name.
|
|
1145
|
-
|
|
1146
|
-
For example, the npm package could be named:
|
|
1189
|
+
The package exports in `package.json` are:
|
|
1147
1190
|
|
|
1148
|
-
```
|
|
1149
|
-
|
|
1191
|
+
```json
|
|
1192
|
+
{
|
|
1193
|
+
"./server": "./src/cache-engine.ts",
|
|
1194
|
+
"./tui": "./src/tui.mjs"
|
|
1195
|
+
}
|
|
1150
1196
|
```
|
|
1151
1197
|
|
|
1152
|
-
|
|
1198
|
+
The server target owns all CacheEngine runtime hooks and request behavior. The
|
|
1199
|
+
TUI target only registers the package with OpenCode's plugin manager; it does
|
|
1200
|
+
not duplicate server logic.
|
|
1153
1201
|
|
|
1154
1202
|
---
|
|
1155
1203
|
|
|
1156
1204
|
# Installation
|
|
1157
1205
|
|
|
1158
|
-
|
|
1206
|
+
### Local development
|
|
1159
1207
|
|
|
1160
|
-
|
|
1208
|
+
Develop against this repository/package checkout using the project's OpenCode
|
|
1209
|
+
plugin development path. Edit and test the Git working tree as the source of
|
|
1210
|
+
truth; do not copy the plugin into `~/.config/opencode/plugins/` or keep a
|
|
1211
|
+
second active source tree there.
|
|
1161
1212
|
|
|
1162
|
-
|
|
1213
|
+
### Released package
|
|
1163
1214
|
|
|
1164
|
-
|
|
1215
|
+
OpenCode loads the server and TUI targets from the npm package's separate
|
|
1216
|
+
exports. For reproducible released installs, pin an exact version. For this
|
|
1217
|
+
release, use:
|
|
1165
1218
|
|
|
1166
1219
|
```json
|
|
1167
1220
|
{
|
|
1168
1221
|
"plugin": [
|
|
1169
|
-
"opencode-cache-engine"
|
|
1222
|
+
"opencode-cache-engine@0.3.6"
|
|
1170
1223
|
]
|
|
1171
1224
|
}
|
|
1172
1225
|
```
|
|
1173
1226
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
```ts
|
|
1177
|
-
export const CacheEngine: Plugin = async ({ client, directory }) => {
|
|
1178
|
-
// ...
|
|
1179
|
-
}
|
|
1180
|
-
```
|
|
1227
|
+
Avoid a bare package name that resolves a moving `@latest` version when
|
|
1228
|
+
reproducibility matters. Update the pinned version deliberately when upgrading.
|
|
1181
1229
|
|
|
1182
|
-
After installation, verify that OpenCode loads the plugin successfully before
|
|
1230
|
+
After installation, verify that OpenCode loads the plugin successfully before
|
|
1231
|
+
benchmarking cache behavior.
|
|
1183
1232
|
|
|
1184
1233
|
---
|
|
1185
1234
|
|
|
@@ -1209,7 +1258,7 @@ GLM-5.3:
|
|
|
1209
1258
|
MiMo-V2.6:
|
|
1210
1259
|
volatile env block relocated when eligible
|
|
1211
1260
|
no GPT/GLM-only cache fields present
|
|
1212
|
-
|
|
1261
|
+
x-session-id added only for actual OpenRouter provider identity
|
|
1213
1262
|
telemetry carries provider/model/promptTokens/cachedTokens/cacheHitRate
|
|
1214
1263
|
```
|
|
1215
1264
|
|
|
@@ -1278,14 +1327,15 @@ not matched.
|
|
|
1278
1327
|
|
|
1279
1328
|
---
|
|
1280
1329
|
|
|
1281
|
-
##
|
|
1330
|
+
## OpenRouter affinity header is not added
|
|
1282
1331
|
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
`
|
|
1287
|
-
|
|
1288
|
-
|
|
1332
|
+
CacheEngine adds its `x-session-id` only for a detected MiMo-V2.6 or GLM-5.3
|
|
1333
|
+
request when the actual OpenCode `providerID` is exactly `openrouter`. A direct
|
|
1334
|
+
provider route or missing provider identity is bypassed. If a case-insensitive
|
|
1335
|
+
`x-session-id` is already present in model or plugin headers, it is preserved
|
|
1336
|
+
and CacheEngine does not replace it. Check the `openrouter_affinity_*` boundary
|
|
1337
|
+
records for eligibility, provider identity, and whether CacheEngine added the
|
|
1338
|
+
header; the record does not include the header value.
|
|
1289
1339
|
|
|
1290
1340
|
---
|
|
1291
1341
|
|
|
@@ -1311,9 +1361,9 @@ The current implementation is intentionally conservative:
|
|
|
1311
1361
|
|
|
1312
1362
|
```text
|
|
1313
1363
|
DeepSeek -> preserve and measure
|
|
1314
|
-
GPT-5.6 ->
|
|
1315
|
-
GLM-5.3 ->
|
|
1316
|
-
MiMo-V2.6 ->
|
|
1364
|
+
GPT-5.6 -> documented cache key/options; user/harness controls the 272K pricing boundary
|
|
1365
|
+
GLM-5.3 -> preserve-content <env> relocation + OpenRouter affinity header
|
|
1366
|
+
MiMo-V2.6 -> preserve-content <env> relocation + OpenRouter affinity header
|
|
1317
1367
|
```
|
|
1318
1368
|
|
|
1319
1369
|
That separation is the core design of the project.
|
package/package.json
CHANGED
|
@@ -500,14 +500,51 @@ export function isOpenRouterAffinityEligible(policyFamily, providerID) {
|
|
|
500
500
|
)
|
|
501
501
|
}
|
|
502
502
|
|
|
503
|
+
// Produce safe telemetry fields for one affinity-capable request. Provider
|
|
504
|
+
// identity is observable and recorded; header values are deliberately not.
|
|
505
|
+
// A supplied non-OpenRouter identity is classified from the observed value
|
|
506
|
+
// rather than guessed against a provider allowlist.
|
|
507
|
+
export function affinityTelemetryFields(policyFamily, providerID, headerSource) {
|
|
508
|
+
if (policyFamily !== POLICY_MIMO26 && policyFamily !== POLICY_GLM53) return null
|
|
509
|
+
|
|
510
|
+
const identity = typeof providerID === "string" && providerID.length > 0 ? providerID : null
|
|
511
|
+
const eligible = isOpenRouterAffinityEligible(policyFamily, identity)
|
|
512
|
+
if (!eligible) {
|
|
513
|
+
return {
|
|
514
|
+
reason: identity
|
|
515
|
+
? "openrouter_affinity_bypassed_non_openrouter"
|
|
516
|
+
: "openrouter_affinity_bypassed_provider_missing_or_unknown",
|
|
517
|
+
eligible: false,
|
|
518
|
+
providerIdentityKnown: identity !== null,
|
|
519
|
+
provider: identity,
|
|
520
|
+
headerPresent: false,
|
|
521
|
+
headerAttached: false,
|
|
522
|
+
headerSource: "not_applicable",
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const source = ["cache_engine", "preexisting", "unavailable"].includes(headerSource)
|
|
527
|
+
? headerSource
|
|
528
|
+
: "unavailable"
|
|
529
|
+
const headerPresent = source === "cache_engine" || source === "preexisting"
|
|
530
|
+
return {
|
|
531
|
+
reason: "openrouter_affinity_eligible",
|
|
532
|
+
eligible: true,
|
|
533
|
+
providerIdentityKnown: true,
|
|
534
|
+
provider: identity,
|
|
535
|
+
headerPresent,
|
|
536
|
+
headerAttached: source === "cache_engine",
|
|
537
|
+
headerSource: source,
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
503
541
|
// Derive a stable, session-scoped identifier suitable for OpenRouter's
|
|
504
542
|
// documented `session_id` sticky-routing key. Pure function of the OpenCode
|
|
505
543
|
// session id only: identical sessions map to identical ids, distinct sessions
|
|
506
|
-
// map to distinct ids, and transient request contents cannot influence it.
|
|
507
|
-
//
|
|
508
|
-
//
|
|
509
|
-
//
|
|
510
|
-
// so this id is currently recorded as telemetry only and is never injected.
|
|
544
|
+
// map to distinct ids, and transient request contents cannot influence it. The
|
|
545
|
+
// value is printable, contains no whitespace, and is far below the 256-char cap
|
|
546
|
+
// (25 chars). The plugin uses it as the existing OpenRouter x-session-id header
|
|
547
|
+
// value for eligible MiMo/GLM requests.
|
|
511
548
|
export function mimoSessionIdFor(sessionID) {
|
|
512
549
|
if (typeof sessionID !== "string" || sessionID.length === 0) return null
|
|
513
550
|
return `mimo-ses-${shorthash(sessionID)}`
|
package/src/cache-engine.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { Event, Message, Part } from "@opencode-ai/sdk"
|
|
|
3
3
|
import {
|
|
4
4
|
DEFAULT_CONFIG_PATH,
|
|
5
5
|
DIGEST_TEMPLATE,
|
|
6
|
+
affinityTelemetryFields,
|
|
6
7
|
POLICY_GLM53,
|
|
7
8
|
POLICY_GPT56,
|
|
8
9
|
POLICY_MIMO26,
|
|
@@ -127,6 +128,7 @@ type SessionState = {
|
|
|
127
128
|
reasoningSeen: Map<string, number>
|
|
128
129
|
reasoningLastSeq: string[] | null
|
|
129
130
|
mimoProvider: { providerID: string; modelID: string } | null
|
|
131
|
+
glmProvider: { providerID: string; modelID: string } | null
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
const emptyShape = (): Shape => ({
|
|
@@ -175,6 +177,7 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
|
|
|
175
177
|
reasoningSeen: new Map(),
|
|
176
178
|
reasoningLastSeq: null,
|
|
177
179
|
mimoProvider: null,
|
|
180
|
+
glmProvider: null,
|
|
178
181
|
}
|
|
179
182
|
sessions.set(sid, s)
|
|
180
183
|
}
|
|
@@ -493,14 +496,37 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
|
|
|
493
496
|
const model = input.model as unknown as ChatParamsModel
|
|
494
497
|
const providerID = String(model?.providerID ?? "")
|
|
495
498
|
const family = detectPolicy(model)
|
|
496
|
-
if (
|
|
499
|
+
if (family !== POLICY_MIMO26 && family !== POLICY_GLM53) return
|
|
497
500
|
|
|
498
501
|
const hasSessionIDHeader = (headers?: Record<string, string>) =>
|
|
499
502
|
Object.keys(headers ?? {}).some((name) => name.toLowerCase() === "x-session-id")
|
|
500
|
-
|
|
503
|
+
let headerSource = "not_applicable"
|
|
504
|
+
if (providerID === "openrouter") {
|
|
505
|
+
if (hasSessionIDHeader(model?.headers) || hasSessionIDHeader(output.headers)) {
|
|
506
|
+
headerSource = "preexisting"
|
|
507
|
+
} else {
|
|
508
|
+
const sessionID = mimoSessionIdFor(input.sessionID)
|
|
509
|
+
if (sessionID) {
|
|
510
|
+
output.headers["x-session-id"] = sessionID
|
|
511
|
+
headerSource = "cache_engine"
|
|
512
|
+
} else {
|
|
513
|
+
headerSource = "unavailable"
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
501
517
|
|
|
502
|
-
const
|
|
503
|
-
if (
|
|
518
|
+
const telemetry = affinityTelemetryFields(family, providerID, headerSource)
|
|
519
|
+
if (telemetry) {
|
|
520
|
+
rec.record({
|
|
521
|
+
kind: "boundary",
|
|
522
|
+
sid: input.sessionID,
|
|
523
|
+
ts: Date.now(),
|
|
524
|
+
policy: family,
|
|
525
|
+
provider: telemetry.provider,
|
|
526
|
+
model: String(model?.api?.id ?? model?.id ?? "") || null,
|
|
527
|
+
...telemetry,
|
|
528
|
+
})
|
|
529
|
+
}
|
|
504
530
|
} catch (e) {
|
|
505
531
|
rec.record({ kind: "telemetry-error", ts: Date.now(), error: String(e) })
|
|
506
532
|
}
|
|
@@ -552,6 +578,33 @@ export const CacheEngine: Plugin = async ({ client, directory }) => {
|
|
|
552
578
|
return
|
|
553
579
|
}
|
|
554
580
|
|
|
581
|
+
// ---- GLM-5.3 provider identity observation (telemetry only) ----------
|
|
582
|
+
if (family === POLICY_GLM53 && policyEnabled(cfg, POLICY_GLM53) && info) {
|
|
583
|
+
const live = input.model as unknown as ChatParamsModel
|
|
584
|
+
const cur = {
|
|
585
|
+
providerID: String(live?.providerID ?? ""),
|
|
586
|
+
modelID: String(live?.api?.id ?? live?.id ?? ""),
|
|
587
|
+
}
|
|
588
|
+
if (cur.providerID) {
|
|
589
|
+
const s = get(input.sessionID)
|
|
590
|
+
const ev = providerChangeEvent(s.glmProvider, cur)
|
|
591
|
+
if (ev.changed) {
|
|
592
|
+
rec.record({
|
|
593
|
+
kind: "boundary",
|
|
594
|
+
sid: input.sessionID,
|
|
595
|
+
ts: Date.now(),
|
|
596
|
+
reason: "glm_provider_changed",
|
|
597
|
+
policy: POLICY_GLM53,
|
|
598
|
+
from: ev.from,
|
|
599
|
+
to: ev.to,
|
|
600
|
+
note: "OpenCode providerID changed; provider-specific upstream routing is not plugin-visible",
|
|
601
|
+
})
|
|
602
|
+
}
|
|
603
|
+
s.glmProvider = cur
|
|
604
|
+
}
|
|
605
|
+
return
|
|
606
|
+
}
|
|
607
|
+
|
|
555
608
|
if (!(family === POLICY_GPT56 && policyEnabled(cfg, POLICY_GPT56))) {
|
|
556
609
|
// DeepSeek / GLM / neutral: nothing to inject. GLM has no cache-key API;
|
|
557
610
|
// DeepSeek caching is fully passive; we never mutate requests for them.
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
POLICY_GPT56,
|
|
20
20
|
POLICY_MIMO26,
|
|
21
21
|
POLICY_NEUTRAL,
|
|
22
|
+
affinityTelemetryFields,
|
|
22
23
|
canonicalStringify,
|
|
23
24
|
commonPrefixLength,
|
|
24
25
|
createRecorder,
|
|
@@ -938,6 +939,42 @@ test("OpenRouter affinity is ineligible for unknown families and providers", ()
|
|
|
938
939
|
assert.equal(isOpenRouterAffinityEligible(POLICY_MIMO26, undefined), false)
|
|
939
940
|
})
|
|
940
941
|
|
|
942
|
+
test("affinity telemetry fields classify eligibility, attachment, bypass, and missing identity", () => {
|
|
943
|
+
assert.deepEqual(affinityTelemetryFields(POLICY_MIMO26, "openrouter", "cache_engine"), {
|
|
944
|
+
reason: "openrouter_affinity_eligible",
|
|
945
|
+
eligible: true,
|
|
946
|
+
providerIdentityKnown: true,
|
|
947
|
+
provider: "openrouter",
|
|
948
|
+
headerPresent: true,
|
|
949
|
+
headerAttached: true,
|
|
950
|
+
headerSource: "cache_engine",
|
|
951
|
+
})
|
|
952
|
+
assert.deepEqual(affinityTelemetryFields(POLICY_GLM53, "openrouter", "preexisting"), {
|
|
953
|
+
reason: "openrouter_affinity_eligible",
|
|
954
|
+
eligible: true,
|
|
955
|
+
providerIdentityKnown: true,
|
|
956
|
+
provider: "openrouter",
|
|
957
|
+
headerPresent: true,
|
|
958
|
+
headerAttached: false,
|
|
959
|
+
headerSource: "preexisting",
|
|
960
|
+
})
|
|
961
|
+
assert.equal(affinityTelemetryFields(POLICY_MIMO26, "xiaomi", "not_applicable").reason,
|
|
962
|
+
"openrouter_affinity_bypassed_non_openrouter")
|
|
963
|
+
assert.equal(affinityTelemetryFields(POLICY_GLM53, "unknown-provider", "not_applicable").reason,
|
|
964
|
+
"openrouter_affinity_bypassed_non_openrouter")
|
|
965
|
+
assert.deepEqual(affinityTelemetryFields(POLICY_MIMO26, "", "not_applicable"), {
|
|
966
|
+
reason: "openrouter_affinity_bypassed_provider_missing_or_unknown",
|
|
967
|
+
eligible: false,
|
|
968
|
+
providerIdentityKnown: false,
|
|
969
|
+
provider: null,
|
|
970
|
+
headerPresent: false,
|
|
971
|
+
headerAttached: false,
|
|
972
|
+
headerSource: "not_applicable",
|
|
973
|
+
})
|
|
974
|
+
assert.equal(affinityTelemetryFields(POLICY_DEEPSEEK, "openrouter", "not_applicable"), null)
|
|
975
|
+
assert.equal(affinityTelemetryFields(POLICY_GPT56, "openrouter", "not_applicable"), null)
|
|
976
|
+
})
|
|
977
|
+
|
|
941
978
|
// ===========================================================================
|
|
942
979
|
// MiMo-V2.6: cached/prompt token metrics
|
|
943
980
|
// ===========================================================================
|
|
@@ -1019,8 +1056,10 @@ async function runMiMoHeaderHookProbe() {
|
|
|
1019
1056
|
const coreURL = new URL("../src/cache-engine-core.mjs", import.meta.url).href
|
|
1020
1057
|
const script = `
|
|
1021
1058
|
import assert from "node:assert/strict"
|
|
1059
|
+
import { readFileSync } from "node:fs"
|
|
1060
|
+
process.env.CACHE_ENGINE_METRICS_FILE = process.env.HOME + "/affinity-metrics.jsonl"
|
|
1022
1061
|
const { CacheEngine } = await import(${JSON.stringify(pluginURL)})
|
|
1023
|
-
const { mimoSessionIdFor } = await import(${JSON.stringify(coreURL)})
|
|
1062
|
+
const { detectPolicy, mimoSessionIdFor, POLICY_GLM53, POLICY_MIMO26 } = await import(${JSON.stringify(coreURL)})
|
|
1024
1063
|
const client = {
|
|
1025
1064
|
app: { log: async () => ({}) },
|
|
1026
1065
|
session: { get: async () => ({ data: { parentID: null } }) },
|
|
@@ -1031,6 +1070,16 @@ async function runMiMoHeaderHookProbe() {
|
|
|
1031
1070
|
const invoke = async (model, sessionID, existingHeaders = {}) => {
|
|
1032
1071
|
const output = { headers: { ...existingHeaders } }
|
|
1033
1072
|
const headersRef = output.headers
|
|
1073
|
+
const family = detectPolicy(model)
|
|
1074
|
+
if (family === POLICY_MIMO26 || family === POLICY_GLM53) {
|
|
1075
|
+
await hooks["chat.params"]({
|
|
1076
|
+
sessionID,
|
|
1077
|
+
agent: "build",
|
|
1078
|
+
model,
|
|
1079
|
+
provider: { source: "config", info: { id: model.providerID }, options: {} },
|
|
1080
|
+
message: { id: "msg", sessionID, role: "user", content: "probe" },
|
|
1081
|
+
}, { options: {} })
|
|
1082
|
+
}
|
|
1034
1083
|
await hooks["chat.headers"]({
|
|
1035
1084
|
sessionID,
|
|
1036
1085
|
agent: "build",
|
|
@@ -1062,6 +1111,10 @@ async function runMiMoHeaderHookProbe() {
|
|
|
1062
1111
|
const glm2 = await invoke(glmOpenRouter, "ses_glm_same")
|
|
1063
1112
|
const glmDifferent = await invoke(glmOpenRouter, "ses_glm_other")
|
|
1064
1113
|
const glmDirect = await invoke({ ...glmOpenRouter, providerID: "zai" }, "ses_glm_direct", { "User-Agent": "preserve-glm" })
|
|
1114
|
+
const mimoSwitchOpen = await invoke(mimoOpenRouter, "ses_mimo_switch")
|
|
1115
|
+
const mimoSwitchDirect = await invoke({ ...mimoOpenRouter, providerID: "xiaomi" }, "ses_mimo_switch")
|
|
1116
|
+
const glmSwitchOpen = await invoke(glmOpenRouter, "ses_glm_switch")
|
|
1117
|
+
const glmSwitchDirect = await invoke({ ...glmOpenRouter, providerID: "zai" }, "ses_glm_switch")
|
|
1065
1118
|
const nonOpenRouterMatrix = [
|
|
1066
1119
|
["mimo-direct", { ...mimoOpenRouter, providerID: "xiaomi" }],
|
|
1067
1120
|
["glm-direct", { ...glmOpenRouter, providerID: "zai" }],
|
|
@@ -1101,6 +1154,10 @@ async function runMiMoHeaderHookProbe() {
|
|
|
1101
1154
|
glm2,
|
|
1102
1155
|
glmDifferent,
|
|
1103
1156
|
glmDirect,
|
|
1157
|
+
mimoSwitchOpen,
|
|
1158
|
+
mimoSwitchDirect,
|
|
1159
|
+
glmSwitchOpen,
|
|
1160
|
+
glmSwitchDirect,
|
|
1104
1161
|
compatibility,
|
|
1105
1162
|
gptOptions: gptOutput.options,
|
|
1106
1163
|
configured,
|
|
@@ -1110,6 +1167,8 @@ async function runMiMoHeaderHookProbe() {
|
|
|
1110
1167
|
expectedOther: mimoSessionIdFor("ses_other"),
|
|
1111
1168
|
expectedGlm: mimoSessionIdFor("ses_glm_same"),
|
|
1112
1169
|
expectedGlmOther: mimoSessionIdFor("ses_glm_other"),
|
|
1170
|
+
telemetry: readFileSync(process.env.CACHE_ENGINE_METRICS_FILE, "utf8")
|
|
1171
|
+
.trim().split("\\n").filter(Boolean).map((line) => JSON.parse(line)),
|
|
1113
1172
|
}
|
|
1114
1173
|
assert.equal(configured.headers["x-session-id"], undefined)
|
|
1115
1174
|
assert.equal(earlierPlugin.headers["X-SESSION-ID"], "earlier-plugin-value")
|
|
@@ -1186,3 +1245,74 @@ test("OpenAI GPT retains its existing chat.params cache options without affinity
|
|
|
1186
1245
|
assert.equal(typeof result.gptOptions.promptCacheKey, "string")
|
|
1187
1246
|
assert.deepEqual(result.gptOptions.promptCacheOptions, { mode: "implicit", ttl: "30m" })
|
|
1188
1247
|
})
|
|
1248
|
+
|
|
1249
|
+
test("affinity telemetry classifies eligible, attached, preexisting, bypassed, and missing-provider requests", async () => {
|
|
1250
|
+
const result = await miMoHeaderResults()
|
|
1251
|
+
const events = result.telemetry.filter((event) => event.reason?.startsWith("openrouter_affinity_"))
|
|
1252
|
+
const eventFor = (sid, policy) => events.find((event) => event.sid === sid && event.policy === policy)
|
|
1253
|
+
|
|
1254
|
+
for (const [sid, policy] of [
|
|
1255
|
+
["ses_same", POLICY_MIMO26],
|
|
1256
|
+
["ses_glm_same", POLICY_GLM53],
|
|
1257
|
+
]) {
|
|
1258
|
+
const event = eventFor(sid, policy)
|
|
1259
|
+
assert.equal(event.reason, "openrouter_affinity_eligible")
|
|
1260
|
+
assert.equal(event.eligible, true)
|
|
1261
|
+
assert.equal(event.provider, "openrouter")
|
|
1262
|
+
assert.equal(event.headerPresent, true)
|
|
1263
|
+
assert.equal(event.headerAttached, true)
|
|
1264
|
+
assert.equal(event.headerSource, "cache_engine")
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
const configured = eventFor("ses_configured", POLICY_MIMO26)
|
|
1268
|
+
assert.equal(configured.reason, "openrouter_affinity_eligible")
|
|
1269
|
+
assert.equal(configured.headerPresent, true)
|
|
1270
|
+
assert.equal(configured.headerAttached, false)
|
|
1271
|
+
assert.equal(configured.headerSource, "preexisting")
|
|
1272
|
+
|
|
1273
|
+
for (const [sid, policy, provider] of [
|
|
1274
|
+
["ses_direct", POLICY_MIMO26, "xiaomi"],
|
|
1275
|
+
["ses_glm_direct", POLICY_GLM53, "zai"],
|
|
1276
|
+
["ses_unknown", POLICY_MIMO26, "unknown-provider"],
|
|
1277
|
+
]) {
|
|
1278
|
+
const event = eventFor(sid, policy)
|
|
1279
|
+
assert.equal(event.reason, "openrouter_affinity_bypassed_non_openrouter")
|
|
1280
|
+
assert.equal(event.eligible, false)
|
|
1281
|
+
assert.equal(event.provider, provider)
|
|
1282
|
+
assert.equal(event.headerAttached, false)
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
const missing = eventFor("ses_missing_provider", POLICY_MIMO26)
|
|
1286
|
+
assert.equal(missing.reason, "openrouter_affinity_bypassed_provider_missing_or_unknown")
|
|
1287
|
+
assert.equal(missing.providerIdentityKnown, false)
|
|
1288
|
+
assert.equal(missing.provider, null)
|
|
1289
|
+
assert.equal(missing.headerAttached, false)
|
|
1290
|
+
|
|
1291
|
+
// New affinity-observation records contain classifications, never the
|
|
1292
|
+
// generated or pre-existing x-session-id value.
|
|
1293
|
+
for (const event of events) {
|
|
1294
|
+
assert.equal(Object.hasOwn(event, "x-session-id"), false)
|
|
1295
|
+
assert.equal(Object.hasOwn(event, "headerValue"), false)
|
|
1296
|
+
}
|
|
1297
|
+
const serializedAffinityEvents = JSON.stringify(events)
|
|
1298
|
+
assert.equal(serializedAffinityEvents.includes(result.expectedSame), false)
|
|
1299
|
+
assert.equal(serializedAffinityEvents.includes(result.expectedGlm), false)
|
|
1300
|
+
})
|
|
1301
|
+
|
|
1302
|
+
test("affinity telemetry preserves non-OpenRouter families and reports provider changes", async () => {
|
|
1303
|
+
const result = await miMoHeaderResults()
|
|
1304
|
+
const events = result.telemetry
|
|
1305
|
+
const affinityEvents = events.filter((event) => event.reason?.startsWith("openrouter_affinity_"))
|
|
1306
|
+
assert.equal(affinityEvents.some((event) => event.policy === POLICY_DEEPSEEK), false)
|
|
1307
|
+
assert.equal(affinityEvents.some((event) => event.policy === POLICY_GPT56), false)
|
|
1308
|
+
|
|
1309
|
+
const mimoChange = events.find((event) => event.reason === "mimo_provider_changed" && event.sid === "ses_mimo_switch")
|
|
1310
|
+
assert.equal(mimoChange.from.providerID, "openrouter")
|
|
1311
|
+
assert.equal(mimoChange.to.providerID, "xiaomi")
|
|
1312
|
+
|
|
1313
|
+
const glmChange = events.find((event) => event.reason === "glm_provider_changed" && event.sid === "ses_glm_switch")
|
|
1314
|
+
assert.equal(glmChange.kind, "boundary")
|
|
1315
|
+
assert.equal(glmChange.from.providerID, "openrouter")
|
|
1316
|
+
assert.equal(glmChange.to.providerID, "zai")
|
|
1317
|
+
assert.equal(glmChange.policy, POLICY_GLM53)
|
|
1318
|
+
})
|