opencode-cache-engine 0.4.0 → 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.
- package/package.json +1 -1
- package/src/cache-engine.ts +60 -49
- package/src/cache-policy-core.mjs +83 -11
- package/test/cache-engine.test.mjs +190 -0
package/package.json
CHANGED
package/src/cache-engine.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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 &&
|
|
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
|
|
365
|
+
const caps = s.modelInfo?.caps
|
|
354
366
|
const glmIntegrity =
|
|
355
|
-
|
|
356
|
-
policyEnabled(cfg,
|
|
357
|
-
cfg.policies?.[
|
|
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 (
|
|
432
|
+
if (caps?.cacheRatio === "glm") {
|
|
421
433
|
recFields.promptTokens = read + write + input
|
|
422
434
|
recFields.glmHitRate = glmHitRatio(read, write, input)
|
|
423
435
|
}
|
|
424
|
-
if (
|
|
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?.[
|
|
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 (
|
|
455
|
+
if (caps?.gptCacheMetadata === true && s.gptInjected) {
|
|
444
456
|
recFields.keyStrategy = "session"
|
|
445
|
-
recFields.mode = cfg.policies?.[
|
|
446
|
-
recFields.ttl = cfg.policies?.[
|
|
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
|
|
499
|
-
if (
|
|
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 (
|
|
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?.[
|
|
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:
|
|
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 (
|
|
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:
|
|
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 (!(
|
|
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?.[
|
|
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:
|
|
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
|
|
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
|
-
//
|
|
793
|
-
// present exactly. Never touches other content/order; never
|
|
794
|
-
// other families. The runtime passes a single-element system
|
|
795
|
-
// (verified against the installed runtime), so no generic
|
|
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
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
cfg
|
|
807
|
-
|
|
808
|
-
|
|
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 (
|
|
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:
|
|
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 (
|
|
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:
|
|
926
|
+
policy: caps!.policy,
|
|
916
927
|
provider: s.modelInfo?.providerID,
|
|
917
928
|
model: s.modelInfo?.modelID,
|
|
918
929
|
changedFields: granular,
|
|
@@ -17,8 +17,10 @@
|
|
|
17
17
|
// `inventoryRef`. Inheritance is always explicit (`inheritsFrom`); "newer means
|
|
18
18
|
// same behavior" is never an unconditional rule.
|
|
19
19
|
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
20
|
+
// As of v0.4.1 the runtime hook layer (cache-engine.ts) consumes
|
|
21
|
+
// resolveRuntimePolicy() as its single source of policy classification.
|
|
22
|
+
// detectPolicy() is retained as the compatibility classifier for the legacy
|
|
23
|
+
// POLICY_* strings.
|
|
22
24
|
|
|
23
25
|
// ---------------------------------------------------------------------------
|
|
24
26
|
// Model normalization (shared with the legacy classifier)
|
|
@@ -206,6 +208,36 @@ function resolveTransport(s) {
|
|
|
206
208
|
return { id: p, kind: "direct", sessionAffinityHeader: null, stickyRouting: false, inventoryRef: "§5 OpenRouter transport" }
|
|
207
209
|
}
|
|
208
210
|
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// Runtime capability descriptors
|
|
213
|
+
//
|
|
214
|
+
// The runtime consumes `resolvePolicy(...).runtime` for gating. `policy` is the
|
|
215
|
+
// legacy telemetry/state string, so telemetry stays byte-identical. Every
|
|
216
|
+
// capability is explicit per registry entry: classification into a creator or
|
|
217
|
+
// family never implies a mutation. `legacy: false` entries always resolve to
|
|
218
|
+
// NEUTRAL_RUNTIME, so a future-looking model gains nothing until the registry
|
|
219
|
+
// explicitly says so.
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
const NEUTRAL_RUNTIME = Object.freeze({
|
|
223
|
+
policy: "neutral",
|
|
224
|
+
isNeutral: true,
|
|
225
|
+
gptCacheMetadata: false,
|
|
226
|
+
envRelocation: null,
|
|
227
|
+
thinkingIntegrity: false,
|
|
228
|
+
cacheRatio: null,
|
|
229
|
+
providerChange: null,
|
|
230
|
+
prefixDiagnostics: false,
|
|
231
|
+
openRouterAffinity: false,
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
const rt = (policy, overrides = {}) => ({
|
|
235
|
+
...NEUTRAL_RUNTIME,
|
|
236
|
+
...overrides,
|
|
237
|
+
policy,
|
|
238
|
+
isNeutral: policy === "neutral",
|
|
239
|
+
})
|
|
240
|
+
|
|
209
241
|
// ---------------------------------------------------------------------------
|
|
210
242
|
// Registry
|
|
211
243
|
//
|
|
@@ -229,6 +261,7 @@ export const POLICY_REGISTRY = [
|
|
|
229
261
|
baseline: "openai.gpt56.cache",
|
|
230
262
|
overlays: ["gpt56.prompt-cache-options"],
|
|
231
263
|
legacy: true,
|
|
264
|
+
runtime: rt("gpt56", { gptCacheMetadata: true }),
|
|
232
265
|
inventoryRef: "§1 OpenAI",
|
|
233
266
|
},
|
|
234
267
|
{
|
|
@@ -243,7 +276,8 @@ export const POLICY_REGISTRY = [
|
|
|
243
276
|
inheritsFrom: "gpt-5.6",
|
|
244
277
|
overlays: [],
|
|
245
278
|
legacy: false,
|
|
246
|
-
|
|
279
|
+
runtime: rt("neutral"),
|
|
280
|
+
note: "Documented inheritance of the GPT-5.6-and-later baseline. No CacheEngine overlay is registered for gpt-6 yet, so the runtime stays neutral.",
|
|
247
281
|
inventoryRef: "§1 OpenAI",
|
|
248
282
|
},
|
|
249
283
|
{
|
|
@@ -256,6 +290,13 @@ export const POLICY_REGISTRY = [
|
|
|
256
290
|
baseline: "zai.implicit-cache",
|
|
257
291
|
overlays: ["glm53.env-relocation"],
|
|
258
292
|
legacy: true,
|
|
293
|
+
runtime: rt("glm53", {
|
|
294
|
+
envRelocation: "glm",
|
|
295
|
+
thinkingIntegrity: true,
|
|
296
|
+
cacheRatio: "glm",
|
|
297
|
+
providerChange: "glm",
|
|
298
|
+
openRouterAffinity: true,
|
|
299
|
+
}),
|
|
259
300
|
inventoryRef: "§3 Z.AI GLM",
|
|
260
301
|
},
|
|
261
302
|
{
|
|
@@ -268,6 +309,13 @@ export const POLICY_REGISTRY = [
|
|
|
268
309
|
baseline: "xiaomi.implicit-cache",
|
|
269
310
|
overlays: ["mimo26.env-relocation"],
|
|
270
311
|
legacy: true,
|
|
312
|
+
runtime: rt("mimo26", {
|
|
313
|
+
envRelocation: "mimo",
|
|
314
|
+
cacheRatio: "mimo",
|
|
315
|
+
providerChange: "mimo",
|
|
316
|
+
prefixDiagnostics: true,
|
|
317
|
+
openRouterAffinity: true,
|
|
318
|
+
}),
|
|
271
319
|
inventoryRef: "§4 Xiaomi MiMo",
|
|
272
320
|
},
|
|
273
321
|
{
|
|
@@ -279,6 +327,7 @@ export const POLICY_REGISTRY = [
|
|
|
279
327
|
baseline: "xiaomi.implicit-cache",
|
|
280
328
|
overlays: [],
|
|
281
329
|
legacy: false,
|
|
330
|
+
runtime: rt("neutral"),
|
|
282
331
|
policyStatus: "documented-series-member-without-registered-overlay",
|
|
283
332
|
note: "Documented as a Pro mode in the same V2.6 series, but the inventory does not establish identical cache controls and CacheEngine registers no overlay for it.",
|
|
284
333
|
inventoryRef: "§4 Xiaomi MiMo",
|
|
@@ -293,6 +342,7 @@ export const POLICY_REGISTRY = [
|
|
|
293
342
|
baseline: "deepseek.kv-cache",
|
|
294
343
|
overlays: [],
|
|
295
344
|
legacy: true,
|
|
345
|
+
runtime: rt("deepseek"),
|
|
296
346
|
inventoryRef: "§2 DeepSeek",
|
|
297
347
|
},
|
|
298
348
|
]
|
|
@@ -301,13 +351,16 @@ export const POLICY_REGISTRY = [
|
|
|
301
351
|
// Explicit aliases identified by the inventory
|
|
302
352
|
// ---------------------------------------------------------------------------
|
|
303
353
|
|
|
354
|
+
// `legacy` records whether the pre-v0.4.0 classifier already matched this alias.
|
|
355
|
+
// Only legacy aliases carry runtime capabilities; newer documented aliases are
|
|
356
|
+
// resolved for information but stay runtime-neutral (no new optimization).
|
|
304
357
|
export const MODEL_ALIASES = {
|
|
305
|
-
"gpt-5.6": { canonicalId: "gpt-5.6-sol", family: "gpt-5.6", creator: "openai", inventoryRef: "§1 OpenAI" },
|
|
306
|
-
"gpt-daybreak-blue-latest": { canonicalId: "gpt-5.6-sol", family: "gpt-5.6", creator: "openai", inventoryRef: "§1 OpenAI" },
|
|
307
|
-
"gpt-daybreak-red-latest": { canonicalId: "gpt-5.6-cyber", family: "gpt-5.6", creator: "openai", inventoryRef: "§1 OpenAI" },
|
|
308
|
-
"deepseek-v4-flash": { canonicalId: "deepseek-flash", family: "deepseek", creator: "deepseek", status: "retired-legacy-id", inventoryRef: "§2 DeepSeek" },
|
|
309
|
-
"deepseek-chat": { canonicalId: null, family: "deepseek", creator: "deepseek", status: "retired", inventoryRef: "§2 DeepSeek" },
|
|
310
|
-
"deepseek-reasoner": { canonicalId: null, family: "deepseek", creator: "deepseek", status: "retired", inventoryRef: "§2 DeepSeek" },
|
|
358
|
+
"gpt-5.6": { canonicalId: "gpt-5.6-sol", family: "gpt-5.6", creator: "openai", legacy: true, inventoryRef: "§1 OpenAI" },
|
|
359
|
+
"gpt-daybreak-blue-latest": { canonicalId: "gpt-5.6-sol", family: "gpt-5.6", creator: "openai", legacy: false, inventoryRef: "§1 OpenAI" },
|
|
360
|
+
"gpt-daybreak-red-latest": { canonicalId: "gpt-5.6-cyber", family: "gpt-5.6", creator: "openai", legacy: false, inventoryRef: "§1 OpenAI" },
|
|
361
|
+
"deepseek-v4-flash": { canonicalId: "deepseek-flash", family: "deepseek", creator: "deepseek", legacy: true, status: "retired-legacy-id", inventoryRef: "§2 DeepSeek" },
|
|
362
|
+
"deepseek-chat": { canonicalId: null, family: "deepseek", creator: "deepseek", legacy: true, status: "retired", inventoryRef: "§2 DeepSeek" },
|
|
363
|
+
"deepseek-reasoner": { canonicalId: null, family: "deepseek", creator: "deepseek", legacy: true, status: "retired", inventoryRef: "§2 DeepSeek" },
|
|
311
364
|
}
|
|
312
365
|
|
|
313
366
|
// ---------------------------------------------------------------------------
|
|
@@ -320,6 +373,7 @@ function neutralResult(reason, transport) {
|
|
|
320
373
|
family: "neutral",
|
|
321
374
|
baseline: BASELINES["neutral.none"],
|
|
322
375
|
overlays: [],
|
|
376
|
+
runtime: NEUTRAL_RUNTIME,
|
|
323
377
|
transport,
|
|
324
378
|
matchType: "neutral",
|
|
325
379
|
matchReason: reason,
|
|
@@ -333,12 +387,19 @@ function overlaysFor(ids) {
|
|
|
333
387
|
return (ids ?? []).map((id) => OVERLAYS[id]).filter(Boolean)
|
|
334
388
|
}
|
|
335
389
|
|
|
390
|
+
// Only legacy entries carry runtime capabilities. A non-legacy entry (gpt-6,
|
|
391
|
+
// Pro UltraSpeed) resolves for information but stays neutral at runtime.
|
|
392
|
+
function runtimeForEntry(entry) {
|
|
393
|
+
return entry.legacy === false ? NEUTRAL_RUNTIME : entry.runtime ?? NEUTRAL_RUNTIME
|
|
394
|
+
}
|
|
395
|
+
|
|
336
396
|
function resultFromEntry(entry, matchType, matchReason, matchedId, transport) {
|
|
337
397
|
return {
|
|
338
398
|
creator: entry.creator,
|
|
339
399
|
family: entry.family,
|
|
340
400
|
baseline: BASELINES[entry.baseline] ?? null,
|
|
341
401
|
overlays: overlaysFor(entry.overlays),
|
|
402
|
+
runtime: runtimeForEntry(entry),
|
|
342
403
|
transport,
|
|
343
404
|
matchType,
|
|
344
405
|
matchReason,
|
|
@@ -348,13 +409,17 @@ function resultFromEntry(entry, matchType, matchReason, matchedId, transport) {
|
|
|
348
409
|
}
|
|
349
410
|
}
|
|
350
411
|
|
|
351
|
-
function resultFromFamily(family, creator, matchType, matchReason, matchedId, inventoryRef, note, transport) {
|
|
412
|
+
function resultFromFamily(family, creator, matchType, matchReason, matchedId, inventoryRef, note, transport, aliasLegacy) {
|
|
352
413
|
const entry = POLICY_REGISTRY.find((e) => e.family === family && e.kind !== "exact")
|
|
414
|
+
// An alias is runtime-active only when both the alias and its target family
|
|
415
|
+
// were recognized before v0.4.0.
|
|
416
|
+
const active = aliasLegacy !== false && (!entry || entry.legacy !== false)
|
|
353
417
|
return {
|
|
354
418
|
creator,
|
|
355
419
|
family,
|
|
356
420
|
baseline: entry ? BASELINES[entry.baseline] ?? null : null,
|
|
357
421
|
overlays: entry ? overlaysFor(entry.overlays) : [],
|
|
422
|
+
runtime: active && entry ? entry.runtime ?? NEUTRAL_RUNTIME : NEUTRAL_RUNTIME,
|
|
358
423
|
transport,
|
|
359
424
|
matchType,
|
|
360
425
|
matchReason,
|
|
@@ -386,7 +451,7 @@ export function resolvePolicy(model) {
|
|
|
386
451
|
const familyEntry = POLICY_REGISTRY.find((e) => e.family === alias.family && e.kind !== "exact")
|
|
387
452
|
if (familyEntry?.requiresOpenAIish && !isOpenAIish(s)) continue
|
|
388
453
|
const reason = `alias:${id}->${alias.canonicalId ?? alias.family}`
|
|
389
|
-
return resultFromFamily(alias.family, alias.creator, "exact", reason, id, alias.inventoryRef, alias.status ?? null, transport)
|
|
454
|
+
return resultFromFamily(alias.family, alias.creator, "exact", reason, id, alias.inventoryRef, alias.status ?? null, transport, alias.legacy)
|
|
390
455
|
}
|
|
391
456
|
|
|
392
457
|
// 2. Exact model ids (documented models).
|
|
@@ -415,6 +480,13 @@ export function resolvePolicy(model) {
|
|
|
415
480
|
return neutralResult("neutral:no-match", transport)
|
|
416
481
|
}
|
|
417
482
|
|
|
483
|
+
// Convenience accessor for the runtime: the legacy policy string + explicit
|
|
484
|
+
// capability flags. This is the single source the runtime gates on; it is
|
|
485
|
+
// guaranteed equal to the pre-v0.4.0 detectPolicy() classification.
|
|
486
|
+
export function resolveRuntimePolicy(model) {
|
|
487
|
+
return resolvePolicy(model).runtime
|
|
488
|
+
}
|
|
489
|
+
|
|
418
490
|
// Compatibility classification used by detectPolicy(). Reproduces the
|
|
419
491
|
// pre-v0.4.0 behavior exactly: it considers only `legacy` registry entries,
|
|
420
492
|
// excludes newer-generation/alias/exact-overlay additions, and returns a family
|
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
POLICY_REGISTRY,
|
|
56
56
|
resolveLegacyFamily,
|
|
57
57
|
resolvePolicy,
|
|
58
|
+
resolveRuntimePolicy,
|
|
58
59
|
} from "../src/cache-policy-core.mjs"
|
|
59
60
|
|
|
60
61
|
const asst = (id, read, write) => ({
|
|
@@ -1551,3 +1552,192 @@ test("registry is traceable and internally consistent", () => {
|
|
|
1551
1552
|
assert.ok(alias.family)
|
|
1552
1553
|
}
|
|
1553
1554
|
})
|
|
1555
|
+
|
|
1556
|
+
// ===========================================================================
|
|
1557
|
+
// v0.4.1 runtime policy migration (behavior preservation)
|
|
1558
|
+
//
|
|
1559
|
+
// The runtime now classifies via resolveRuntimePolicy(). These tests prove the
|
|
1560
|
+
// resolved policy equals the legacy detectPolicy() string for every supported
|
|
1561
|
+
// model, and that the hook-observable behavior (GPT cache options, <env>
|
|
1562
|
+
// relocation, OpenRouter affinity) is unchanged. Newer/unknown models must
|
|
1563
|
+
// gain no mutation.
|
|
1564
|
+
// ===========================================================================
|
|
1565
|
+
|
|
1566
|
+
const policyCoreURL = new URL("../src/cache-policy-core.mjs", import.meta.url).href
|
|
1567
|
+
|
|
1568
|
+
test("v0.4.1: resolveRuntimePolicy.policy matches detectPolicy across a broad matrix", () => {
|
|
1569
|
+
const samples = [
|
|
1570
|
+
M("openai", "gpt-5.6"),
|
|
1571
|
+
M("openai", "gpt-5.6-sol"),
|
|
1572
|
+
M("openrouter", "openai/gpt-5.6-luna"),
|
|
1573
|
+
M("openai-compatible", "gpt-5.6"),
|
|
1574
|
+
M("openai", "gpt-6-astra"),
|
|
1575
|
+
M("openai", "gpt-5.5"),
|
|
1576
|
+
M("openai", "gpt-daybreak-blue-latest"),
|
|
1577
|
+
M("openai", "gpt-daybreak-red-latest"),
|
|
1578
|
+
M("zai", "glm-5.3"),
|
|
1579
|
+
M("zai", "glm-5.3-flash"),
|
|
1580
|
+
M("zai", "glm-5.2"),
|
|
1581
|
+
M("xiaomi", "mimo-v2.6-flash"),
|
|
1582
|
+
M("xiaomi", "mimo-v2.6-pro"),
|
|
1583
|
+
M("xiaomi", "mimo-v2.6-pro-ultraspeed"),
|
|
1584
|
+
M("xiaomi", "mimo-v2.5"),
|
|
1585
|
+
M("deepseek", "deepseek-v4-pro"),
|
|
1586
|
+
M("deepseek", "deepseek-flash"),
|
|
1587
|
+
M("deepseek", "deepseek-v4-flash"),
|
|
1588
|
+
M("deepseek", "deepseek-chat"),
|
|
1589
|
+
M("openrouter", "x-ai/grok-4"),
|
|
1590
|
+
{},
|
|
1591
|
+
null,
|
|
1592
|
+
undefined,
|
|
1593
|
+
42,
|
|
1594
|
+
]
|
|
1595
|
+
for (const m of samples) {
|
|
1596
|
+
assert.equal(resolveRuntimePolicy(m).policy, detectPolicy(m))
|
|
1597
|
+
}
|
|
1598
|
+
})
|
|
1599
|
+
|
|
1600
|
+
test("v0.4.1: GPT-5.6 alias keeps the overlay but a newer alias stays runtime-neutral", () => {
|
|
1601
|
+
// Legacy alias: gpt-5.6 -> gpt-5.6-sol (was matched by the old regex).
|
|
1602
|
+
const legacy = resolveRuntimePolicy(M("openai", "gpt-5.6"))
|
|
1603
|
+
assert.equal(legacy.policy, "gpt56")
|
|
1604
|
+
assert.equal(legacy.gptCacheMetadata, true)
|
|
1605
|
+
// Newer documented alias that the old classifier did NOT match: no mutation.
|
|
1606
|
+
const newer = resolveRuntimePolicy(M("openai", "gpt-daybreak-blue-latest"))
|
|
1607
|
+
assert.equal(newer.policy, "neutral")
|
|
1608
|
+
assert.equal(newer.gptCacheMetadata, false)
|
|
1609
|
+
assert.equal(resolvePolicy(M("openai", "gpt-daybreak-blue-latest")).family, "gpt-5.6")
|
|
1610
|
+
})
|
|
1611
|
+
|
|
1612
|
+
async function runPolicyMigrationProbe() {
|
|
1613
|
+
const home = mkdtempSync(join(tmpdir(), "ce-policy-migration-"))
|
|
1614
|
+
const pluginURL = new URL("../src/cache-engine.ts", import.meta.url).href
|
|
1615
|
+
const coreURL = new URL("../src/cache-engine-core.mjs", import.meta.url).href
|
|
1616
|
+
const script = `
|
|
1617
|
+
import assert from "node:assert/strict"
|
|
1618
|
+
process.env.CACHE_ENGINE_METRICS_FILE = process.env.HOME + "/policy-migration.jsonl"
|
|
1619
|
+
const { CacheEngine } = await import(${JSON.stringify(pluginURL)})
|
|
1620
|
+
const { detectPolicy } = await import(${JSON.stringify(coreURL)})
|
|
1621
|
+
const { resolvePolicy, resolveRuntimePolicy } = await import(${JSON.stringify(policyCoreURL)})
|
|
1622
|
+
const client = {
|
|
1623
|
+
app: { log: async () => ({}) },
|
|
1624
|
+
session: { get: async () => ({ data: { parentID: null } }) },
|
|
1625
|
+
tool: { list: async () => ({ data: [] }) },
|
|
1626
|
+
}
|
|
1627
|
+
const hooks = await CacheEngine({ client, directory: process.env.HOME })
|
|
1628
|
+
assert.equal(typeof hooks["chat.params"], "function")
|
|
1629
|
+
assert.equal(typeof hooks["chat.headers"], "function")
|
|
1630
|
+
assert.equal(typeof hooks["experimental.chat.system.transform"], "function")
|
|
1631
|
+
const SYS = ["A: keep1", "B: You are powered by the model named x. The exact model ID is acme/x", "C: <env>", "D: Today's date: 2026-08-17", "E: </env>", "F: keep2"].join("\\n")
|
|
1632
|
+
const CASES = [
|
|
1633
|
+
{ name: "gpt-5.6", model: { providerID: "openai", id: "gpt-5.6", api: { id: "gpt-5.6", npm: "@ai-sdk/openai" } }, expect: { policy: "gpt56", env: false, gpt: true, header: false } },
|
|
1634
|
+
{ name: "gpt-5.6-openrouter", model: { providerID: "openrouter", id: "openai/gpt-5.6-sol", api: { id: "openai/gpt-5.6-sol" } }, expect: { policy: "gpt56", env: false, gpt: true, header: false } },
|
|
1635
|
+
{ name: "gpt-6-astra", model: { providerID: "openai", id: "gpt-6-astra", api: { id: "gpt-6-astra", npm: "@ai-sdk/openai" } }, expect: { policy: "neutral", env: false, gpt: false, header: false } },
|
|
1636
|
+
{ name: "gpt-5.5", model: { providerID: "openai", id: "gpt-5.5", api: { id: "gpt-5.5", npm: "@ai-sdk/openai" } }, expect: { policy: "neutral", env: false, gpt: false, header: false } },
|
|
1637
|
+
{ name: "gpt-daybreak-alias", model: { providerID: "openai", id: "gpt-daybreak-blue-latest", api: { id: "gpt-daybreak-blue-latest", npm: "@ai-sdk/openai" } }, expect: { policy: "neutral", env: false, gpt: false, header: false } },
|
|
1638
|
+
{ name: "deepseek-v4-pro", model: { providerID: "deepseek", id: "deepseek-v4-pro", api: { id: "deepseek-v4-pro" } }, expect: { policy: "deepseek", env: false, gpt: false, header: false } },
|
|
1639
|
+
{ name: "deepseek-flash", model: { providerID: "deepseek", id: "deepseek-flash", api: { id: "deepseek-flash" } }, expect: { policy: "deepseek", env: false, gpt: false, header: false } },
|
|
1640
|
+
{ name: "glm-5.3-direct", model: { providerID: "zai", id: "glm-5.3", api: { id: "glm-5.3" } }, expect: { policy: "glm53", env: true, gpt: false, header: false } },
|
|
1641
|
+
{ name: "glm-5.3-openrouter", model: { providerID: "openrouter", id: "z-ai/glm-5.3-flash", api: { id: "z-ai/glm-5.3-flash" } }, expect: { policy: "glm53", env: true, gpt: false, header: true } },
|
|
1642
|
+
{ name: "glm-5.2", model: { providerID: "zai", id: "glm-5.2", api: { id: "glm-5.2" } }, expect: { policy: "neutral", env: false, gpt: false, header: false } },
|
|
1643
|
+
{ name: "mimo-v2.6-flash-direct", model: { providerID: "xiaomi", id: "mimo-v2.6-flash", api: { id: "mimo-v2.6-flash" } }, expect: { policy: "mimo26", env: true, gpt: false, header: false } },
|
|
1644
|
+
{ name: "mimo-v2.6-pro-openrouter", model: { providerID: "openrouter", id: "xiaomi/mimo-v2.6-pro", api: { id: "xiaomi/mimo-v2.6-pro" } }, expect: { policy: "mimo26", env: true, gpt: false, header: true } },
|
|
1645
|
+
{ name: "mimo-v2.6-pro-ultraspeed", model: { providerID: "xiaomi", id: "mimo-v2.6-pro-ultraspeed", api: { id: "mimo-v2.6-pro-ultraspeed" } }, expect: { policy: "neutral", env: false, gpt: false, header: false } },
|
|
1646
|
+
{ name: "mimo-v2.5", model: { providerID: "xiaomi", id: "mimo-v2.5", api: { id: "mimo-v2.5" } }, expect: { policy: "neutral", env: false, gpt: false, header: false } },
|
|
1647
|
+
{ name: "unknown-provider", model: { providerID: "mystery-provider", id: "xiaomi/mimo-v2.6-flash", api: { id: "xiaomi/mimo-v2.6-flash" } }, expect: { policy: "mimo26", env: true, gpt: false, header: false } },
|
|
1648
|
+
{ name: "unknown-openrouter", model: { providerID: "openrouter", id: "acme/mystery-9", api: { id: "acme/mystery-9" } }, expect: { policy: "neutral", env: false, gpt: false, header: false } },
|
|
1649
|
+
]
|
|
1650
|
+
const results = []
|
|
1651
|
+
for (const c of CASES) {
|
|
1652
|
+
const model = c.model
|
|
1653
|
+
const sid = "ses_" + c.name
|
|
1654
|
+
const provider = { source: "config", info: { id: String(model.providerID ?? "") }, options: {} }
|
|
1655
|
+
const existing = { "User-Agent": "preserve", "x-custom": "preserve" }
|
|
1656
|
+
const paramsOut = { options: {} }
|
|
1657
|
+
const headersOut = { headers: { ...existing } }
|
|
1658
|
+
const sysOut = { system: [SYS] }
|
|
1659
|
+
await hooks["chat.params"]({ sessionID: sid, agent: "build", model, provider, message: { id: "msg-" + c.name, sessionID: sid, role: "user", content: "probe" } }, paramsOut)
|
|
1660
|
+
await hooks["experimental.chat.system.transform"]({ sessionID: sid, model, provider }, sysOut)
|
|
1661
|
+
await hooks["chat.headers"]({ sessionID: sid, agent: "build", model, provider, message: { id: "msg-" + c.name, sessionID: sid, role: "user", content: "probe" } }, headersOut)
|
|
1662
|
+
const rt = resolveRuntimePolicy(model)
|
|
1663
|
+
const existingHeadersPreserved = Object.entries(existing).every(([k, v]) => headersOut.headers[k] === v)
|
|
1664
|
+
results.push({
|
|
1665
|
+
name: c.name,
|
|
1666
|
+
runtimePolicy: rt.policy,
|
|
1667
|
+
detectPolicy: detectPolicy(model),
|
|
1668
|
+
richFamily: resolvePolicy(model).family,
|
|
1669
|
+
overlays: resolvePolicy(model).overlays.map((o) => o.id),
|
|
1670
|
+
systemRelocated: sysOut.system[0] !== SYS,
|
|
1671
|
+
gptOptionInjected: paramsOut.options.promptCacheKey !== undefined,
|
|
1672
|
+
gptOptions: paramsOut.options.promptCacheOptions ?? null,
|
|
1673
|
+
affinityHeaderAttached: headersOut.headers["x-session-id"] !== undefined,
|
|
1674
|
+
existingHeadersPreserved,
|
|
1675
|
+
expect: c.expect,
|
|
1676
|
+
})
|
|
1677
|
+
}
|
|
1678
|
+
process.stdout.write(JSON.stringify({ results }))
|
|
1679
|
+
`
|
|
1680
|
+
const stdout = execFileSync(process.execPath, ["--experimental-strip-types", "--input-type=module", "-e", script], {
|
|
1681
|
+
cwd: process.cwd(),
|
|
1682
|
+
env: { ...process.env, HOME: home },
|
|
1683
|
+
encoding: "utf8",
|
|
1684
|
+
})
|
|
1685
|
+
return JSON.parse(stdout.trim())
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
let policyMigrationProbe
|
|
1689
|
+
const policyMigrationResults = async () => (policyMigrationProbe ??= runPolicyMigrationProbe())
|
|
1690
|
+
|
|
1691
|
+
test("v0.4.1: runtime policy is the resolver's and stays equal to detectPolicy (hook path)", async () => {
|
|
1692
|
+
const { results } = await policyMigrationResults()
|
|
1693
|
+
assert.ok(results.length >= 15)
|
|
1694
|
+
for (const r of results) {
|
|
1695
|
+
assert.equal(r.runtimePolicy, r.detectPolicy, `${r.name}: resolver policy must equal legacy detectPolicy`)
|
|
1696
|
+
assert.equal(r.runtimePolicy, r.expect.policy, `${r.name}: unexpected policy`)
|
|
1697
|
+
}
|
|
1698
|
+
})
|
|
1699
|
+
|
|
1700
|
+
test("v0.4.1: every supported model keeps its pre-migration hook behavior", async () => {
|
|
1701
|
+
const { results } = await policyMigrationResults()
|
|
1702
|
+
for (const r of results) {
|
|
1703
|
+
assert.equal(r.systemRelocated, r.expect.env, `${r.name}: <env> relocation`)
|
|
1704
|
+
assert.equal(r.gptOptionInjected, r.expect.gpt, `${r.name}: GPT cache-options injection`)
|
|
1705
|
+
assert.equal(r.affinityHeaderAttached, r.expect.header, `${r.name}: OpenRouter affinity header`)
|
|
1706
|
+
// No provider in the matrix mutates or drops pre-existing headers.
|
|
1707
|
+
assert.equal(r.existingHeadersPreserved, true, `${r.name}: existing headers preserved`)
|
|
1708
|
+
}
|
|
1709
|
+
})
|
|
1710
|
+
|
|
1711
|
+
test("v0.4.1: GPT-5.6 keeps promptCacheOptions implicit/30m through the resolver", async () => {
|
|
1712
|
+
const { results } = await policyMigrationResults()
|
|
1713
|
+
const gpt = results.find((r) => r.name === "gpt-5.6")
|
|
1714
|
+
assert.deepEqual(gpt.gptOptions, { mode: "implicit", ttl: "30m" })
|
|
1715
|
+
})
|
|
1716
|
+
|
|
1717
|
+
test("v0.4.1: future-looking and unknown models gain no mutation", async () => {
|
|
1718
|
+
const { results } = await policyMigrationResults()
|
|
1719
|
+
const noMutation = [
|
|
1720
|
+
"gpt-6-astra",
|
|
1721
|
+
"gpt-daybreak-alias",
|
|
1722
|
+
"gpt-5.5",
|
|
1723
|
+
"glm-5.2",
|
|
1724
|
+
"mimo-v2.6-pro-ultraspeed",
|
|
1725
|
+
"mimo-v2.5",
|
|
1726
|
+
"unknown-openrouter",
|
|
1727
|
+
]
|
|
1728
|
+
for (const name of noMutation) {
|
|
1729
|
+
const r = results.find((x) => x.name === name)
|
|
1730
|
+
assert.ok(r, `${name} present`)
|
|
1731
|
+
assert.equal(r.gptOptionInjected, false, `${name}: no GPT options`)
|
|
1732
|
+
assert.equal(r.systemRelocated, false, `${name}: no <env> relocation`)
|
|
1733
|
+
assert.equal(r.affinityHeaderAttached, false, `${name}: no affinity header`)
|
|
1734
|
+
}
|
|
1735
|
+
})
|
|
1736
|
+
|
|
1737
|
+
test("v0.4.1: non-OpenRouter models never receive the OpenRouter header", async () => {
|
|
1738
|
+
const { results } = await policyMigrationResults()
|
|
1739
|
+
for (const name of ["glm-5.3-direct", "mimo-v2.6-flash-direct", "unknown-provider", "gpt-5.6", "deepseek-v4-pro"]) {
|
|
1740
|
+
const r = results.find((x) => x.name === name)
|
|
1741
|
+
assert.equal(r.affinityHeaderAttached, false, `${name}: no affinity header off OpenRouter`)
|
|
1742
|
+
}
|
|
1743
|
+
})
|