opencode-cache-engine 0.3.5 → 0.4.0
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/docs/cache-policy-inventory.md +313 -0
- package/package.json +1 -1
- package/src/cache-engine-core.mjs +16 -50
- package/src/cache-policy-core.mjs +440 -0
- package/test/cache-engine.test.mjs +235 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
// cache-policy-core.mjs
|
|
2
|
+
//
|
|
3
|
+
// Pure policy registry + resolver for CacheEngine (v0.4.0).
|
|
4
|
+
//
|
|
5
|
+
// This module is the structured policy-resolution layer described by
|
|
6
|
+
// docs/cache-policy-inventory.md. It separates four concerns that used to be
|
|
7
|
+
// entangled in a single model-name-to-behavior branch:
|
|
8
|
+
//
|
|
9
|
+
// 1. creator / family classification
|
|
10
|
+
// 2. baseline cache policy (documented facts)
|
|
11
|
+
// 3. model-specific overlays (CacheEngine code behaviors, NOT implied by family)
|
|
12
|
+
// 4. transport capabilities (e.g. OpenRouter affinity), kept separate from
|
|
13
|
+
// creator cache semantics
|
|
14
|
+
//
|
|
15
|
+
// It performs NO network calls and NO runtime documentation lookups. Every
|
|
16
|
+
// registry entry is traceable to docs/cache-policy-inventory.md via
|
|
17
|
+
// `inventoryRef`. Inheritance is always explicit (`inheritsFrom`); "newer means
|
|
18
|
+
// same behavior" is never an unconditional rule.
|
|
19
|
+
//
|
|
20
|
+
// This release does not wire the resolver into runtime hooks. detectPolicy()
|
|
21
|
+
// remains the compatibility classifier until wiring is approved.
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Model normalization (shared with the legacy classifier)
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
// Normalize a model-like object into a searchable haystack. Accepts both the
|
|
28
|
+
// full OpenCode Model ({providerID, id, api:{id,npm}, name}) and slim test
|
|
29
|
+
// objects ({providerID, modelID/apiID}).
|
|
30
|
+
export function modelSignals(model) {
|
|
31
|
+
const m = model && typeof model === "object" ? model : {}
|
|
32
|
+
const api = m.api && typeof m.api === "object" ? m.api : {}
|
|
33
|
+
const providerID = String(m.providerID ?? m.provider ?? "")
|
|
34
|
+
const apiID = String(m.modelID ?? api.id ?? m.id ?? m.apiID ?? "")
|
|
35
|
+
const modelID = String(m.id ?? "")
|
|
36
|
+
const npm = String(api.npm ?? m.npm ?? "")
|
|
37
|
+
const name = String(m.name ?? "")
|
|
38
|
+
const slug = `${apiID} ${modelID}`.trim()
|
|
39
|
+
return { providerID, apiID, modelID, npm, name, slug: slug.toLowerCase() }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// OpenAI-ish context is required before we apply GPT-5.6 options, so we never
|
|
43
|
+
// send GPT-5.6-only fields to a non-OpenAI endpoint merely because a model
|
|
44
|
+
// string contains "gpt-5.6". A slug that explicitly starts with openai/ or
|
|
45
|
+
// azure/ (typical for openrouter/azure/openai-compatible routes) also counts
|
|
46
|
+
// because the upstream IS OpenAI. A bare openai-compatible provider with no
|
|
47
|
+
// such slug does NOT count: we must not guess.
|
|
48
|
+
export function isOpenAIish(s) {
|
|
49
|
+
const { providerID, slug, npm } = s
|
|
50
|
+
const p = providerID.toLowerCase()
|
|
51
|
+
if (p === "openai" || p === "azure") return true
|
|
52
|
+
if (slug.startsWith("openai/") || slug.startsWith("azure/")) return true
|
|
53
|
+
if (/@ai-sdk\/openai|@ai-sdk\/azure/.test(npm)) return true
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Candidate ids for exact/alias lookup. Includes the raw apiID/modelID, the
|
|
58
|
+
// lower-cased forms, and a single stripped transport/vendor prefix
|
|
59
|
+
// (e.g. "openai/gpt-5.6-luna" -> "gpt-5.6-luna", "xiaomi/mimo-v2.6-flash" ->
|
|
60
|
+
// "mimo-v2.6-flash"). Prefix stripping is a lookup convenience only and never
|
|
61
|
+
// implies cache semantics.
|
|
62
|
+
function candidateIds(s) {
|
|
63
|
+
const raw = [s.apiID, s.modelID].filter(Boolean).map((v) => String(v).toLowerCase())
|
|
64
|
+
const out = new Set()
|
|
65
|
+
for (const v of raw) {
|
|
66
|
+
out.add(v)
|
|
67
|
+
const stripped = v.replace(/^[a-z0-9._-]+\//, "")
|
|
68
|
+
if (stripped) out.add(stripped)
|
|
69
|
+
}
|
|
70
|
+
return [...out]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Baseline policies (documented cache-policy facts, not code behavior)
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
export const BASELINES = {
|
|
78
|
+
"openai.gpt56.cache": {
|
|
79
|
+
id: "openai.gpt56.cache",
|
|
80
|
+
creator: "openai",
|
|
81
|
+
appliesTo: "GPT-5.6 and later (OpenAI-documented generation boundary)",
|
|
82
|
+
automatic: true,
|
|
83
|
+
defaultMode: "implicit",
|
|
84
|
+
supportsExplicitBreakpoints: true,
|
|
85
|
+
minCacheTokens: 1024,
|
|
86
|
+
ttl: "30m",
|
|
87
|
+
cacheKeyOptional: true,
|
|
88
|
+
cacheWriteBilled: true,
|
|
89
|
+
usageFields: [
|
|
90
|
+
"input_tokens_details.cached_tokens",
|
|
91
|
+
"input_tokens_details.cache_write_tokens",
|
|
92
|
+
],
|
|
93
|
+
inventoryRef: "§1 OpenAI",
|
|
94
|
+
},
|
|
95
|
+
"deepseek.kv-cache": {
|
|
96
|
+
id: "deepseek.kv-cache",
|
|
97
|
+
creator: "deepseek",
|
|
98
|
+
appliesTo: "DeepSeek provider-wide (documented default for all users)",
|
|
99
|
+
automatic: true,
|
|
100
|
+
defaultMode: "implicit",
|
|
101
|
+
supportsExplicitBreakpoints: false,
|
|
102
|
+
minCacheTokens: null,
|
|
103
|
+
ttl: null,
|
|
104
|
+
cacheKeyOptional: false,
|
|
105
|
+
cacheWriteBilled: false,
|
|
106
|
+
usageFields: ["prompt_cache_hit_tokens", "prompt_cache_miss_tokens"],
|
|
107
|
+
inventoryRef: "§2 DeepSeek",
|
|
108
|
+
},
|
|
109
|
+
"zai.implicit-cache": {
|
|
110
|
+
id: "zai.implicit-cache",
|
|
111
|
+
creator: "z.ai",
|
|
112
|
+
appliesTo: "Z.AI service-wide implicit context caching",
|
|
113
|
+
automatic: true,
|
|
114
|
+
defaultMode: "implicit",
|
|
115
|
+
supportsExplicitBreakpoints: false,
|
|
116
|
+
minCacheTokens: null,
|
|
117
|
+
ttl: null,
|
|
118
|
+
cacheKeyOptional: false,
|
|
119
|
+
cacheWriteBilled: false,
|
|
120
|
+
usageFields: ["prompt_tokens_details.cached_tokens"],
|
|
121
|
+
inventoryRef: "§3 Z.AI GLM",
|
|
122
|
+
},
|
|
123
|
+
"xiaomi.implicit-cache": {
|
|
124
|
+
id: "xiaomi.implicit-cache",
|
|
125
|
+
creator: "xiaomi",
|
|
126
|
+
appliesTo: "Xiaomi MiMo provider-managed implicit caching",
|
|
127
|
+
automatic: true,
|
|
128
|
+
defaultMode: "implicit",
|
|
129
|
+
supportsExplicitBreakpoints: false,
|
|
130
|
+
minCacheTokens: null,
|
|
131
|
+
ttl: null,
|
|
132
|
+
cacheKeyOptional: false,
|
|
133
|
+
cacheWriteBilled: false,
|
|
134
|
+
usageFields: ["prompt_tokens_details.cached_tokens", "cache_read_input_tokens"],
|
|
135
|
+
inventoryRef: "§4 Xiaomi MiMo",
|
|
136
|
+
},
|
|
137
|
+
"neutral.none": {
|
|
138
|
+
id: "neutral.none",
|
|
139
|
+
creator: "unknown",
|
|
140
|
+
appliesTo: "No registered cache policy; no model-specific optimization implied",
|
|
141
|
+
automatic: false,
|
|
142
|
+
defaultMode: null,
|
|
143
|
+
supportsExplicitBreakpoints: false,
|
|
144
|
+
minCacheTokens: null,
|
|
145
|
+
ttl: null,
|
|
146
|
+
cacheKeyOptional: false,
|
|
147
|
+
cacheWriteBilled: false,
|
|
148
|
+
usageFields: [],
|
|
149
|
+
inventoryRef: "§6 Compatibility Matrix",
|
|
150
|
+
},
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Model-specific overlays (CacheEngine code behaviors)
|
|
155
|
+
//
|
|
156
|
+
// An overlay is a concrete CacheEngine mutation. It is attached to a family
|
|
157
|
+
// ONLY by explicit registration; being classified into a creator/family never
|
|
158
|
+
// implies an overlay. This is what keeps "is GLM" from automatically meaning
|
|
159
|
+
// "<env> relocation".
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
export const OVERLAYS = {
|
|
163
|
+
"gpt56.prompt-cache-options": {
|
|
164
|
+
id: "gpt56.prompt-cache-options",
|
|
165
|
+
family: "gpt-5.6",
|
|
166
|
+
hook: "chat.params",
|
|
167
|
+
behavior: "inject missing promptCacheKey + promptCacheOptions(implicit, 30m)",
|
|
168
|
+
inventoryRef: "§1 OpenAI",
|
|
169
|
+
},
|
|
170
|
+
"glm53.env-relocation": {
|
|
171
|
+
id: "glm53.env-relocation",
|
|
172
|
+
family: "glm-5.3",
|
|
173
|
+
hook: "experimental.chat.system.transform",
|
|
174
|
+
behavior: "relocate the identifiable <env> block to the system tail",
|
|
175
|
+
inventoryRef: "§3 Z.AI GLM",
|
|
176
|
+
},
|
|
177
|
+
"mimo26.env-relocation": {
|
|
178
|
+
id: "mimo26.env-relocation",
|
|
179
|
+
family: "mimo-v2.6",
|
|
180
|
+
hook: "experimental.chat.system.transform",
|
|
181
|
+
behavior: "relocate the identifiable <env> block to the system tail",
|
|
182
|
+
inventoryRef: "§4 Xiaomi MiMo",
|
|
183
|
+
},
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// Transport capabilities (routing), deliberately independent of cache policy
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
export const TRANSPORTS = {
|
|
191
|
+
openrouter: {
|
|
192
|
+
id: "openrouter",
|
|
193
|
+
kind: "openrouter",
|
|
194
|
+
sessionAffinityHeader: "x-session-id",
|
|
195
|
+
stickyRouting: true,
|
|
196
|
+
inventoryRef: "§5 OpenRouter transport",
|
|
197
|
+
},
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function resolveTransport(s) {
|
|
201
|
+
const p = String(s.providerID ?? "").toLowerCase()
|
|
202
|
+
if (p === "openrouter") return { ...TRANSPORTS.openrouter }
|
|
203
|
+
if (!p) {
|
|
204
|
+
return { id: "unknown", kind: "unknown", sessionAffinityHeader: null, stickyRouting: false, inventoryRef: "§5 OpenRouter transport" }
|
|
205
|
+
}
|
|
206
|
+
return { id: p, kind: "direct", sessionAffinityHeader: null, stickyRouting: false, inventoryRef: "§5 OpenRouter transport" }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// Registry
|
|
211
|
+
//
|
|
212
|
+
// Entries are evaluated in array order, which encodes detection priority and
|
|
213
|
+
// preserves the legacy classifier's precedence (gpt-5.6 > glm-5.3 > mimo-v2.6 >
|
|
214
|
+
// deepseek). `legacy: true` entries reproduce the pre-v0.4.0 detectPolicy()
|
|
215
|
+
// behavior exactly. `legacy: false` entries (gpt-6, Pro UltraSpeed) are
|
|
216
|
+
// available to the new resolver only, so runtime behavior is unchanged until
|
|
217
|
+
// wiring is approved.
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
export const POLICY_REGISTRY = [
|
|
221
|
+
{
|
|
222
|
+
id: "openai.gpt-5.6",
|
|
223
|
+
creator: "openai",
|
|
224
|
+
family: "gpt-5.6",
|
|
225
|
+
kind: "family",
|
|
226
|
+
pattern: /gpt-5\.6(?![\d.])/i,
|
|
227
|
+
requiresOpenAIish: true,
|
|
228
|
+
exactIds: ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-5.6-cyber"],
|
|
229
|
+
baseline: "openai.gpt56.cache",
|
|
230
|
+
overlays: ["gpt56.prompt-cache-options"],
|
|
231
|
+
legacy: true,
|
|
232
|
+
inventoryRef: "§1 OpenAI",
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
id: "openai.gpt-6",
|
|
236
|
+
creator: "openai",
|
|
237
|
+
family: "gpt-6",
|
|
238
|
+
kind: "family",
|
|
239
|
+
pattern: /gpt-6(?![\d.])/i,
|
|
240
|
+
requiresOpenAIish: true,
|
|
241
|
+
exactIds: ["gpt-6-astra", "gpt-6-sol", "gpt-6-luna"],
|
|
242
|
+
baseline: "openai.gpt56.cache",
|
|
243
|
+
inheritsFrom: "gpt-5.6",
|
|
244
|
+
overlays: [],
|
|
245
|
+
legacy: false,
|
|
246
|
+
note: "Documented inheritance of the GPT-5.6-and-later baseline. No CacheEngine overlay is registered for gpt-6 yet.",
|
|
247
|
+
inventoryRef: "§1 OpenAI",
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
id: "zai.glm-5.3",
|
|
251
|
+
creator: "z.ai",
|
|
252
|
+
family: "glm-5.3",
|
|
253
|
+
kind: "family",
|
|
254
|
+
pattern: /glm-5\.3(?![\d.])/i,
|
|
255
|
+
exactIds: ["glm-5.3", "glm-5.3-flash", "glm-5.3-flashx"],
|
|
256
|
+
baseline: "zai.implicit-cache",
|
|
257
|
+
overlays: ["glm53.env-relocation"],
|
|
258
|
+
legacy: true,
|
|
259
|
+
inventoryRef: "§3 Z.AI GLM",
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
id: "xiaomi.mimo-v2.6",
|
|
263
|
+
creator: "xiaomi",
|
|
264
|
+
family: "mimo-v2.6",
|
|
265
|
+
kind: "family",
|
|
266
|
+
pattern: /mimo-v2\.6-(flash|pro)(?![\w-])/i,
|
|
267
|
+
exactIds: ["mimo-v2.6-flash", "mimo-v2.6-pro"],
|
|
268
|
+
baseline: "xiaomi.implicit-cache",
|
|
269
|
+
overlays: ["mimo26.env-relocation"],
|
|
270
|
+
legacy: true,
|
|
271
|
+
inventoryRef: "§4 Xiaomi MiMo",
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
id: "xiaomi.mimo-v2.6-pro-ultraspeed",
|
|
275
|
+
creator: "xiaomi",
|
|
276
|
+
family: "mimo-v2.6",
|
|
277
|
+
kind: "exact",
|
|
278
|
+
exactIds: ["mimo-v2.6-pro-ultraspeed"],
|
|
279
|
+
baseline: "xiaomi.implicit-cache",
|
|
280
|
+
overlays: [],
|
|
281
|
+
legacy: false,
|
|
282
|
+
policyStatus: "documented-series-member-without-registered-overlay",
|
|
283
|
+
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
|
+
inventoryRef: "§4 Xiaomi MiMo",
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
id: "deepseek.baseline",
|
|
288
|
+
creator: "deepseek",
|
|
289
|
+
family: "deepseek",
|
|
290
|
+
kind: "creator",
|
|
291
|
+
pattern: /deepseek/i,
|
|
292
|
+
providerPattern: /deepseek/i,
|
|
293
|
+
baseline: "deepseek.kv-cache",
|
|
294
|
+
overlays: [],
|
|
295
|
+
legacy: true,
|
|
296
|
+
inventoryRef: "§2 DeepSeek",
|
|
297
|
+
},
|
|
298
|
+
]
|
|
299
|
+
|
|
300
|
+
// ---------------------------------------------------------------------------
|
|
301
|
+
// Explicit aliases identified by the inventory
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
|
|
304
|
+
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" },
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ---------------------------------------------------------------------------
|
|
314
|
+
// Resolution
|
|
315
|
+
// ---------------------------------------------------------------------------
|
|
316
|
+
|
|
317
|
+
function neutralResult(reason, transport) {
|
|
318
|
+
return {
|
|
319
|
+
creator: "unknown",
|
|
320
|
+
family: "neutral",
|
|
321
|
+
baseline: BASELINES["neutral.none"],
|
|
322
|
+
overlays: [],
|
|
323
|
+
transport,
|
|
324
|
+
matchType: "neutral",
|
|
325
|
+
matchReason: reason,
|
|
326
|
+
matchedId: null,
|
|
327
|
+
inventoryRef: null,
|
|
328
|
+
note: null,
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function overlaysFor(ids) {
|
|
333
|
+
return (ids ?? []).map((id) => OVERLAYS[id]).filter(Boolean)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function resultFromEntry(entry, matchType, matchReason, matchedId, transport) {
|
|
337
|
+
return {
|
|
338
|
+
creator: entry.creator,
|
|
339
|
+
family: entry.family,
|
|
340
|
+
baseline: BASELINES[entry.baseline] ?? null,
|
|
341
|
+
overlays: overlaysFor(entry.overlays),
|
|
342
|
+
transport,
|
|
343
|
+
matchType,
|
|
344
|
+
matchReason,
|
|
345
|
+
matchedId: matchedId ?? null,
|
|
346
|
+
inventoryRef: entry.inventoryRef ?? null,
|
|
347
|
+
note: entry.note ?? null,
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function resultFromFamily(family, creator, matchType, matchReason, matchedId, inventoryRef, note, transport) {
|
|
352
|
+
const entry = POLICY_REGISTRY.find((e) => e.family === family && e.kind !== "exact")
|
|
353
|
+
return {
|
|
354
|
+
creator,
|
|
355
|
+
family,
|
|
356
|
+
baseline: entry ? BASELINES[entry.baseline] ?? null : null,
|
|
357
|
+
overlays: entry ? overlaysFor(entry.overlays) : [],
|
|
358
|
+
transport,
|
|
359
|
+
matchType,
|
|
360
|
+
matchReason,
|
|
361
|
+
matchedId: matchedId ?? null,
|
|
362
|
+
inventoryRef: inventoryRef ?? null,
|
|
363
|
+
note: note ?? null,
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Structured resolver. Returns creator, family, baseline, overlays, transport,
|
|
368
|
+
// matchType, and matchReason. Unknown models resolve to a neutral result with
|
|
369
|
+
// an empty overlay list and no model-specific optimization.
|
|
370
|
+
export function resolvePolicy(model) {
|
|
371
|
+
if (!model || typeof model !== "object") {
|
|
372
|
+
return neutralResult("neutral:invalid-model", resolveTransport({}))
|
|
373
|
+
}
|
|
374
|
+
const s = modelSignals(model)
|
|
375
|
+
const transport = resolveTransport(s)
|
|
376
|
+
if (!s.slug) return neutralResult("neutral:no-model-identity", transport)
|
|
377
|
+
|
|
378
|
+
const ids = candidateIds(s)
|
|
379
|
+
|
|
380
|
+
// 1. Explicit aliases (inventory-identified). An alias inherits its target
|
|
381
|
+
// family's context gate, so e.g. an OpenAI alias on a non-OpenAI gateway does
|
|
382
|
+
// not gain a cache policy it would not otherwise have.
|
|
383
|
+
for (const id of ids) {
|
|
384
|
+
const alias = MODEL_ALIASES[id]
|
|
385
|
+
if (!alias) continue
|
|
386
|
+
const familyEntry = POLICY_REGISTRY.find((e) => e.family === alias.family && e.kind !== "exact")
|
|
387
|
+
if (familyEntry?.requiresOpenAIish && !isOpenAIish(s)) continue
|
|
388
|
+
const reason = `alias:${id}->${alias.canonicalId ?? alias.family}`
|
|
389
|
+
return resultFromFamily(alias.family, alias.creator, "exact", reason, id, alias.inventoryRef, alias.status ?? null, transport)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// 2. Exact model ids (documented models).
|
|
393
|
+
for (const entry of POLICY_REGISTRY) {
|
|
394
|
+
if (!entry.exactIds || entry.exactIds.length === 0) continue
|
|
395
|
+
const hit = ids.find((id) => entry.exactIds.includes(id))
|
|
396
|
+
if (hit) return resultFromEntry(entry, "exact", `exact-id:${hit}`, hit, transport)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// 3. Model family / range patterns.
|
|
400
|
+
for (const entry of POLICY_REGISTRY) {
|
|
401
|
+
if (entry.kind !== "family" || !entry.pattern) continue
|
|
402
|
+
if (!entry.pattern.test(s.slug)) continue
|
|
403
|
+
if (entry.requiresOpenAIish && !isOpenAIish(s)) continue
|
|
404
|
+
return resultFromEntry(entry, "family", `family-pattern:${entry.id}`, null, transport)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// 4. Creator baseline.
|
|
408
|
+
for (const entry of POLICY_REGISTRY) {
|
|
409
|
+
if (entry.kind !== "creator") continue
|
|
410
|
+
const slugHit = entry.pattern ? entry.pattern.test(s.slug) : false
|
|
411
|
+
const providerHit = entry.providerPattern ? entry.providerPattern.test(s.providerID) : false
|
|
412
|
+
if (slugHit || providerHit) return resultFromEntry(entry, "creator", `creator-baseline:${entry.id}`, null, transport)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return neutralResult("neutral:no-match", transport)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Compatibility classification used by detectPolicy(). Reproduces the
|
|
419
|
+
// pre-v0.4.0 behavior exactly: it considers only `legacy` registry entries,
|
|
420
|
+
// excludes newer-generation/alias/exact-overlay additions, and returns a family
|
|
421
|
+
// string (or "neutral"). Callers map the family to a POLICY_* constant.
|
|
422
|
+
export function resolveLegacyFamily(model) {
|
|
423
|
+
if (!model || typeof model !== "object") return "neutral"
|
|
424
|
+
const s = modelSignals(model)
|
|
425
|
+
if (!s.slug) return "neutral"
|
|
426
|
+
for (const entry of POLICY_REGISTRY) {
|
|
427
|
+
if (!entry.legacy) continue
|
|
428
|
+
if (entry.kind === "family") {
|
|
429
|
+
if (!entry.pattern.test(s.slug)) continue
|
|
430
|
+
if (entry.requiresOpenAIish && !isOpenAIish(s)) continue
|
|
431
|
+
return entry.family
|
|
432
|
+
}
|
|
433
|
+
if (entry.kind === "creator") {
|
|
434
|
+
const slugHit = entry.pattern ? entry.pattern.test(s.slug) : false
|
|
435
|
+
const providerHit = entry.providerPattern ? entry.providerPattern.test(s.providerID) : false
|
|
436
|
+
if (slugHit || providerHit) return entry.family
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return "neutral"
|
|
440
|
+
}
|
|
@@ -48,6 +48,14 @@ import {
|
|
|
48
48
|
toolFingerprint,
|
|
49
49
|
toolWireFingerprint,
|
|
50
50
|
} from "../src/cache-engine-core.mjs"
|
|
51
|
+
import {
|
|
52
|
+
BASELINES,
|
|
53
|
+
MODEL_ALIASES,
|
|
54
|
+
OVERLAYS,
|
|
55
|
+
POLICY_REGISTRY,
|
|
56
|
+
resolveLegacyFamily,
|
|
57
|
+
resolvePolicy,
|
|
58
|
+
} from "../src/cache-policy-core.mjs"
|
|
51
59
|
|
|
52
60
|
const asst = (id, read, write) => ({
|
|
53
61
|
info: { id, role: "assistant", tokens: { cache: { read, write } } },
|
|
@@ -1316,3 +1324,230 @@ test("affinity telemetry preserves non-OpenRouter families and reports provider
|
|
|
1316
1324
|
assert.equal(glmChange.to.providerID, "zai")
|
|
1317
1325
|
assert.equal(glmChange.policy, POLICY_GLM53)
|
|
1318
1326
|
})
|
|
1327
|
+
|
|
1328
|
+
// ===========================================================================
|
|
1329
|
+
// v0.4.0 policy registry + resolvePolicy() (research-backed, pure, unwired)
|
|
1330
|
+
//
|
|
1331
|
+
// The inventory (docs/cache-policy-inventory.md) is the authority. These tests
|
|
1332
|
+
// assert the structured resolution layer and that detectPolicy() remains
|
|
1333
|
+
// byte-compatible with its pre-v0.4.0 behavior.
|
|
1334
|
+
// ===========================================================================
|
|
1335
|
+
|
|
1336
|
+
const overlayIds = (r) => r.overlays.map((o) => o.id)
|
|
1337
|
+
const baseId = (r) => (r.baseline ? r.baseline.id : null)
|
|
1338
|
+
|
|
1339
|
+
test("resolvePolicy: GPT-5.6 exact + inventory aliases resolve to the gpt-5.6 family", () => {
|
|
1340
|
+
const exact = resolvePolicy(M("openai", "gpt-5.6-sol"))
|
|
1341
|
+
assert.equal(exact.creator, "openai")
|
|
1342
|
+
assert.equal(exact.family, "gpt-5.6")
|
|
1343
|
+
assert.equal(exact.matchType, "exact")
|
|
1344
|
+
assert.equal(baseId(exact), "openai.gpt56.cache")
|
|
1345
|
+
assert.deepEqual(overlayIds(exact), ["gpt56.prompt-cache-options"])
|
|
1346
|
+
assert.equal(exact.transport.kind, "direct")
|
|
1347
|
+
|
|
1348
|
+
// Inventory alias: gpt-5.6 -> gpt-5.6-sol
|
|
1349
|
+
const alias = resolvePolicy(M("openai", "gpt-5.6"))
|
|
1350
|
+
assert.equal(alias.family, "gpt-5.6")
|
|
1351
|
+
assert.equal(alias.matchType, "exact")
|
|
1352
|
+
assert.ok(alias.matchReason.startsWith("alias:gpt-5.6"))
|
|
1353
|
+
assert.deepEqual(overlayIds(alias), ["gpt56.prompt-cache-options"])
|
|
1354
|
+
|
|
1355
|
+
// OpenRouter-prefixed documented variant resolves by exact id (prefix stripped)
|
|
1356
|
+
const orVariant = resolvePolicy(M("openrouter", "openai/gpt-5.6-luna"))
|
|
1357
|
+
assert.equal(orVariant.family, "gpt-5.6")
|
|
1358
|
+
assert.equal(orVariant.matchType, "exact")
|
|
1359
|
+
})
|
|
1360
|
+
|
|
1361
|
+
test("resolvePolicy: GPT-6 resolves via explicit documented inheritance, with no overlay", () => {
|
|
1362
|
+
const r = resolvePolicy(M("openrouter", "openai/gpt-6-luna"))
|
|
1363
|
+
assert.equal(r.creator, "openai")
|
|
1364
|
+
assert.equal(r.family, "gpt-6")
|
|
1365
|
+
assert.equal(baseId(r), "openai.gpt56.cache")
|
|
1366
|
+
assert.deepEqual(overlayIds(r), [])
|
|
1367
|
+
assert.equal(resolvePolicy(M("openai", "gpt-6-astra")).family, "gpt-6")
|
|
1368
|
+
|
|
1369
|
+
// Inheritance is explicit, not "newer means same": the registry records it.
|
|
1370
|
+
const gpt6 = POLICY_REGISTRY.find((e) => e.family === "gpt-6")
|
|
1371
|
+
assert.equal(gpt6.inheritsFrom, "gpt-5.6")
|
|
1372
|
+
assert.ok(gpt6.inventoryRef)
|
|
1373
|
+
// And no overlay is implied by that inheritance.
|
|
1374
|
+
assert.deepEqual(gpt6.overlays, [])
|
|
1375
|
+
})
|
|
1376
|
+
|
|
1377
|
+
test("resolvePolicy: GPT-6 is not classified by the legacy detectPolicy wrapper", () => {
|
|
1378
|
+
// Intentional: the new layer knows gpt-6, the compatibility wrapper does not.
|
|
1379
|
+
assert.equal(detectPolicy(M("openai", "gpt-6-astra")), POLICY_NEUTRAL)
|
|
1380
|
+
assert.equal(resolvePolicy(M("openai", "gpt-6-astra")).family, "gpt-6")
|
|
1381
|
+
})
|
|
1382
|
+
|
|
1383
|
+
test("resolvePolicy: pre-5.6 GPT negative controls are neutral with no overlays", () => {
|
|
1384
|
+
for (const id of ["gpt-5.5", "gpt-5.4", "gpt-5.2", "gpt-5.1", "gpt-5", "gpt-4.1", "gpt-4o"]) {
|
|
1385
|
+
const r = resolvePolicy(M("openai", id))
|
|
1386
|
+
assert.equal(r.family, "neutral", `${id} must be neutral`)
|
|
1387
|
+
assert.deepEqual(overlayIds(r), [])
|
|
1388
|
+
assert.equal(r.matchType, "neutral")
|
|
1389
|
+
}
|
|
1390
|
+
})
|
|
1391
|
+
|
|
1392
|
+
test("resolvePolicy: DeepSeek V4 / V4.1 resolve to the creator baseline (passive, no overlay)", () => {
|
|
1393
|
+
const v4 = resolvePolicy(M("deepseek", "deepseek-v4-pro"))
|
|
1394
|
+
assert.equal(v4.creator, "deepseek")
|
|
1395
|
+
assert.equal(v4.family, "deepseek")
|
|
1396
|
+
assert.equal(v4.matchType, "creator")
|
|
1397
|
+
assert.equal(baseId(v4), "deepseek.kv-cache")
|
|
1398
|
+
assert.deepEqual(overlayIds(v4), [])
|
|
1399
|
+
|
|
1400
|
+
assert.equal(resolvePolicy(M("deepseek", "deepseek-flash")).family, "deepseek")
|
|
1401
|
+
|
|
1402
|
+
// Inventory alias: retired deepseek-v4-flash -> deepseek-flash
|
|
1403
|
+
const legacy = resolvePolicy(M("deepseek", "deepseek-v4-flash"))
|
|
1404
|
+
assert.equal(legacy.family, "deepseek")
|
|
1405
|
+
assert.ok(legacy.matchReason.startsWith("alias:deepseek-v4-flash"))
|
|
1406
|
+
|
|
1407
|
+
// pre-V4 negative control still resolves to the creator baseline
|
|
1408
|
+
assert.equal(resolvePolicy(M("deepseek", "deepseek-chat")).family, "deepseek")
|
|
1409
|
+
})
|
|
1410
|
+
|
|
1411
|
+
test("resolvePolicy: GLM-5.3 + documented 5.3 variants carry the overlay; 5.2 and earlier do not", () => {
|
|
1412
|
+
const r = resolvePolicy(M("zai", "glm-5.3"))
|
|
1413
|
+
assert.equal(r.creator, "z.ai")
|
|
1414
|
+
assert.equal(r.family, "glm-5.3")
|
|
1415
|
+
assert.equal(baseId(r), "zai.implicit-cache")
|
|
1416
|
+
assert.deepEqual(overlayIds(r), ["glm53.env-relocation"])
|
|
1417
|
+
|
|
1418
|
+
assert.equal(resolvePolicy(M("zai", "glm-5.3-flash")).family, "glm-5.3")
|
|
1419
|
+
assert.equal(resolvePolicy(M("z-ai", "glm-5.3-flashx")).family, "glm-5.3")
|
|
1420
|
+
|
|
1421
|
+
for (const id of ["glm-5.2", "glm-5.1", "glm-5", "glm-4.7", "glm-4.6", "glm-4.5"]) {
|
|
1422
|
+
const g = resolvePolicy(M("zai", id))
|
|
1423
|
+
assert.equal(g.family, "neutral", `${id} must be neutral`)
|
|
1424
|
+
assert.deepEqual(overlayIds(g), [])
|
|
1425
|
+
}
|
|
1426
|
+
})
|
|
1427
|
+
|
|
1428
|
+
test("resolvePolicy: MiMo V2.6 Flash/Pro carry the overlay; Pro UltraSpeed is an explicit no-overlay series member", () => {
|
|
1429
|
+
const flash = resolvePolicy(M("xiaomi", "mimo-v2.6-flash"))
|
|
1430
|
+
assert.equal(flash.creator, "xiaomi")
|
|
1431
|
+
assert.equal(flash.family, "mimo-v2.6")
|
|
1432
|
+
assert.equal(baseId(flash), "xiaomi.implicit-cache")
|
|
1433
|
+
assert.deepEqual(overlayIds(flash), ["mimo26.env-relocation"])
|
|
1434
|
+
|
|
1435
|
+
const pro = resolvePolicy(M("xiaomi", "mimo-v2.6-pro"))
|
|
1436
|
+
assert.equal(pro.family, "mimo-v2.6")
|
|
1437
|
+
assert.deepEqual(overlayIds(pro), ["mimo26.env-relocation"])
|
|
1438
|
+
|
|
1439
|
+
// Documented as the same V2.6 series but with NO registered overlay.
|
|
1440
|
+
const ultraspeed = resolvePolicy(M("xiaomi", "mimo-v2.6-pro-ultraspeed"))
|
|
1441
|
+
assert.equal(ultraspeed.family, "mimo-v2.6")
|
|
1442
|
+
assert.equal(ultraspeed.matchType, "exact")
|
|
1443
|
+
assert.deepEqual(overlayIds(ultraspeed), [])
|
|
1444
|
+
const uEntry = POLICY_REGISTRY.find((e) => e.id === "xiaomi.mimo-v2.6-pro-ultraspeed")
|
|
1445
|
+
assert.equal(uEntry.policyStatus, "documented-series-member-without-registered-overlay")
|
|
1446
|
+
|
|
1447
|
+
const v25 = resolvePolicy(M("xiaomi", "mimo-v2.5"))
|
|
1448
|
+
assert.equal(v25.family, "neutral")
|
|
1449
|
+
assert.deepEqual(overlayIds(v25), [])
|
|
1450
|
+
})
|
|
1451
|
+
|
|
1452
|
+
test("resolvePolicy: overlays are never implied by creator/family classification", () => {
|
|
1453
|
+
// Being GLM/MiMo/DeepSeek does not by itself grant a transformation overlay.
|
|
1454
|
+
assert.deepEqual(overlayIds(resolvePolicy(M("zai", "glm-4.6"))), [])
|
|
1455
|
+
assert.deepEqual(overlayIds(resolvePolicy(M("xiaomi", "mimo-v2.5"))), [])
|
|
1456
|
+
assert.deepEqual(overlayIds(resolvePolicy(M("deepseek", "deepseek-v4-pro"))), [])
|
|
1457
|
+
// A hypothetical future GLM does not silently inherit the 5.3 overlay.
|
|
1458
|
+
assert.deepEqual(overlayIds(resolvePolicy(M("zai", "glm-5.4"))), [])
|
|
1459
|
+
})
|
|
1460
|
+
|
|
1461
|
+
test("resolvePolicy: malformed and unknown model ids resolve safely", () => {
|
|
1462
|
+
for (const bad of [undefined, null, {}, 42, "gpt-5.6", [], true]) {
|
|
1463
|
+
const r = resolvePolicy(bad)
|
|
1464
|
+
assert.equal(r.family, "neutral")
|
|
1465
|
+
assert.equal(r.matchType, "neutral")
|
|
1466
|
+
assert.equal(baseId(r), "neutral.none")
|
|
1467
|
+
assert.deepEqual(r.overlays, [])
|
|
1468
|
+
}
|
|
1469
|
+
// A provider with no model identity gets no family.
|
|
1470
|
+
assert.equal(resolvePolicy({ providerID: "openai" }).family, "neutral")
|
|
1471
|
+
})
|
|
1472
|
+
|
|
1473
|
+
test("resolvePolicy: unknown providers/creators do not gain policy from transport identity", () => {
|
|
1474
|
+
const orUnknown = resolvePolicy(M("openrouter", "acme/mystery-model-9"))
|
|
1475
|
+
assert.equal(orUnknown.family, "neutral")
|
|
1476
|
+
assert.deepEqual(orUnknown.overlays, [])
|
|
1477
|
+
assert.equal(orUnknown.transport.kind, "openrouter")
|
|
1478
|
+
assert.equal(orUnknown.transport.sessionAffinityHeader, "x-session-id")
|
|
1479
|
+
|
|
1480
|
+
// gpt-5.6 on a non-OpenAI gateway stays neutral even though the id looks OpenAI
|
|
1481
|
+
assert.equal(resolvePolicy(M("some-gateway", "gpt-5.6")).family, "neutral")
|
|
1482
|
+
assert.equal(resolvePolicy(M("xiaomi", "not-a-mimo")).family, "neutral")
|
|
1483
|
+
})
|
|
1484
|
+
|
|
1485
|
+
test("resolvePolicy: transport is computed independently from cache policy", () => {
|
|
1486
|
+
const ds = resolvePolicy(M("deepseek", "deepseek-v4-pro"))
|
|
1487
|
+
assert.equal(ds.transport.kind, "direct")
|
|
1488
|
+
assert.equal(ds.transport.sessionAffinityHeader, null)
|
|
1489
|
+
|
|
1490
|
+
const or = resolvePolicy(M("openrouter", "openai/gpt-5.6-luna"))
|
|
1491
|
+
assert.equal(or.family, "gpt-5.6")
|
|
1492
|
+
assert.equal(or.transport.kind, "openrouter")
|
|
1493
|
+
assert.equal(or.transport.sessionAffinityHeader, "x-session-id")
|
|
1494
|
+
|
|
1495
|
+
const zai = resolvePolicy(M("zai", "glm-5.3"))
|
|
1496
|
+
assert.equal(zai.transport.kind, "direct")
|
|
1497
|
+
assert.equal(zai.transport.sessionAffinityHeader, null)
|
|
1498
|
+
|
|
1499
|
+
const noProvider = resolvePolicy({ modelID: "gpt-6-astra" })
|
|
1500
|
+
assert.equal(noProvider.transport.kind, "unknown")
|
|
1501
|
+
})
|
|
1502
|
+
|
|
1503
|
+
test("resolvePolicy is pure and does not mutate its input", () => {
|
|
1504
|
+
const model = Object.freeze({ providerID: "openai", modelID: "gpt-5.6-sol" })
|
|
1505
|
+
const r = resolvePolicy(model)
|
|
1506
|
+
assert.equal(r.family, "gpt-5.6")
|
|
1507
|
+
assert.equal(model.modelID, "gpt-5.6-sol")
|
|
1508
|
+
})
|
|
1509
|
+
|
|
1510
|
+
test("detectPolicy remains compatible with the legacy family resolution", () => {
|
|
1511
|
+
const legacyMap = {
|
|
1512
|
+
"gpt-5.6": POLICY_GPT56,
|
|
1513
|
+
"glm-5.3": POLICY_GLM53,
|
|
1514
|
+
"mimo-v2.6": POLICY_MIMO26,
|
|
1515
|
+
deepseek: POLICY_DEEPSEEK,
|
|
1516
|
+
}
|
|
1517
|
+
const samples = [
|
|
1518
|
+
M("openrouter", "openai/gpt-5.6-luna"),
|
|
1519
|
+
M("openai", "gpt-5.6"),
|
|
1520
|
+
M("openai-compatible", "gpt-5.6"),
|
|
1521
|
+
M("openai", "gpt-5.5"),
|
|
1522
|
+
M("zai", "glm-5.3-flash"),
|
|
1523
|
+
M("zai", "glm-4.6"),
|
|
1524
|
+
M("xiaomi", "mimo-v2.6-flash"),
|
|
1525
|
+
M("xiaomi", "mimo-v2.6-pro-ultraspeed"),
|
|
1526
|
+
M("xiaomi", "mimo-v2.5"),
|
|
1527
|
+
M("deepseek", "deepseek-chat"),
|
|
1528
|
+
M("openrouter", "x-ai/grok-4"),
|
|
1529
|
+
M("anthropic", "claude-sonnet-4-5"),
|
|
1530
|
+
{},
|
|
1531
|
+
null,
|
|
1532
|
+
undefined,
|
|
1533
|
+
42,
|
|
1534
|
+
]
|
|
1535
|
+
for (const m of samples) {
|
|
1536
|
+
const expected = legacyMap[resolveLegacyFamily(m)] ?? POLICY_NEUTRAL
|
|
1537
|
+
assert.equal(detectPolicy(m), expected)
|
|
1538
|
+
}
|
|
1539
|
+
})
|
|
1540
|
+
|
|
1541
|
+
test("registry is traceable and internally consistent", () => {
|
|
1542
|
+
for (const entry of POLICY_REGISTRY) {
|
|
1543
|
+
assert.ok(entry.inventoryRef, `${entry.id} must cite the inventory`)
|
|
1544
|
+
assert.ok(BASELINES[entry.baseline], `${entry.id} baseline must exist`)
|
|
1545
|
+
for (const overlayId of entry.overlays) {
|
|
1546
|
+
assert.ok(OVERLAYS[overlayId], `${entry.id} overlay ${overlayId} must exist`)
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
for (const [id, alias] of Object.entries(MODEL_ALIASES)) {
|
|
1550
|
+
assert.ok(alias.inventoryRef, `alias ${id} must cite the inventory`)
|
|
1551
|
+
assert.ok(alias.family)
|
|
1552
|
+
}
|
|
1553
|
+
})
|