modelrig 0.1.0 → 0.3.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.
Files changed (310) hide show
  1. package/CHANGELOG.md +158 -0
  2. package/README.md +53 -11
  3. package/data/PROVENANCE.md +19 -0
  4. package/data/pricing-manual.json +22 -0
  5. package/data/pricing-map.json +46362 -0
  6. package/data/pricing-overlay.json +419 -0
  7. package/dist/adapters/anthropic.d.ts +50 -6
  8. package/dist/adapters/anthropic.d.ts.map +1 -1
  9. package/dist/adapters/anthropic.js +123 -29
  10. package/dist/adapters/build-adapters.d.ts +11 -0
  11. package/dist/adapters/build-adapters.d.ts.map +1 -0
  12. package/dist/adapters/build-adapters.js +45 -0
  13. package/dist/adapters/deepseek.d.ts +6 -0
  14. package/dist/adapters/deepseek.d.ts.map +1 -1
  15. package/dist/adapters/deepseek.js +54 -1
  16. package/dist/adapters/gemini.d.ts +10 -0
  17. package/dist/adapters/gemini.d.ts.map +1 -1
  18. package/dist/adapters/gemini.js +78 -7
  19. package/dist/adapters/grok.d.ts +9 -0
  20. package/dist/adapters/grok.d.ts.map +1 -1
  21. package/dist/adapters/grok.js +67 -3
  22. package/dist/adapters/openai-compat.d.ts +6 -0
  23. package/dist/adapters/openai-compat.d.ts.map +1 -1
  24. package/dist/adapters/openai-compat.js +58 -1
  25. package/dist/adapters/openai-stream.d.ts +58 -0
  26. package/dist/adapters/openai-stream.d.ts.map +1 -0
  27. package/dist/adapters/openai-stream.js +120 -0
  28. package/dist/adapters/openai.d.ts +6 -0
  29. package/dist/adapters/openai.d.ts.map +1 -1
  30. package/dist/adapters/openai.js +60 -1
  31. package/dist/adapters/sampling.d.ts +28 -0
  32. package/dist/adapters/sampling.d.ts.map +1 -0
  33. package/dist/adapters/sampling.js +34 -0
  34. package/dist/adapters/tool-mapping.d.ts +92 -0
  35. package/dist/adapters/tool-mapping.d.ts.map +1 -0
  36. package/dist/adapters/tool-mapping.js +143 -0
  37. package/dist/artifacts/artifact.d.ts +137 -0
  38. package/dist/artifacts/artifact.d.ts.map +1 -0
  39. package/dist/artifacts/artifact.js +377 -0
  40. package/dist/artifacts/content-uploader.d.ts +87 -0
  41. package/dist/artifacts/content-uploader.d.ts.map +1 -0
  42. package/dist/artifacts/content-uploader.js +124 -0
  43. package/dist/artifacts/index.d.ts +63 -0
  44. package/dist/artifacts/index.d.ts.map +1 -0
  45. package/dist/artifacts/index.js +80 -0
  46. package/dist/artifacts/run-context.d.ts +129 -0
  47. package/dist/artifacts/run-context.d.ts.map +1 -0
  48. package/dist/artifacts/run-context.js +208 -0
  49. package/dist/artifacts/scrub.d.ts +60 -0
  50. package/dist/artifacts/scrub.d.ts.map +1 -0
  51. package/dist/artifacts/scrub.js +139 -0
  52. package/dist/artifacts/step.d.ts +26 -0
  53. package/dist/artifacts/step.d.ts.map +1 -0
  54. package/dist/artifacts/step.js +37 -0
  55. package/dist/artifacts/taint.d.ts +63 -0
  56. package/dist/artifacts/taint.d.ts.map +1 -0
  57. package/dist/artifacts/taint.js +76 -0
  58. package/dist/bakeoff/cache-sim.d.ts +99 -0
  59. package/dist/bakeoff/cache-sim.d.ts.map +1 -0
  60. package/dist/bakeoff/cache-sim.js +126 -0
  61. package/dist/bakeoff/eval-cases.d.ts +96 -0
  62. package/dist/bakeoff/eval-cases.d.ts.map +1 -0
  63. package/dist/bakeoff/eval-cases.js +105 -0
  64. package/dist/bakeoff/report.d.ts +41 -0
  65. package/dist/bakeoff/report.d.ts.map +1 -0
  66. package/dist/bakeoff/report.js +142 -0
  67. package/dist/bakeoff/samples.d.ts +15 -0
  68. package/dist/bakeoff/samples.d.ts.map +1 -1
  69. package/dist/bakeoff/samples.js +20 -0
  70. package/dist/cli.d.ts +1 -3
  71. package/dist/cli.d.ts.map +1 -1
  72. package/dist/cli.js +134 -116
  73. package/dist/config.d.ts +100 -0
  74. package/dist/config.d.ts.map +1 -1
  75. package/dist/config.js +104 -0
  76. package/dist/deprecations.d.ts +32 -0
  77. package/dist/deprecations.d.ts.map +1 -0
  78. package/dist/deprecations.js +59 -0
  79. package/dist/gateway/http-protocol.d.ts +175 -0
  80. package/dist/gateway/http-protocol.d.ts.map +1 -0
  81. package/dist/gateway/http-protocol.js +105 -0
  82. package/dist/index.d.ts +106 -19
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +266 -138
  85. package/dist/init/finder.d.ts +15 -0
  86. package/dist/init/finder.d.ts.map +1 -1
  87. package/dist/init/finder.js +46 -0
  88. package/dist/init/init-cli.d.ts +10 -0
  89. package/dist/init/init-cli.d.ts.map +1 -0
  90. package/dist/init/init-cli.js +86 -0
  91. package/dist/init/observe-cli.d.ts +19 -0
  92. package/dist/init/observe-cli.d.ts.map +1 -0
  93. package/dist/init/observe-cli.js +73 -0
  94. package/dist/init/observe.d.ts +81 -0
  95. package/dist/init/observe.d.ts.map +1 -0
  96. package/dist/init/observe.js +245 -0
  97. package/dist/registry/build.d.ts +94 -1
  98. package/dist/registry/build.d.ts.map +1 -1
  99. package/dist/registry/build.js +125 -4
  100. package/dist/registry/cache-policy.d.ts +94 -0
  101. package/dist/registry/cache-policy.d.ts.map +1 -0
  102. package/dist/registry/cache-policy.js +238 -0
  103. package/dist/registry/call-notes.d.ts.map +1 -1
  104. package/dist/registry/call-notes.js +40 -2
  105. package/dist/registry/effective-rate.d.ts +151 -0
  106. package/dist/registry/effective-rate.d.ts.map +1 -0
  107. package/dist/registry/effective-rate.js +156 -0
  108. package/dist/registry/facts.d.ts.map +1 -1
  109. package/dist/registry/facts.js +16 -0
  110. package/dist/registry/parity.d.ts +26 -0
  111. package/dist/registry/parity.d.ts.map +1 -1
  112. package/dist/registry/parity.js +43 -2
  113. package/dist/registry/pricing-shape.d.ts +113 -0
  114. package/dist/registry/pricing-shape.d.ts.map +1 -0
  115. package/dist/registry/pricing-shape.js +123 -0
  116. package/dist/registry/pricing.d.ts +25 -1
  117. package/dist/registry/pricing.d.ts.map +1 -1
  118. package/dist/registry/pricing.js +64 -6
  119. package/dist/rigindex/bootstrap.d.ts +36 -0
  120. package/dist/rigindex/bootstrap.d.ts.map +1 -0
  121. package/dist/rigindex/bootstrap.js +97 -0
  122. package/dist/rigindex/fit.d.ts +24 -0
  123. package/dist/rigindex/fit.d.ts.map +1 -0
  124. package/dist/rigindex/fit.js +76 -0
  125. package/dist/rigindex/gates.d.ts +52 -0
  126. package/dist/rigindex/gates.d.ts.map +1 -0
  127. package/dist/rigindex/gates.js +109 -0
  128. package/dist/rigindex/graph.d.ts +31 -0
  129. package/dist/rigindex/graph.d.ts.map +1 -0
  130. package/dist/rigindex/graph.js +120 -0
  131. package/dist/rigindex/index.d.ts +23 -0
  132. package/dist/rigindex/index.d.ts.map +1 -0
  133. package/dist/rigindex/index.js +112 -0
  134. package/dist/rigindex/types.d.ts +76 -0
  135. package/dist/rigindex/types.d.ts.map +1 -0
  136. package/dist/rigindex/types.js +8 -0
  137. package/dist/routes/cache-lint.d.ts +41 -0
  138. package/dist/routes/cache-lint.d.ts.map +1 -0
  139. package/dist/routes/cache-lint.js +81 -0
  140. package/dist/routes/loader.d.ts +48 -0
  141. package/dist/routes/loader.d.ts.map +1 -1
  142. package/dist/routes/loader.js +167 -0
  143. package/dist/routes/resolved-bundle.d.ts +79 -0
  144. package/dist/routes/resolved-bundle.d.ts.map +1 -0
  145. package/dist/routes/resolved-bundle.js +148 -0
  146. package/dist/routes/sampling-config.d.ts +32 -0
  147. package/dist/routes/sampling-config.d.ts.map +1 -0
  148. package/dist/routes/sampling-config.js +91 -0
  149. package/dist/run/ajv.d.ts +22 -0
  150. package/dist/run/ajv.d.ts.map +1 -0
  151. package/dist/run/ajv.js +32 -0
  152. package/dist/run/cache-handle.d.ts +58 -0
  153. package/dist/run/cache-handle.d.ts.map +1 -0
  154. package/dist/run/cache-handle.js +54 -0
  155. package/dist/run/cache-stamp.d.ts +65 -0
  156. package/dist/run/cache-stamp.d.ts.map +1 -0
  157. package/dist/run/cache-stamp.js +0 -0
  158. package/dist/run/capture-write.d.ts +33 -0
  159. package/dist/run/capture-write.d.ts.map +1 -0
  160. package/dist/run/capture-write.js +55 -0
  161. package/dist/run/cost.d.ts.map +1 -1
  162. package/dist/run/cost.js +1 -1
  163. package/dist/run/dispatch.d.ts +99 -0
  164. package/dist/run/dispatch.d.ts.map +1 -0
  165. package/dist/run/dispatch.js +191 -0
  166. package/dist/run/raw.d.ts +130 -0
  167. package/dist/run/raw.d.ts.map +1 -0
  168. package/dist/run/raw.js +363 -0
  169. package/dist/run/record-attempt.d.ts +40 -0
  170. package/dist/run/record-attempt.d.ts.map +1 -0
  171. package/dist/run/record-attempt.js +99 -0
  172. package/dist/run/resolve.d.ts +75 -2
  173. package/dist/run/resolve.d.ts.map +1 -1
  174. package/dist/run/resolve.js +126 -4
  175. package/dist/run/run.d.ts +14 -0
  176. package/dist/run/run.d.ts.map +1 -1
  177. package/dist/run/run.js +0 -0
  178. package/dist/run/stream.d.ts +49 -0
  179. package/dist/run/stream.d.ts.map +1 -0
  180. package/dist/run/stream.js +87 -0
  181. package/dist/run/validate.d.ts.map +1 -1
  182. package/dist/run/validate.js +6 -7
  183. package/dist/savings/cache-ledger.d.ts +108 -0
  184. package/dist/savings/cache-ledger.d.ts.map +1 -0
  185. package/dist/savings/cache-ledger.js +174 -0
  186. package/dist/savings/cache-reconcile.d.ts +64 -0
  187. package/dist/savings/cache-reconcile.d.ts.map +1 -0
  188. package/dist/savings/cache-reconcile.js +59 -0
  189. package/dist/savings/cache-stats.d.ts +63 -0
  190. package/dist/savings/cache-stats.d.ts.map +1 -0
  191. package/dist/savings/cache-stats.js +108 -0
  192. package/dist/savings/emit.d.ts +13 -0
  193. package/dist/savings/emit.d.ts.map +1 -1
  194. package/dist/savings/emit.js +25 -0
  195. package/dist/savings/swap-execute.d.ts +14 -0
  196. package/dist/savings/swap-execute.d.ts.map +1 -1
  197. package/dist/savings/swap-execute.js +44 -1
  198. package/dist/savings/swaps-client.d.ts +14 -0
  199. package/dist/savings/swaps-client.d.ts.map +1 -1
  200. package/dist/savings/swaps-client.js +16 -2
  201. package/dist/settings/api-fetcher.d.ts +39 -0
  202. package/dist/settings/api-fetcher.d.ts.map +1 -0
  203. package/dist/settings/api-fetcher.js +92 -0
  204. package/dist/settings/index.d.ts +10 -0
  205. package/dist/settings/index.d.ts.map +1 -0
  206. package/dist/settings/index.js +15 -0
  207. package/dist/settings/task-settings.d.ts +91 -0
  208. package/dist/settings/task-settings.d.ts.map +1 -0
  209. package/dist/settings/task-settings.js +116 -0
  210. package/dist/status.d.ts +77 -0
  211. package/dist/status.d.ts.map +1 -0
  212. package/dist/status.js +113 -0
  213. package/dist/telemetry/artifact-store.d.ts +48 -0
  214. package/dist/telemetry/artifact-store.d.ts.map +1 -0
  215. package/dist/telemetry/artifact-store.js +189 -0
  216. package/dist/telemetry/cache-miss-streak.d.ts +79 -0
  217. package/dist/telemetry/cache-miss-streak.d.ts.map +1 -0
  218. package/dist/telemetry/cache-miss-streak.js +99 -0
  219. package/dist/telemetry/capture.d.ts +3 -0
  220. package/dist/telemetry/capture.d.ts.map +1 -1
  221. package/dist/telemetry/capture.js +15 -8
  222. package/dist/telemetry/exporter-setup.d.ts +28 -0
  223. package/dist/telemetry/exporter-setup.d.ts.map +1 -0
  224. package/dist/telemetry/exporter-setup.js +34 -0
  225. package/dist/telemetry/exporter.d.ts +75 -0
  226. package/dist/telemetry/exporter.d.ts.map +1 -1
  227. package/dist/telemetry/exporter.js +324 -47
  228. package/dist/telemetry/health.d.ts +76 -0
  229. package/dist/telemetry/health.d.ts.map +1 -0
  230. package/dist/telemetry/health.js +92 -0
  231. package/dist/telemetry/ingest-protocol.d.ts +152 -3
  232. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  233. package/dist/telemetry/ingest-protocol.js +204 -4
  234. package/dist/telemetry/load-error-report.d.ts +24 -0
  235. package/dist/telemetry/load-error-report.d.ts.map +1 -0
  236. package/dist/telemetry/load-error-report.js +50 -0
  237. package/dist/telemetry/otel-map.d.ts +121 -0
  238. package/dist/telemetry/otel-map.d.ts.map +1 -0
  239. package/dist/telemetry/otel-map.js +224 -0
  240. package/dist/telemetry/otel-normalize.d.ts +104 -0
  241. package/dist/telemetry/otel-normalize.d.ts.map +1 -0
  242. package/dist/telemetry/otel-normalize.js +530 -0
  243. package/dist/telemetry/otlp-emitter.d.ts +52 -0
  244. package/dist/telemetry/otlp-emitter.d.ts.map +1 -0
  245. package/dist/telemetry/otlp-emitter.js +144 -0
  246. package/dist/telemetry/otlp-proto.d.ts +29 -0
  247. package/dist/telemetry/otlp-proto.d.ts.map +1 -0
  248. package/dist/telemetry/otlp-proto.js +266 -0
  249. package/dist/telemetry/sinks.d.ts +37 -1
  250. package/dist/telemetry/sinks.d.ts.map +1 -1
  251. package/dist/telemetry/sinks.js +121 -30
  252. package/dist/telemetry/store.d.ts +28 -1
  253. package/dist/telemetry/store.d.ts.map +1 -1
  254. package/dist/telemetry/store.js +218 -3
  255. package/dist/tenancy/api-key-format.d.ts +15 -5
  256. package/dist/tenancy/api-key-format.d.ts.map +1 -1
  257. package/dist/tenancy/api-key-format.js +24 -5
  258. package/dist/types/config-teaching.d.ts +62 -0
  259. package/dist/types/config-teaching.d.ts.map +1 -0
  260. package/dist/types/config-teaching.js +183 -0
  261. package/dist/types/provider.d.ts +147 -1
  262. package/dist/types/provider.d.ts.map +1 -1
  263. package/dist/types/provider.js +21 -1
  264. package/dist/types/route.d.ts +79 -1
  265. package/dist/types/route.d.ts.map +1 -1
  266. package/dist/types/telemetry.d.ts +117 -0
  267. package/dist/types/telemetry.d.ts.map +1 -1
  268. package/dist/validate-cli.d.ts +12 -0
  269. package/dist/validate-cli.d.ts.map +1 -0
  270. package/dist/validate-cli.js +57 -0
  271. package/dist/verify/capture.d.ts +66 -0
  272. package/dist/verify/capture.d.ts.map +1 -0
  273. package/dist/verify/capture.js +180 -0
  274. package/dist/verify/cli-run.d.ts +23 -0
  275. package/dist/verify/cli-run.d.ts.map +1 -0
  276. package/dist/verify/cli-run.js +57 -0
  277. package/dist/verify/diff.d.ts +48 -0
  278. package/dist/verify/diff.d.ts.map +1 -0
  279. package/dist/verify/diff.js +157 -0
  280. package/dist/verify/index.d.ts +17 -0
  281. package/dist/verify/index.d.ts.map +1 -0
  282. package/dist/verify/index.js +24 -0
  283. package/dist/verify/report.d.ts +14 -0
  284. package/dist/verify/report.d.ts.map +1 -0
  285. package/dist/verify/report.js +88 -0
  286. package/dist/verify/types.d.ts +101 -0
  287. package/dist/verify/types.d.ts.map +1 -0
  288. package/dist/verify/types.js +17 -0
  289. package/dist/verify/verify.d.ts +29 -0
  290. package/dist/verify/verify.d.ts.map +1 -0
  291. package/dist/verify/verify.js +148 -0
  292. package/dist/watch/evaluate.d.ts +162 -3
  293. package/dist/watch/evaluate.d.ts.map +1 -1
  294. package/dist/watch/evaluate.js +61 -0
  295. package/dist/watch/monitor.d.ts +116 -0
  296. package/dist/watch/monitor.d.ts.map +1 -0
  297. package/dist/watch/monitor.js +199 -0
  298. package/dist/watch/proposals.d.ts +20 -4
  299. package/dist/watch/proposals.d.ts.map +1 -1
  300. package/dist/watch/proposals.js +31 -3
  301. package/dist/watch/run.d.ts +33 -0
  302. package/dist/watch/run.d.ts.map +1 -1
  303. package/dist/watch/run.js +39 -0
  304. package/package.json +7 -1
  305. package/registry/LICENSE +20 -0
  306. package/registry/leaderboard.json +3639 -0
  307. package/registry/registry.json +7263 -0
  308. package/dist/telemetry/supabase-exporter.d.ts +0 -64
  309. package/dist/telemetry/supabase-exporter.d.ts.map +0 -1
  310. package/dist/telemetry/supabase-exporter.js +0 -211
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ /**
3
+ * Cache Policy Registry — cache-aware-routing spec §3, [Briefing §7].
4
+ *
5
+ * Per-provider cache SEMANTICS as versioned DATA, never routing code: TTLs,
6
+ * refresh-on-read, minimum-cacheable prefix, key mechanism, telemetry dialect,
7
+ * endpoint affinity. A provider rule change (OpenAI shipping paid writes,
8
+ * Anthropic GA-ing the 1h TTL) becomes a data edit here with a new `as_of`, not
9
+ * a code change anywhere else — the whole point of the registry.
10
+ *
11
+ * TWO HOMES, both pre-existing (spec §3):
12
+ * • RATES ride pricing — `ModelPricingV2.base.{cache_read,cache_write,
13
+ * cache_write_1h}` and flat `ModelPricing.cacheWrite*UsdPerTok`, entered via
14
+ * the pricing sweep with source+as_of. NOT here.
15
+ * • POLICY (this file) is the non-price semantics.
16
+ *
17
+ * L5 (fail-closed): every field cites a first-party page (research §2.1, fetched
18
+ * 2026-08-18). A value we could not verify is `null` (or `cache_supported:
19
+ * false`) — never a guess. `getCachePolicy` returns a fail-closed
20
+ * `cache_supported: false` policy for any provider not seeded, identical posture
21
+ * to the zero-coverage gates.
22
+ *
23
+ * ── CROSS-REFERENCES (no import — the probes package is standalone since P2) ──
24
+ * • `min_cacheable_tokens` is reconciled by hand against the probes package's
25
+ * `MIN_CACHEABLE_TOKENS` (packages/modelrig-probes/src/caching.ts, PR #157),
26
+ * which cites the same first-party pages. See MIN_CACHEABLE_RECONCILIATION
27
+ * below for the value-by-value comparison and the two deliberate deltas
28
+ * (anthropic, gemini) flagged for the architect to converge if desired. A
29
+ * reciprocal comment lives in caching.ts.
30
+ * • OBSERVED cache facts (`cache_hit_rate`, `mean_cached_tokens`,
31
+ * `declared_caching_unrealized`) are materialized separately in
32
+ * `src/registry/build.ts` from probe/production telemetry — the DECLARED
33
+ * policy here is the counterpart truth, and the §5.6 streak alert (WS-C)
34
+ * cross-references `declared_caching_unrealized` (same smell, other plane).
35
+ */
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.failClosedCachePolicy = failClosedCachePolicy;
38
+ exports.getCachePolicy = getCachePolicy;
39
+ exports.seededCachePolicyProviders = seededCachePolicyProviders;
40
+ const AS_OF = "2026-08-18";
41
+ /**
42
+ * min_cacheable_tokens reconciliation vs probes `MIN_CACHEABLE_TOKENS`
43
+ * (packages/modelrig-probes/src/caching.ts, PR #157). Same first-party pages;
44
+ * the two tables cannot share by import (probes is standalone). The probe table
45
+ * is a conservative PROBE-ELIGIBILITY gate — it decides whether to SPEND a
46
+ * paired probe call, so it errs toward the larger provider-wide bound. This
47
+ * registry records the documented minimum for the models ModelRig actually
48
+ * serves, for accounting/policy. Value-by-value (registry ⇄ probes):
49
+ *
50
+ * anthropic 1024 ⇄ 2048 DELTA — registry = Sonnet 5 / Opus 4.8 minimum
51
+ * (the deployed frontier; Opus 5/Fable 5 = 512,
52
+ * Haiku up to 4096); probes uses a haiku-safe 2048.
53
+ * openai 1024 ⇄ 1024 agree (automatic prefix engages ≥1024).
54
+ * gemini 4096 ⇄ 2048 DELTA — registry = implicit-caching 4096-token
55
+ * minimum prefix (research §2.1 / spec §1.4); probes
56
+ * uses 2048. FLAGGED for the architect to converge.
57
+ * deepseek 64 ⇄ 64 agree (64-token cache blocks).
58
+ * grok null ⇄ 1024* registry = no documented minimum (L5 null);
59
+ * deepinfra null ⇄ 1024* probes falls these to DEFAULT_MIN_CACHEABLE_TOKENS
60
+ * fireworks null ⇄ 1024* (1024) as an operational gate. Deliberate divergence:
61
+ * the registry will not assert a minimum it cannot
62
+ * source first-party.
63
+ */
64
+ const POLICIES = {
65
+ // Anthropic — explicit cache_control (≤4 breakpoints) + automatic top-level
66
+ // mode. Writes 1.25× (5m) / 2× (1h); reads 0.1×. platform.claude.com
67
+ // prompt-caching, fetched 2026-08-18 (research §2.1).
68
+ anthropic: {
69
+ cache_supported: true,
70
+ mode: "explicit_breakpoints",
71
+ default_ttl_seconds: 300, // 5-min ephemeral, refreshes free on read
72
+ extended_ttl_seconds: 3600, // 1h GA at the 2× write multiplier
73
+ ttl_refresh_on_read: true, // documented: a read refreshes the 5m window
74
+ min_cacheable_tokens: 1024, // Sonnet 5 / Opus 4.8 (deployed); see reconciliation
75
+ cache_key_param: "cache_control",
76
+ reports_read_tokens: true, // cache_read_input_tokens
77
+ reports_write_tokens: true, // cache_creation_input_tokens (+ 5m/1h split)
78
+ usage_dialect: "anthropic",
79
+ provider_affinity_required: false, // model-scoped/workspace-isolated, no endpoint stickiness
80
+ source: "platform.claude.com/docs prompt-caching (fetched 2026-08-18)",
81
+ as_of: AS_OF,
82
+ policy_version: AS_OF,
83
+ },
84
+ // OpenAI — automatic prefix caching (≤5.5 baseline). Reads 0.1×; writes free
85
+ // ≤5.5, and GPT-5.6+ adds explicit breakpoints + PAID writes (1.25×, exact
86
+ // 30-min TTL) reported as input_tokens_details.cache_write_tokens.
87
+ // developers.openai.com prompt-caching, fetched 2026-08-18 (research §2.1).
88
+ openai: {
89
+ cache_supported: true,
90
+ mode: "automatic_prefix", // 5.6+ additionally supports explicit_breakpoints
91
+ default_ttl_seconds: 600, // 5-10 min inactivity window (5.6+: exact 30 min)
92
+ extended_ttl_seconds: 86400, // optional 24h retention, same price
93
+ ttl_refresh_on_read: true, // inactivity-based window — activity extends it (≤5.5)
94
+ min_cacheable_tokens: 1024, // automatic prefix engages at ≥1024 tokens
95
+ cache_key_param: "prompt_cache_key", // routing hint (not required to cache)
96
+ reports_read_tokens: true, // prompt_tokens_details.cached_tokens
97
+ reports_write_tokens: true, // input_tokens_details.cache_write_tokens (5.6+; 0 on ≤5.5)
98
+ usage_dialect: "openai_details",
99
+ provider_affinity_required: true, // ~15 req/min/key affinity (research §2.5)
100
+ source: "developers.openai.com/docs prompt-caching (fetched 2026-08-18)",
101
+ as_of: AS_OF,
102
+ policy_version: AS_OF,
103
+ },
104
+ // DeepSeek — automatic disk cache, always on. Reads ~0.03× (deepest in
105
+ // market); writes free. Usage is a hit/miss PARTITION (not a subset).
106
+ // api-docs.deepseek.com, fetched 2026-08-18 (research §2.1).
107
+ deepseek: {
108
+ cache_supported: true,
109
+ mode: "automatic_prefix",
110
+ default_ttl_seconds: null, // "hours to days", no exact documented TTL
111
+ extended_ttl_seconds: null,
112
+ ttl_refresh_on_read: false, // undocumented → conservative
113
+ min_cacheable_tokens: 64, // 64-token cache blocks (matches probes)
114
+ cache_key_param: null, // pure automatic, no key
115
+ reports_read_tokens: true, // prompt_cache_hit_tokens
116
+ reports_write_tokens: false, // writes free; miss tokens are the non-cached input
117
+ usage_dialect: "deepseek_partition",
118
+ provider_affinity_required: false,
119
+ source: "api-docs.deepseek.com context-caching (fetched 2026-08-18)",
120
+ as_of: AS_OF,
121
+ policy_version: AS_OF,
122
+ },
123
+ // Gemini — implicit caching, default-on 2.5+. Reads 0.1×; writes free. TTL is
124
+ // undocumented best-effort. EXCLUDED from WS-C `cache: auto` (its adapter
125
+ // treats any cache.key as a cachedContents RESOURCE NAME and drops
126
+ // systemInstruction+tools — gemini.ts:145). ai.google.dev, fetched 2026-08-18.
127
+ gemini: {
128
+ cache_supported: true,
129
+ mode: "automatic_prefix",
130
+ default_ttl_seconds: null, // undocumented best-effort
131
+ extended_ttl_seconds: null,
132
+ ttl_refresh_on_read: false, // undocumented → conservative
133
+ min_cacheable_tokens: 4096, // implicit-caching minimum prefix (spec §1.4); see reconciliation
134
+ cache_key_param: null, // implicit only; the cachedContents API is a different (explicit) product
135
+ reports_read_tokens: true, // usageMetadata.cachedContentTokenCount
136
+ reports_write_tokens: false,
137
+ usage_dialect: "gemini_metadata",
138
+ provider_affinity_required: false,
139
+ source: "ai.google.dev/gemini-api context-caching (fetched 2026-08-18)",
140
+ as_of: AS_OF,
141
+ policy_version: AS_OF,
142
+ },
143
+ // Grok (xAI) — automatic caching; reads 0.25× (Grok 4.6, model-varying),
144
+ // writes free. TTL ~5 min (3rd-party). docs.x.ai, fetched 2026-08-18.
145
+ grok: {
146
+ cache_supported: true,
147
+ mode: "automatic_prefix",
148
+ default_ttl_seconds: 300, // ~5 min (3rd-party; not first-party confirmed)
149
+ extended_ttl_seconds: null,
150
+ ttl_refresh_on_read: false, // undocumented → conservative
151
+ min_cacheable_tokens: null, // no documented minimum (L5)
152
+ cache_key_param: "prompt_cache_key", // x-grok-conv-id / prompt_cache_key hint
153
+ reports_read_tokens: true, // prompt_tokens_details.cached_tokens (compat)
154
+ reports_write_tokens: false,
155
+ usage_dialect: "openai_details",
156
+ provider_affinity_required: false, // team-scoped, undocumented
157
+ source: "docs.x.ai prompt-caching (fetched 2026-08-18)",
158
+ as_of: AS_OF,
159
+ policy_version: AS_OF,
160
+ },
161
+ // Fireworks — automatic caching on all models; reads ~0.5× serverless / free
162
+ // dedicated. Usage rides HTTP HEADERS (fireworks-prompt-tokens /
163
+ // -cached-prompt-tokens), NOT the usage JSON the openai-compat adapter reads —
164
+ // so cache tokens are NOT observable on our path today (header lift is a
165
+ // spec non-goal / flagged follow-up). docs.fireworks.ai, fetched 2026-08-18.
166
+ fireworks: {
167
+ cache_supported: true,
168
+ mode: "automatic_prefix",
169
+ default_ttl_seconds: null, // minutes-to-hours LRU, no exact
170
+ extended_ttl_seconds: null,
171
+ ttl_refresh_on_read: false,
172
+ min_cacheable_tokens: null,
173
+ cache_key_param: null,
174
+ reports_read_tokens: false, // header-based; not read by our usage-JSON path yet
175
+ reports_write_tokens: false,
176
+ usage_dialect: "fireworks_headers",
177
+ provider_affinity_required: true, // x-session-affinity
178
+ source: "docs.fireworks.ai prompt-caching (fetched 2026-08-18)",
179
+ as_of: AS_OF,
180
+ policy_version: AS_OF,
181
+ },
182
+ // DeepInfra — caching on SELECT models only (DeepSeek V3.x, Qwen3, some
183
+ // Claude), cached rates unverified first-party. Fail-closed to
184
+ // cache_supported:false, matching DeepInfra's existing pricing posture
185
+ // (pricing-sweep sweepDeepInfra: base-only, richer fields unverified, L5).
186
+ deepinfra: {
187
+ cache_supported: false,
188
+ mode: "automatic_prefix",
189
+ default_ttl_seconds: null,
190
+ extended_ttl_seconds: null,
191
+ ttl_refresh_on_read: false,
192
+ min_cacheable_tokens: null,
193
+ cache_key_param: null,
194
+ reports_read_tokens: false,
195
+ reports_write_tokens: false,
196
+ usage_dialect: "openai_details",
197
+ provider_affinity_required: false,
198
+ source: "deepinfra — cached rates unverified first-party (fail-closed, L5)",
199
+ as_of: AS_OF,
200
+ policy_version: AS_OF,
201
+ },
202
+ };
203
+ /** The fail-closed policy for any provider not seeded (spec §3): caching is
204
+ * assumed OFF until a first-party source says otherwise — identical posture to
205
+ * the zero-coverage gates. `usage_dialect` defaults to the OpenAI shape only as
206
+ * a type-satisfying placeholder; `cache_supported: false` means no consumer
207
+ * reads it. */
208
+ function failClosedCachePolicy() {
209
+ return {
210
+ cache_supported: false,
211
+ mode: "automatic_prefix",
212
+ default_ttl_seconds: null,
213
+ extended_ttl_seconds: null,
214
+ ttl_refresh_on_read: false,
215
+ min_cacheable_tokens: null,
216
+ cache_key_param: null,
217
+ reports_read_tokens: false,
218
+ reports_write_tokens: false,
219
+ usage_dialect: "openai_details",
220
+ provider_affinity_required: false,
221
+ source: "unseeded provider — fail-closed (no first-party cache policy)",
222
+ as_of: AS_OF,
223
+ policy_version: AS_OF,
224
+ };
225
+ }
226
+ /**
227
+ * The cache policy for a provider — the seeded first-party record, or a
228
+ * fail-closed `cache_supported: false` policy when the provider is not seeded
229
+ * (spec §3). Provider-level in v1; model-specific overrides land with the
230
+ * conversational-gateway phase.
231
+ */
232
+ function getCachePolicy(provider) {
233
+ return POLICIES[provider] ?? failClosedCachePolicy();
234
+ }
235
+ /** Every seeded provider — for tests and the console policy surface (WS-D). */
236
+ function seededCachePolicyProviders() {
237
+ return Object.keys(POLICIES);
238
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"call-notes.d.ts","sourceRoot":"","sources":["../../src/registry/call-notes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA8BH,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAoChE"}
1
+ {"version":3,"file":"call-notes.d.ts","sourceRoot":"","sources":["../../src/registry/call-notes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAwCH,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAkEhE"}
@@ -15,6 +15,15 @@ const GEMINI_THINKING_CAPABLE = new Set([
15
15
  "gemini-3.0-pro-preview",
16
16
  "gemini-3.1-flash-preview",
17
17
  ]);
18
+ /**
19
+ * Models whose published probe measurements predate the provider-default
20
+ * reasoning policy (architect ruling, 2026-08-10): probe cycles 001–002
21
+ * pinned thinkingConfig thinkingLevel "high" on this set, inflating output
22
+ * tokens (billed as output) and therefore the effective-cost figure. The only
23
+ * board model affected. Remove each entry once its cycle-003 re-measure
24
+ * (provider-default thinking) is published.
25
+ */
26
+ const GEMINI_PROBED_AT_THINKING_HIGH = new Set(["gemini-3.1-pro-preview"]);
18
27
  const GEMINI_COMMON = [
19
28
  "temperature must be 1.0 — lower values cause output looping on Gemini 3 models",
20
29
  "native strict structured output via responseJsonSchema; very complex schemas can be rejected as INVALID_ARGUMENT (complexity ceiling)",
@@ -47,25 +56,54 @@ function callNotesFor(modelKey) {
47
56
  GEMINI_THINKING_CAPABLE.has(model)
48
57
  ? "accepts thinkingConfig (thinking level); thinking tokens are billed as output and count against maxOutputTokens"
49
58
  : "does NOT accept thinkingConfig — sending it returns 400 (thinking models only)",
59
+ ...(GEMINI_PROBED_AT_THINKING_HIGH.has(model)
60
+ ? [
61
+ "probe cycles 001-002 measured this model at thinking level high; probes run at provider-default thinking from cycle 003, and the published cost figure is re-measured then",
62
+ ]
63
+ : []),
50
64
  ];
51
65
  case "openai": {
66
+ // The Pro tier is Responses-API only (v1/chat/completions returns "not a
67
+ // chat model") — probed via the responses caller (coverage follow-ups
68
+ // 2026-08-11). Reasoning tokens bill as output at the Pro rate.
69
+ const responsesOnly = model.endsWith("-pro");
52
70
  const flexEligible = !model.includes("-mini") &&
53
71
  !model.includes("-nano") &&
72
+ !responsesOnly &&
54
73
  ["gpt-5.4", "gpt-5.2", "gpt-5.1", "gpt-5", "gpt-4.1"].some((p) => model.startsWith(p));
55
74
  return [
56
75
  ...OPENAI_COMMON,
76
+ ...(responsesOnly
77
+ ? [
78
+ "Responses-API only — v1/chat/completions rejects it ('not a chat model'); probed via the v1/responses caller with strict json_schema output; reasoning tokens are billed as output at the Pro rate",
79
+ ]
80
+ : []),
57
81
  flexEligible
58
82
  ? "flex service tier eligible — discounted, sheddable capacity; served tier is echoed on the stream"
59
- : "NOT flex-eligible (mini/nano and older families) — flex requests are silently served at default tier",
83
+ : "NOT flex-eligible (mini/nano, Pro, and older families) — flex requests are silently served at default tier",
60
84
  ];
61
85
  }
62
- case "deepseek":
86
+ case "deepseek": {
87
+ // Alias resolution (coverage follow-ups, verified 2026-08-11 against the
88
+ // live models list + a call to each): DeepSeek's model list now shows only
89
+ // deepseek-v4-flash / deepseek-v4-pro; the classic `deepseek-chat` and
90
+ // `deepseek-reasoner` are rolling ALIASES that both currently resolve to
91
+ // deepseek-v4-flash (the API echoes `model: "deepseek-v4-flash"`). They are
92
+ // alive, not retired — kept on the board with this note. reasoner is
93
+ // v4-flash in reasoning mode.
94
+ const aliasNote = model === "deepseek-chat"
95
+ ? "rolling alias — currently serves deepseek-v4-flash (API echoes model=deepseek-v4-flash, verified 2026-08-11); alive despite being absent from the models list"
96
+ : model === "deepseek-reasoner"
97
+ ? "rolling alias — currently serves deepseek-v4-flash in reasoning mode (API echoes model=deepseek-v4-flash, verified 2026-08-11); alive despite being absent from the models list"
98
+ : "listed as a current DeepSeek model (deepseek-v4-*), 2026-08-11";
63
99
  return [
64
100
  ...DEEPSEEK_COMMON,
65
101
  model.includes("reasoner")
66
102
  ? "emits reasoning_content (chain-of-thought) deltas — exclude them from the JSON buffer; only delta.content is the answer"
67
103
  : "max_tokens up to 64K — full capacity for long structured reports",
104
+ aliasNote,
68
105
  ];
106
+ }
69
107
  default:
70
108
  return [];
71
109
  }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Cache-adjusted route economics — cache-aware-routing spec §4.2–4.3, WS-B.
3
+ * PURE: no store, no clock, no network. Every function here is total and
4
+ * deterministic; the whole module is the math the architect reviews pre-merge.
5
+ *
6
+ * ── THE REGRESSION PIN (load-bearing, WS-A discipline) ────────────────────
7
+ * With observed hit rate h = 0 and write rate w = 0, `effectiveInputRate`
8
+ * returns the LIST input rate BYTE-IDENTICALLY, and every per-call saving
9
+ * collapses to 0. A route with no cache activity therefore prices exactly as it
10
+ * did before this module existed. The property test locks this: the cache-aware
11
+ * lens NEVER changes a number until real cache tokens appear.
12
+ *
13
+ * ── FAIL-CLOSED RATES (dev-rule L5) ───────────────────────────────────────
14
+ * An unverified cache-read or cache-write rate degrades to the INPUT rate
15
+ * (1.0×), never a guessed premium/discount — the same posture as
16
+ * `estimateCostUsd`. So an unpriced cache component can only make caching look
17
+ * NEUTRAL, never spuriously attractive.
18
+ *
19
+ * Units: every rate is USD PER TOKEN (matching `ModelPricing.inputUsdPerTok` and
20
+ * `pricing-shape.ts` `TokenRates`), so callers pass either shape unwidened.
21
+ */
22
+ /**
23
+ * The rate components the effective-rate math needs, USD per token. A thin,
24
+ * self-contained shape so this module imports neither `ModelPricing` nor
25
+ * `pricing-shape` (both can build one) and — critically — does NOT touch the
26
+ * UNRELATED local `TokenRates` in `watch/evaluate.ts` (QA name-collision note:
27
+ * that one is a flat `{inputUsdPerTok, outputUsdPerTok}` blend input; keep them
28
+ * separate). `cacheRead`/`cacheWrite` null ⇒ fall back to `input` (L5).
29
+ */
30
+ export interface EffectiveRates {
31
+ readonly input: number;
32
+ readonly cacheRead: number | null;
33
+ readonly cacheWrite: number | null;
34
+ }
35
+ /** Observed cache activity over a trailing window — the aggregate form (spec
36
+ * §4.2): `h = Σcached/Σin`, `w = Σwrite/Σin`. Both in [0,1]; `h + w ≤ 1` holds
37
+ * because cached and write tokens are disjoint subsets of input tokens. */
38
+ export interface ObservedCacheRates {
39
+ readonly h: number;
40
+ readonly w: number;
41
+ }
42
+ /**
43
+ * Cache-adjusted effective INPUT rate (spec §4.2, aggregate form):
44
+ *
45
+ * p_eff_in = h·p_read + w·p_write + (1 − h − w)·p_in
46
+ *
47
+ * The blend of what a token of input ACTUALLY costs given the window's cache
48
+ * behavior. Clamps h,w to non-negative and their sum to ≤ 1 (defensive — a
49
+ * malformed rollup must never produce a negative uncached weight). At h = w = 0
50
+ * this is exactly `rates.input` (the regression pin).
51
+ */
52
+ export declare function effectiveInputRate(obs: ObservedCacheRates, rates: EffectiveRates): number;
53
+ /**
54
+ * Expected per-call saving from READING a warm prefix instead of paying full
55
+ * input on it (spec §4.2, steady-state form): the cached prefix tokens cost
56
+ * `p_read` rather than `p_in`, so the saving per call is
57
+ *
58
+ * E[T_read]·(p_in − p_read), E[T_read] = matchingPrefixTokens · P(hit)
59
+ *
60
+ * The one-time write premium is NOT here — it is the switch cost (§4.3). At
61
+ * P(hit) = 0, or when the read rate has degraded to the input rate (L5), this is
62
+ * 0: a candidate with no verified read discount shows no per-call saving.
63
+ */
64
+ export declare function perCallCacheSavingsUsd(matchingPrefixTokens: number, pHit: number, rates: EffectiveRates): number;
65
+ /**
66
+ * Switch cost of moving a warm workflow from `oldRates` to `newRates` (spec
67
+ * §4.3):
68
+ *
69
+ * switch_cost = prefix_tok·p_write(new) − prefix_tok·p_read(old)
70
+ *
71
+ * The new candidate must WRITE the prefix into its (cold) cache at its write
72
+ * rate; the incumbent would have READ it at its read rate. Positive ⇒ switching
73
+ * costs money up front; negative ⇒ the incumbent's read was already dearer than
74
+ * the challenger's write (rare, but real when the incumbent has no read
75
+ * discount). With `prefixTokens = 0` this is exactly 0 (the regression pin for a
76
+ * route with no cacheable prefix).
77
+ */
78
+ export declare function switchCostUsd(prefixTokens: number, newRates: EffectiveRates, oldRates: EffectiveRates): number;
79
+ /**
80
+ * Cache option value (spec §4.3): the total warmth advantage a candidate is
81
+ * expected to accrue over its remaining life — `E[remaining_calls] ×
82
+ * per_call_cache_savings`. This is the benefit side the switch cost is weighed
83
+ * against. Non-negative inputs; 0 at either factor 0.
84
+ */
85
+ export declare function cacheOptionValueUsd(remainingCalls: number, perCallSavingsUsd: number): number;
86
+ /** Floor on the denominator of `breakevenCalls` so a vanishing per-call saving
87
+ * yields a very large (but finite) break-even rather than Infinity/NaN. */
88
+ export declare const BREAKEVEN_EPSILON_USD = 1e-9;
89
+ /**
90
+ * Break-even call count (spec §4.3): `switch_cost / max(per_call_savings, ε)` —
91
+ * how many warm calls a swap must serve before its up-front switch cost is
92
+ * repaid by per-call savings. Returns `Infinity` when the switch never pays back
93
+ * (non-positive per-call saving with a positive switch cost) so callers render
94
+ * "never breaks even" rather than a misleading huge integer. A non-positive
95
+ * switch cost (free or profitable to switch) breaks even at 0 calls.
96
+ */
97
+ export declare function breakevenCalls(switchCostUsd_: number, perCallSavingsUsd: number): number;
98
+ /**
99
+ * The cache-aware verdict on a swap proposal (spec §4.3). Renders on the
100
+ * console, the `swap execute` approval prompt, and the OTLP decision span.
101
+ * • `cache_positive` — cache-adjusted advantage clears BOTH the absolute
102
+ * floor and the relative threshold; recommended.
103
+ * • `cache_marginal` — a real advantage that does not clear the hysteresis
104
+ * bar; created + labeled, NOT recommended (anti-thrash).
105
+ * • `cache_negative` — cache-adjusted, the challenger is WORSE than list
106
+ * would suggest (incumbent warm, challenger cold).
107
+ * • `no_cache_signal` — no observed cache activity to base a cache-aware
108
+ * verdict on; the ordinary list comparison stands.
109
+ */
110
+ export type CacheVerdict = "cache_positive" | "cache_marginal" | "cache_negative" | "no_cache_signal";
111
+ /**
112
+ * Hysteresis config (transaction-cost analogy, [Briefing §16]). A swap is
113
+ * `cache_positive` only when its advantage clears BOTH bars — an absolute
114
+ * per-call floor AND a relative fraction of window spend. Defaults per spec
115
+ * §4.3; lives in `WatchConfig` (same home as `thresholdPct`), which composes
116
+ * this shape rather than redefining it.
117
+ */
118
+ export interface CacheHysteresisConfig {
119
+ /** Absolute floor, USD per call-equivalent (spec default $0.03). */
120
+ readonly cacheFloorUsdPerCall: number;
121
+ /** Relative floor, PERCENT of trailing-window spend (spec default 8%). */
122
+ readonly cacheRelativeThresholdPct: number;
123
+ }
124
+ export declare const DEFAULT_CACHE_HYSTERESIS: CacheHysteresisConfig;
125
+ /** The inputs the verdict classifier weighs (all cache-ADJUSTED). */
126
+ export interface CacheVerdictInput {
127
+ /** Whether the window carries ANY cache observation (read or write) on either
128
+ * side — false ⇒ `no_cache_signal` regardless of the numbers below. */
129
+ readonly hasCacheSignal: boolean;
130
+ /** Cache-adjusted per-call advantage of the challenger over the incumbent,
131
+ * USD (incumbent_effective_per_call − challenger_effective_per_call). >0 ⇒
132
+ * challenger cheaper once warmth + switch cost are counted. */
133
+ readonly perCallAdvantageUsd: number;
134
+ /** The same advantage projected over the expected remaining calls, USD — the
135
+ * quantity weighed against the relative (window-spend) bar. */
136
+ readonly totalAdvantageUsd: number;
137
+ /** Trailing-window spend on this route, USD — the base of the relative bar. */
138
+ readonly windowSpendUsd: number;
139
+ }
140
+ /**
141
+ * Classify a swap proposal's cache-aware verdict with hysteresis (spec §4.3).
142
+ * Pure and total. The ordering is deliberate: no signal short-circuits first
143
+ * (we cannot speak to cache economics we have not observed), then a negative
144
+ * advantage is `cache_negative`, then the two-bar hysteresis gate separates
145
+ * `cache_positive` from `cache_marginal`.
146
+ */
147
+ export declare function classifyCacheVerdict(input: CacheVerdictInput, config?: CacheHysteresisConfig): CacheVerdict;
148
+ /** Human-readable one-liner for a verdict — reused by the console, the approval
149
+ * prompt, and the definitions page (WS-D) so the phrasing is single-sourced. */
150
+ export declare function describeCacheVerdict(verdict: CacheVerdict): string;
151
+ //# sourceMappingURL=effective-rate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"effective-rate.d.ts","sourceRoot":"","sources":["../../src/registry/effective-rate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED;;2EAE2E;AAC3E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;CACpB;AAWD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,kBAAkB,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAUzF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,oBAAoB,EAAE,MAAM,EAC5B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,cAAc,GACpB,MAAM,CAKR;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,cAAc,GACvB,MAAM,CAGR;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,cAAc,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAE7F;AAED;2EAC2E;AAC3E,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAIxF;AAID;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,YAAY,GAAG,gBAAgB,GAAG,gBAAgB,GAAG,gBAAgB,GAAG,iBAAiB,CAAC;AAEtG;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,oEAAoE;IACpE,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,0EAA0E;IAC1E,QAAQ,CAAC,yBAAyB,EAAE,MAAM,CAAC;CAC5C;AAED,eAAO,MAAM,wBAAwB,EAAE,qBAGtC,CAAC;AAEF,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC;2EACuE;IACvE,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;mEAE+D;IAC/D,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC;mEAC+D;IAC/D,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,+EAA+E;IAC/E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,iBAAiB,EACxB,MAAM,GAAE,qBAAgD,GACvD,YAAY,CAOd;AAED;gFACgF;AAChF,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAWlE"}
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ /**
3
+ * Cache-adjusted route economics — cache-aware-routing spec §4.2–4.3, WS-B.
4
+ * PURE: no store, no clock, no network. Every function here is total and
5
+ * deterministic; the whole module is the math the architect reviews pre-merge.
6
+ *
7
+ * ── THE REGRESSION PIN (load-bearing, WS-A discipline) ────────────────────
8
+ * With observed hit rate h = 0 and write rate w = 0, `effectiveInputRate`
9
+ * returns the LIST input rate BYTE-IDENTICALLY, and every per-call saving
10
+ * collapses to 0. A route with no cache activity therefore prices exactly as it
11
+ * did before this module existed. The property test locks this: the cache-aware
12
+ * lens NEVER changes a number until real cache tokens appear.
13
+ *
14
+ * ── FAIL-CLOSED RATES (dev-rule L5) ───────────────────────────────────────
15
+ * An unverified cache-read or cache-write rate degrades to the INPUT rate
16
+ * (1.0×), never a guessed premium/discount — the same posture as
17
+ * `estimateCostUsd`. So an unpriced cache component can only make caching look
18
+ * NEUTRAL, never spuriously attractive.
19
+ *
20
+ * Units: every rate is USD PER TOKEN (matching `ModelPricing.inputUsdPerTok` and
21
+ * `pricing-shape.ts` `TokenRates`), so callers pass either shape unwidened.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.DEFAULT_CACHE_HYSTERESIS = exports.BREAKEVEN_EPSILON_USD = void 0;
25
+ exports.effectiveInputRate = effectiveInputRate;
26
+ exports.perCallCacheSavingsUsd = perCallCacheSavingsUsd;
27
+ exports.switchCostUsd = switchCostUsd;
28
+ exports.cacheOptionValueUsd = cacheOptionValueUsd;
29
+ exports.breakevenCalls = breakevenCalls;
30
+ exports.classifyCacheVerdict = classifyCacheVerdict;
31
+ exports.describeCacheVerdict = describeCacheVerdict;
32
+ /** Resolve a possibly-null cache rate to its fail-closed fallback (the input
33
+ * rate). Centralized so every function degrades identically (L5). */
34
+ function readRate(rates) {
35
+ return rates.cacheRead ?? rates.input;
36
+ }
37
+ function writeRate(rates) {
38
+ return rates.cacheWrite ?? rates.input;
39
+ }
40
+ /**
41
+ * Cache-adjusted effective INPUT rate (spec §4.2, aggregate form):
42
+ *
43
+ * p_eff_in = h·p_read + w·p_write + (1 − h − w)·p_in
44
+ *
45
+ * The blend of what a token of input ACTUALLY costs given the window's cache
46
+ * behavior. Clamps h,w to non-negative and their sum to ≤ 1 (defensive — a
47
+ * malformed rollup must never produce a negative uncached weight). At h = w = 0
48
+ * this is exactly `rates.input` (the regression pin).
49
+ */
50
+ function effectiveInputRate(obs, rates) {
51
+ const h = Math.max(obs.h, 0);
52
+ const w = Math.max(obs.w, 0);
53
+ // Cap the combined cache weight at 1: uncached weight never goes negative.
54
+ const cacheWeight = Math.min(h + w, 1);
55
+ const scale = h + w > 0 ? cacheWeight / (h + w) : 0;
56
+ const hh = h * scale;
57
+ const ww = w * scale;
58
+ const uncachedWeight = 1 - hh - ww;
59
+ return hh * readRate(rates) + ww * writeRate(rates) + uncachedWeight * rates.input;
60
+ }
61
+ /**
62
+ * Expected per-call saving from READING a warm prefix instead of paying full
63
+ * input on it (spec §4.2, steady-state form): the cached prefix tokens cost
64
+ * `p_read` rather than `p_in`, so the saving per call is
65
+ *
66
+ * E[T_read]·(p_in − p_read), E[T_read] = matchingPrefixTokens · P(hit)
67
+ *
68
+ * The one-time write premium is NOT here — it is the switch cost (§4.3). At
69
+ * P(hit) = 0, or when the read rate has degraded to the input rate (L5), this is
70
+ * 0: a candidate with no verified read discount shows no per-call saving.
71
+ */
72
+ function perCallCacheSavingsUsd(matchingPrefixTokens, pHit, rates) {
73
+ const tokens = Math.max(matchingPrefixTokens, 0);
74
+ const hit = Math.min(Math.max(pHit, 0), 1);
75
+ const perTokenSaving = Math.max(rates.input - readRate(rates), 0);
76
+ return tokens * hit * perTokenSaving;
77
+ }
78
+ /**
79
+ * Switch cost of moving a warm workflow from `oldRates` to `newRates` (spec
80
+ * §4.3):
81
+ *
82
+ * switch_cost = prefix_tok·p_write(new) − prefix_tok·p_read(old)
83
+ *
84
+ * The new candidate must WRITE the prefix into its (cold) cache at its write
85
+ * rate; the incumbent would have READ it at its read rate. Positive ⇒ switching
86
+ * costs money up front; negative ⇒ the incumbent's read was already dearer than
87
+ * the challenger's write (rare, but real when the incumbent has no read
88
+ * discount). With `prefixTokens = 0` this is exactly 0 (the regression pin for a
89
+ * route with no cacheable prefix).
90
+ */
91
+ function switchCostUsd(prefixTokens, newRates, oldRates) {
92
+ const tokens = Math.max(prefixTokens, 0);
93
+ return tokens * writeRate(newRates) - tokens * readRate(oldRates);
94
+ }
95
+ /**
96
+ * Cache option value (spec §4.3): the total warmth advantage a candidate is
97
+ * expected to accrue over its remaining life — `E[remaining_calls] ×
98
+ * per_call_cache_savings`. This is the benefit side the switch cost is weighed
99
+ * against. Non-negative inputs; 0 at either factor 0.
100
+ */
101
+ function cacheOptionValueUsd(remainingCalls, perCallSavingsUsd) {
102
+ return Math.max(remainingCalls, 0) * Math.max(perCallSavingsUsd, 0);
103
+ }
104
+ /** Floor on the denominator of `breakevenCalls` so a vanishing per-call saving
105
+ * yields a very large (but finite) break-even rather than Infinity/NaN. */
106
+ exports.BREAKEVEN_EPSILON_USD = 1e-9;
107
+ /**
108
+ * Break-even call count (spec §4.3): `switch_cost / max(per_call_savings, ε)` —
109
+ * how many warm calls a swap must serve before its up-front switch cost is
110
+ * repaid by per-call savings. Returns `Infinity` when the switch never pays back
111
+ * (non-positive per-call saving with a positive switch cost) so callers render
112
+ * "never breaks even" rather than a misleading huge integer. A non-positive
113
+ * switch cost (free or profitable to switch) breaks even at 0 calls.
114
+ */
115
+ function breakevenCalls(switchCostUsd_, perCallSavingsUsd) {
116
+ if (switchCostUsd_ <= 0)
117
+ return 0;
118
+ if (perCallSavingsUsd <= 0)
119
+ return Number.POSITIVE_INFINITY;
120
+ return switchCostUsd_ / Math.max(perCallSavingsUsd, exports.BREAKEVEN_EPSILON_USD);
121
+ }
122
+ exports.DEFAULT_CACHE_HYSTERESIS = {
123
+ cacheFloorUsdPerCall: 0.03,
124
+ cacheRelativeThresholdPct: 8,
125
+ };
126
+ /**
127
+ * Classify a swap proposal's cache-aware verdict with hysteresis (spec §4.3).
128
+ * Pure and total. The ordering is deliberate: no signal short-circuits first
129
+ * (we cannot speak to cache economics we have not observed), then a negative
130
+ * advantage is `cache_negative`, then the two-bar hysteresis gate separates
131
+ * `cache_positive` from `cache_marginal`.
132
+ */
133
+ function classifyCacheVerdict(input, config = exports.DEFAULT_CACHE_HYSTERESIS) {
134
+ if (!input.hasCacheSignal)
135
+ return "no_cache_signal";
136
+ if (input.perCallAdvantageUsd < 0)
137
+ return "cache_negative";
138
+ const clearsFloor = input.perCallAdvantageUsd >= config.cacheFloorUsdPerCall;
139
+ const relativeBar = (config.cacheRelativeThresholdPct / 100) * Math.max(input.windowSpendUsd, 0);
140
+ const clearsRelative = input.totalAdvantageUsd >= relativeBar;
141
+ return clearsFloor && clearsRelative ? "cache_positive" : "cache_marginal";
142
+ }
143
+ /** Human-readable one-liner for a verdict — reused by the console, the approval
144
+ * prompt, and the definitions page (WS-D) so the phrasing is single-sourced. */
145
+ function describeCacheVerdict(verdict) {
146
+ switch (verdict) {
147
+ case "cache_positive":
148
+ return "cache-positive — clears the switch-cost hysteresis; recommended";
149
+ case "cache_marginal":
150
+ return "cache-marginal — a real but sub-threshold advantage; not recommended (anti-thrash)";
151
+ case "cache_negative":
152
+ return "cache-negative — the incumbent is warm and the challenger is cold; switching costs more than it saves";
153
+ case "no_cache_signal":
154
+ return "no cache signal — no observed cache activity; the list-price comparison stands";
155
+ }
156
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"facts.d.ts","sourceRoot":"","sources":["../../src/registry/facts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAY,aAAa,EAAE,MAAM,SAAS,CAAC;AAMvD,MAAM,WAAW,aAAa;IAC5B;wEACoE;IACpE,QAAQ,CACN,QAAQ,EAAE,UAAU,EACpB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,WAAW,CAAC,cAAc,CAAC,GACvC,WAAW,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC;CACvC;AAED;sDACsD;AACtD,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,aAAa,EACpB,WAAW,EAAE,WAAW,CAAC,cAAc,CAAC,GACvC,GAAG,CAAC,cAAc,CAAC,CAsDrB;AAQD,wBAAgB,iBAAiB,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,aAAa,CA8BtE"}
1
+ {"version":3,"file":"facts.d.ts","sourceRoot":"","sources":["../../src/registry/facts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAY,aAAa,EAAE,MAAM,SAAS,CAAC;AAMvD,MAAM,WAAW,aAAa;IAC5B;wEACoE;IACpE,QAAQ,CACN,QAAQ,EAAE,UAAU,EACpB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,WAAW,CAAC,cAAc,CAAC,GACvC,WAAW,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC;CACvC;AAED;sDACsD;AACtD,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,aAAa,EACpB,WAAW,EAAE,WAAW,CAAC,cAAc,CAAC,GACvC,GAAG,CAAC,cAAc,CAAC,CAsErB;AAQD,wBAAgB,iBAAiB,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,aAAa,CA8BtE"}
@@ -81,6 +81,22 @@ function capabilityFactsFromEntry(entry, staticFlags) {
81
81
  else if (declared?.flags.supports_prompt_caching === true) {
82
82
  flags.add("prefix_cache_implicit");
83
83
  }
84
+ // zero_retention (G-3) — the opt-in data-governance filter. Authoritative
85
+ // source is the credential-scoped `facts` layer (claims.ts), NEVER the
86
+ // adapter-static baseline: ZDR is a policy designation, not an observable
87
+ // adapter capability. The flag is present only when a fact designates this
88
+ // endpoint zero-retention under our own (managed) account or unconditionally
89
+ // (any). A byok-only designation does NOT grant it — a customer's own key
90
+ // does not inherit our managed-account posture (the credential-scope
91
+ // compliance rule; product-plan v3.2, phase6-verification.md). Absence, a
92
+ // value:false, and a stray baseline flag all fail closed: no cited managed/any
93
+ // ZDR fact => the model is excluded from a `zero_retention` route.
94
+ const zdrFacts = entry.facts?.zero_retention;
95
+ const zdrDesignated = zdrFacts?.some((f) => f.value === true && (f.scope === "managed" || f.scope === "any")) ?? false;
96
+ if (zdrDesignated)
97
+ flags.add("zero_retention");
98
+ else
99
+ flags.delete("zero_retention");
84
100
  // context_cache_explicit / prompt_cache_key / tier_flex / trace_visible:
85
101
  // unprobed dimensions — the adapter-static baseline stands.
86
102
  return flags;