opencode-cache-engine 0.3.6 → 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/docs/cache-policy-inventory.md +313 -0
- package/package.json +1 -1
- package/src/cache-engine-core.mjs +16 -50
- package/src/cache-engine.ts +60 -49
- package/src/cache-policy-core.mjs +512 -0
- package/test/cache-engine.test.mjs +425 -0
|
@@ -0,0 +1,512 @@
|
|
|
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
|
+
// 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.
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Model normalization (shared with the legacy classifier)
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
// Normalize a model-like object into a searchable haystack. Accepts both the
|
|
30
|
+
// full OpenCode Model ({providerID, id, api:{id,npm}, name}) and slim test
|
|
31
|
+
// objects ({providerID, modelID/apiID}).
|
|
32
|
+
export function modelSignals(model) {
|
|
33
|
+
const m = model && typeof model === "object" ? model : {}
|
|
34
|
+
const api = m.api && typeof m.api === "object" ? m.api : {}
|
|
35
|
+
const providerID = String(m.providerID ?? m.provider ?? "")
|
|
36
|
+
const apiID = String(m.modelID ?? api.id ?? m.id ?? m.apiID ?? "")
|
|
37
|
+
const modelID = String(m.id ?? "")
|
|
38
|
+
const npm = String(api.npm ?? m.npm ?? "")
|
|
39
|
+
const name = String(m.name ?? "")
|
|
40
|
+
const slug = `${apiID} ${modelID}`.trim()
|
|
41
|
+
return { providerID, apiID, modelID, npm, name, slug: slug.toLowerCase() }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// OpenAI-ish context is required before we apply GPT-5.6 options, so we never
|
|
45
|
+
// send GPT-5.6-only fields to a non-OpenAI endpoint merely because a model
|
|
46
|
+
// string contains "gpt-5.6". A slug that explicitly starts with openai/ or
|
|
47
|
+
// azure/ (typical for openrouter/azure/openai-compatible routes) also counts
|
|
48
|
+
// because the upstream IS OpenAI. A bare openai-compatible provider with no
|
|
49
|
+
// such slug does NOT count: we must not guess.
|
|
50
|
+
export function isOpenAIish(s) {
|
|
51
|
+
const { providerID, slug, npm } = s
|
|
52
|
+
const p = providerID.toLowerCase()
|
|
53
|
+
if (p === "openai" || p === "azure") return true
|
|
54
|
+
if (slug.startsWith("openai/") || slug.startsWith("azure/")) return true
|
|
55
|
+
if (/@ai-sdk\/openai|@ai-sdk\/azure/.test(npm)) return true
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Candidate ids for exact/alias lookup. Includes the raw apiID/modelID, the
|
|
60
|
+
// lower-cased forms, and a single stripped transport/vendor prefix
|
|
61
|
+
// (e.g. "openai/gpt-5.6-luna" -> "gpt-5.6-luna", "xiaomi/mimo-v2.6-flash" ->
|
|
62
|
+
// "mimo-v2.6-flash"). Prefix stripping is a lookup convenience only and never
|
|
63
|
+
// implies cache semantics.
|
|
64
|
+
function candidateIds(s) {
|
|
65
|
+
const raw = [s.apiID, s.modelID].filter(Boolean).map((v) => String(v).toLowerCase())
|
|
66
|
+
const out = new Set()
|
|
67
|
+
for (const v of raw) {
|
|
68
|
+
out.add(v)
|
|
69
|
+
const stripped = v.replace(/^[a-z0-9._-]+\//, "")
|
|
70
|
+
if (stripped) out.add(stripped)
|
|
71
|
+
}
|
|
72
|
+
return [...out]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Baseline policies (documented cache-policy facts, not code behavior)
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
export const BASELINES = {
|
|
80
|
+
"openai.gpt56.cache": {
|
|
81
|
+
id: "openai.gpt56.cache",
|
|
82
|
+
creator: "openai",
|
|
83
|
+
appliesTo: "GPT-5.6 and later (OpenAI-documented generation boundary)",
|
|
84
|
+
automatic: true,
|
|
85
|
+
defaultMode: "implicit",
|
|
86
|
+
supportsExplicitBreakpoints: true,
|
|
87
|
+
minCacheTokens: 1024,
|
|
88
|
+
ttl: "30m",
|
|
89
|
+
cacheKeyOptional: true,
|
|
90
|
+
cacheWriteBilled: true,
|
|
91
|
+
usageFields: [
|
|
92
|
+
"input_tokens_details.cached_tokens",
|
|
93
|
+
"input_tokens_details.cache_write_tokens",
|
|
94
|
+
],
|
|
95
|
+
inventoryRef: "§1 OpenAI",
|
|
96
|
+
},
|
|
97
|
+
"deepseek.kv-cache": {
|
|
98
|
+
id: "deepseek.kv-cache",
|
|
99
|
+
creator: "deepseek",
|
|
100
|
+
appliesTo: "DeepSeek provider-wide (documented default for all users)",
|
|
101
|
+
automatic: true,
|
|
102
|
+
defaultMode: "implicit",
|
|
103
|
+
supportsExplicitBreakpoints: false,
|
|
104
|
+
minCacheTokens: null,
|
|
105
|
+
ttl: null,
|
|
106
|
+
cacheKeyOptional: false,
|
|
107
|
+
cacheWriteBilled: false,
|
|
108
|
+
usageFields: ["prompt_cache_hit_tokens", "prompt_cache_miss_tokens"],
|
|
109
|
+
inventoryRef: "§2 DeepSeek",
|
|
110
|
+
},
|
|
111
|
+
"zai.implicit-cache": {
|
|
112
|
+
id: "zai.implicit-cache",
|
|
113
|
+
creator: "z.ai",
|
|
114
|
+
appliesTo: "Z.AI service-wide implicit context caching",
|
|
115
|
+
automatic: true,
|
|
116
|
+
defaultMode: "implicit",
|
|
117
|
+
supportsExplicitBreakpoints: false,
|
|
118
|
+
minCacheTokens: null,
|
|
119
|
+
ttl: null,
|
|
120
|
+
cacheKeyOptional: false,
|
|
121
|
+
cacheWriteBilled: false,
|
|
122
|
+
usageFields: ["prompt_tokens_details.cached_tokens"],
|
|
123
|
+
inventoryRef: "§3 Z.AI GLM",
|
|
124
|
+
},
|
|
125
|
+
"xiaomi.implicit-cache": {
|
|
126
|
+
id: "xiaomi.implicit-cache",
|
|
127
|
+
creator: "xiaomi",
|
|
128
|
+
appliesTo: "Xiaomi MiMo provider-managed implicit caching",
|
|
129
|
+
automatic: true,
|
|
130
|
+
defaultMode: "implicit",
|
|
131
|
+
supportsExplicitBreakpoints: false,
|
|
132
|
+
minCacheTokens: null,
|
|
133
|
+
ttl: null,
|
|
134
|
+
cacheKeyOptional: false,
|
|
135
|
+
cacheWriteBilled: false,
|
|
136
|
+
usageFields: ["prompt_tokens_details.cached_tokens", "cache_read_input_tokens"],
|
|
137
|
+
inventoryRef: "§4 Xiaomi MiMo",
|
|
138
|
+
},
|
|
139
|
+
"neutral.none": {
|
|
140
|
+
id: "neutral.none",
|
|
141
|
+
creator: "unknown",
|
|
142
|
+
appliesTo: "No registered cache policy; no model-specific optimization implied",
|
|
143
|
+
automatic: false,
|
|
144
|
+
defaultMode: null,
|
|
145
|
+
supportsExplicitBreakpoints: false,
|
|
146
|
+
minCacheTokens: null,
|
|
147
|
+
ttl: null,
|
|
148
|
+
cacheKeyOptional: false,
|
|
149
|
+
cacheWriteBilled: false,
|
|
150
|
+
usageFields: [],
|
|
151
|
+
inventoryRef: "§6 Compatibility Matrix",
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// Model-specific overlays (CacheEngine code behaviors)
|
|
157
|
+
//
|
|
158
|
+
// An overlay is a concrete CacheEngine mutation. It is attached to a family
|
|
159
|
+
// ONLY by explicit registration; being classified into a creator/family never
|
|
160
|
+
// implies an overlay. This is what keeps "is GLM" from automatically meaning
|
|
161
|
+
// "<env> relocation".
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
export const OVERLAYS = {
|
|
165
|
+
"gpt56.prompt-cache-options": {
|
|
166
|
+
id: "gpt56.prompt-cache-options",
|
|
167
|
+
family: "gpt-5.6",
|
|
168
|
+
hook: "chat.params",
|
|
169
|
+
behavior: "inject missing promptCacheKey + promptCacheOptions(implicit, 30m)",
|
|
170
|
+
inventoryRef: "§1 OpenAI",
|
|
171
|
+
},
|
|
172
|
+
"glm53.env-relocation": {
|
|
173
|
+
id: "glm53.env-relocation",
|
|
174
|
+
family: "glm-5.3",
|
|
175
|
+
hook: "experimental.chat.system.transform",
|
|
176
|
+
behavior: "relocate the identifiable <env> block to the system tail",
|
|
177
|
+
inventoryRef: "§3 Z.AI GLM",
|
|
178
|
+
},
|
|
179
|
+
"mimo26.env-relocation": {
|
|
180
|
+
id: "mimo26.env-relocation",
|
|
181
|
+
family: "mimo-v2.6",
|
|
182
|
+
hook: "experimental.chat.system.transform",
|
|
183
|
+
behavior: "relocate the identifiable <env> block to the system tail",
|
|
184
|
+
inventoryRef: "§4 Xiaomi MiMo",
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
// Transport capabilities (routing), deliberately independent of cache policy
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
export const TRANSPORTS = {
|
|
193
|
+
openrouter: {
|
|
194
|
+
id: "openrouter",
|
|
195
|
+
kind: "openrouter",
|
|
196
|
+
sessionAffinityHeader: "x-session-id",
|
|
197
|
+
stickyRouting: true,
|
|
198
|
+
inventoryRef: "§5 OpenRouter transport",
|
|
199
|
+
},
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function resolveTransport(s) {
|
|
203
|
+
const p = String(s.providerID ?? "").toLowerCase()
|
|
204
|
+
if (p === "openrouter") return { ...TRANSPORTS.openrouter }
|
|
205
|
+
if (!p) {
|
|
206
|
+
return { id: "unknown", kind: "unknown", sessionAffinityHeader: null, stickyRouting: false, inventoryRef: "§5 OpenRouter transport" }
|
|
207
|
+
}
|
|
208
|
+
return { id: p, kind: "direct", sessionAffinityHeader: null, stickyRouting: false, inventoryRef: "§5 OpenRouter transport" }
|
|
209
|
+
}
|
|
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
|
+
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
// Registry
|
|
243
|
+
//
|
|
244
|
+
// Entries are evaluated in array order, which encodes detection priority and
|
|
245
|
+
// preserves the legacy classifier's precedence (gpt-5.6 > glm-5.3 > mimo-v2.6 >
|
|
246
|
+
// deepseek). `legacy: true` entries reproduce the pre-v0.4.0 detectPolicy()
|
|
247
|
+
// behavior exactly. `legacy: false` entries (gpt-6, Pro UltraSpeed) are
|
|
248
|
+
// available to the new resolver only, so runtime behavior is unchanged until
|
|
249
|
+
// wiring is approved.
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
export const POLICY_REGISTRY = [
|
|
253
|
+
{
|
|
254
|
+
id: "openai.gpt-5.6",
|
|
255
|
+
creator: "openai",
|
|
256
|
+
family: "gpt-5.6",
|
|
257
|
+
kind: "family",
|
|
258
|
+
pattern: /gpt-5\.6(?![\d.])/i,
|
|
259
|
+
requiresOpenAIish: true,
|
|
260
|
+
exactIds: ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-5.6-cyber"],
|
|
261
|
+
baseline: "openai.gpt56.cache",
|
|
262
|
+
overlays: ["gpt56.prompt-cache-options"],
|
|
263
|
+
legacy: true,
|
|
264
|
+
runtime: rt("gpt56", { gptCacheMetadata: true }),
|
|
265
|
+
inventoryRef: "§1 OpenAI",
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
id: "openai.gpt-6",
|
|
269
|
+
creator: "openai",
|
|
270
|
+
family: "gpt-6",
|
|
271
|
+
kind: "family",
|
|
272
|
+
pattern: /gpt-6(?![\d.])/i,
|
|
273
|
+
requiresOpenAIish: true,
|
|
274
|
+
exactIds: ["gpt-6-astra", "gpt-6-sol", "gpt-6-luna"],
|
|
275
|
+
baseline: "openai.gpt56.cache",
|
|
276
|
+
inheritsFrom: "gpt-5.6",
|
|
277
|
+
overlays: [],
|
|
278
|
+
legacy: false,
|
|
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.",
|
|
281
|
+
inventoryRef: "§1 OpenAI",
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
id: "zai.glm-5.3",
|
|
285
|
+
creator: "z.ai",
|
|
286
|
+
family: "glm-5.3",
|
|
287
|
+
kind: "family",
|
|
288
|
+
pattern: /glm-5\.3(?![\d.])/i,
|
|
289
|
+
exactIds: ["glm-5.3", "glm-5.3-flash", "glm-5.3-flashx"],
|
|
290
|
+
baseline: "zai.implicit-cache",
|
|
291
|
+
overlays: ["glm53.env-relocation"],
|
|
292
|
+
legacy: true,
|
|
293
|
+
runtime: rt("glm53", {
|
|
294
|
+
envRelocation: "glm",
|
|
295
|
+
thinkingIntegrity: true,
|
|
296
|
+
cacheRatio: "glm",
|
|
297
|
+
providerChange: "glm",
|
|
298
|
+
openRouterAffinity: true,
|
|
299
|
+
}),
|
|
300
|
+
inventoryRef: "§3 Z.AI GLM",
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
id: "xiaomi.mimo-v2.6",
|
|
304
|
+
creator: "xiaomi",
|
|
305
|
+
family: "mimo-v2.6",
|
|
306
|
+
kind: "family",
|
|
307
|
+
pattern: /mimo-v2\.6-(flash|pro)(?![\w-])/i,
|
|
308
|
+
exactIds: ["mimo-v2.6-flash", "mimo-v2.6-pro"],
|
|
309
|
+
baseline: "xiaomi.implicit-cache",
|
|
310
|
+
overlays: ["mimo26.env-relocation"],
|
|
311
|
+
legacy: true,
|
|
312
|
+
runtime: rt("mimo26", {
|
|
313
|
+
envRelocation: "mimo",
|
|
314
|
+
cacheRatio: "mimo",
|
|
315
|
+
providerChange: "mimo",
|
|
316
|
+
prefixDiagnostics: true,
|
|
317
|
+
openRouterAffinity: true,
|
|
318
|
+
}),
|
|
319
|
+
inventoryRef: "§4 Xiaomi MiMo",
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
id: "xiaomi.mimo-v2.6-pro-ultraspeed",
|
|
323
|
+
creator: "xiaomi",
|
|
324
|
+
family: "mimo-v2.6",
|
|
325
|
+
kind: "exact",
|
|
326
|
+
exactIds: ["mimo-v2.6-pro-ultraspeed"],
|
|
327
|
+
baseline: "xiaomi.implicit-cache",
|
|
328
|
+
overlays: [],
|
|
329
|
+
legacy: false,
|
|
330
|
+
runtime: rt("neutral"),
|
|
331
|
+
policyStatus: "documented-series-member-without-registered-overlay",
|
|
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.",
|
|
333
|
+
inventoryRef: "§4 Xiaomi MiMo",
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
id: "deepseek.baseline",
|
|
337
|
+
creator: "deepseek",
|
|
338
|
+
family: "deepseek",
|
|
339
|
+
kind: "creator",
|
|
340
|
+
pattern: /deepseek/i,
|
|
341
|
+
providerPattern: /deepseek/i,
|
|
342
|
+
baseline: "deepseek.kv-cache",
|
|
343
|
+
overlays: [],
|
|
344
|
+
legacy: true,
|
|
345
|
+
runtime: rt("deepseek"),
|
|
346
|
+
inventoryRef: "§2 DeepSeek",
|
|
347
|
+
},
|
|
348
|
+
]
|
|
349
|
+
|
|
350
|
+
// ---------------------------------------------------------------------------
|
|
351
|
+
// Explicit aliases identified by the inventory
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
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).
|
|
357
|
+
export const MODEL_ALIASES = {
|
|
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" },
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// ---------------------------------------------------------------------------
|
|
367
|
+
// Resolution
|
|
368
|
+
// ---------------------------------------------------------------------------
|
|
369
|
+
|
|
370
|
+
function neutralResult(reason, transport) {
|
|
371
|
+
return {
|
|
372
|
+
creator: "unknown",
|
|
373
|
+
family: "neutral",
|
|
374
|
+
baseline: BASELINES["neutral.none"],
|
|
375
|
+
overlays: [],
|
|
376
|
+
runtime: NEUTRAL_RUNTIME,
|
|
377
|
+
transport,
|
|
378
|
+
matchType: "neutral",
|
|
379
|
+
matchReason: reason,
|
|
380
|
+
matchedId: null,
|
|
381
|
+
inventoryRef: null,
|
|
382
|
+
note: null,
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function overlaysFor(ids) {
|
|
387
|
+
return (ids ?? []).map((id) => OVERLAYS[id]).filter(Boolean)
|
|
388
|
+
}
|
|
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
|
+
|
|
396
|
+
function resultFromEntry(entry, matchType, matchReason, matchedId, transport) {
|
|
397
|
+
return {
|
|
398
|
+
creator: entry.creator,
|
|
399
|
+
family: entry.family,
|
|
400
|
+
baseline: BASELINES[entry.baseline] ?? null,
|
|
401
|
+
overlays: overlaysFor(entry.overlays),
|
|
402
|
+
runtime: runtimeForEntry(entry),
|
|
403
|
+
transport,
|
|
404
|
+
matchType,
|
|
405
|
+
matchReason,
|
|
406
|
+
matchedId: matchedId ?? null,
|
|
407
|
+
inventoryRef: entry.inventoryRef ?? null,
|
|
408
|
+
note: entry.note ?? null,
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function resultFromFamily(family, creator, matchType, matchReason, matchedId, inventoryRef, note, transport, aliasLegacy) {
|
|
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)
|
|
417
|
+
return {
|
|
418
|
+
creator,
|
|
419
|
+
family,
|
|
420
|
+
baseline: entry ? BASELINES[entry.baseline] ?? null : null,
|
|
421
|
+
overlays: entry ? overlaysFor(entry.overlays) : [],
|
|
422
|
+
runtime: active && entry ? entry.runtime ?? NEUTRAL_RUNTIME : NEUTRAL_RUNTIME,
|
|
423
|
+
transport,
|
|
424
|
+
matchType,
|
|
425
|
+
matchReason,
|
|
426
|
+
matchedId: matchedId ?? null,
|
|
427
|
+
inventoryRef: inventoryRef ?? null,
|
|
428
|
+
note: note ?? null,
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Structured resolver. Returns creator, family, baseline, overlays, transport,
|
|
433
|
+
// matchType, and matchReason. Unknown models resolve to a neutral result with
|
|
434
|
+
// an empty overlay list and no model-specific optimization.
|
|
435
|
+
export function resolvePolicy(model) {
|
|
436
|
+
if (!model || typeof model !== "object") {
|
|
437
|
+
return neutralResult("neutral:invalid-model", resolveTransport({}))
|
|
438
|
+
}
|
|
439
|
+
const s = modelSignals(model)
|
|
440
|
+
const transport = resolveTransport(s)
|
|
441
|
+
if (!s.slug) return neutralResult("neutral:no-model-identity", transport)
|
|
442
|
+
|
|
443
|
+
const ids = candidateIds(s)
|
|
444
|
+
|
|
445
|
+
// 1. Explicit aliases (inventory-identified). An alias inherits its target
|
|
446
|
+
// family's context gate, so e.g. an OpenAI alias on a non-OpenAI gateway does
|
|
447
|
+
// not gain a cache policy it would not otherwise have.
|
|
448
|
+
for (const id of ids) {
|
|
449
|
+
const alias = MODEL_ALIASES[id]
|
|
450
|
+
if (!alias) continue
|
|
451
|
+
const familyEntry = POLICY_REGISTRY.find((e) => e.family === alias.family && e.kind !== "exact")
|
|
452
|
+
if (familyEntry?.requiresOpenAIish && !isOpenAIish(s)) continue
|
|
453
|
+
const reason = `alias:${id}->${alias.canonicalId ?? alias.family}`
|
|
454
|
+
return resultFromFamily(alias.family, alias.creator, "exact", reason, id, alias.inventoryRef, alias.status ?? null, transport, alias.legacy)
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// 2. Exact model ids (documented models).
|
|
458
|
+
for (const entry of POLICY_REGISTRY) {
|
|
459
|
+
if (!entry.exactIds || entry.exactIds.length === 0) continue
|
|
460
|
+
const hit = ids.find((id) => entry.exactIds.includes(id))
|
|
461
|
+
if (hit) return resultFromEntry(entry, "exact", `exact-id:${hit}`, hit, transport)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// 3. Model family / range patterns.
|
|
465
|
+
for (const entry of POLICY_REGISTRY) {
|
|
466
|
+
if (entry.kind !== "family" || !entry.pattern) continue
|
|
467
|
+
if (!entry.pattern.test(s.slug)) continue
|
|
468
|
+
if (entry.requiresOpenAIish && !isOpenAIish(s)) continue
|
|
469
|
+
return resultFromEntry(entry, "family", `family-pattern:${entry.id}`, null, transport)
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// 4. Creator baseline.
|
|
473
|
+
for (const entry of POLICY_REGISTRY) {
|
|
474
|
+
if (entry.kind !== "creator") continue
|
|
475
|
+
const slugHit = entry.pattern ? entry.pattern.test(s.slug) : false
|
|
476
|
+
const providerHit = entry.providerPattern ? entry.providerPattern.test(s.providerID) : false
|
|
477
|
+
if (slugHit || providerHit) return resultFromEntry(entry, "creator", `creator-baseline:${entry.id}`, null, transport)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return neutralResult("neutral:no-match", transport)
|
|
481
|
+
}
|
|
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
|
+
|
|
490
|
+
// Compatibility classification used by detectPolicy(). Reproduces the
|
|
491
|
+
// pre-v0.4.0 behavior exactly: it considers only `legacy` registry entries,
|
|
492
|
+
// excludes newer-generation/alias/exact-overlay additions, and returns a family
|
|
493
|
+
// string (or "neutral"). Callers map the family to a POLICY_* constant.
|
|
494
|
+
export function resolveLegacyFamily(model) {
|
|
495
|
+
if (!model || typeof model !== "object") return "neutral"
|
|
496
|
+
const s = modelSignals(model)
|
|
497
|
+
if (!s.slug) return "neutral"
|
|
498
|
+
for (const entry of POLICY_REGISTRY) {
|
|
499
|
+
if (!entry.legacy) continue
|
|
500
|
+
if (entry.kind === "family") {
|
|
501
|
+
if (!entry.pattern.test(s.slug)) continue
|
|
502
|
+
if (entry.requiresOpenAIish && !isOpenAIish(s)) continue
|
|
503
|
+
return entry.family
|
|
504
|
+
}
|
|
505
|
+
if (entry.kind === "creator") {
|
|
506
|
+
const slugHit = entry.pattern ? entry.pattern.test(s.slug) : false
|
|
507
|
+
const providerHit = entry.providerPattern ? entry.providerPattern.test(s.providerID) : false
|
|
508
|
+
if (slugHit || providerHit) return entry.family
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return "neutral"
|
|
512
|
+
}
|