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.
- package/CHANGELOG.md +158 -0
- package/README.md +53 -11
- package/data/PROVENANCE.md +19 -0
- package/data/pricing-manual.json +22 -0
- package/data/pricing-map.json +46362 -0
- package/data/pricing-overlay.json +419 -0
- package/dist/adapters/anthropic.d.ts +50 -6
- package/dist/adapters/anthropic.d.ts.map +1 -1
- package/dist/adapters/anthropic.js +123 -29
- package/dist/adapters/build-adapters.d.ts +11 -0
- package/dist/adapters/build-adapters.d.ts.map +1 -0
- package/dist/adapters/build-adapters.js +45 -0
- package/dist/adapters/deepseek.d.ts +6 -0
- package/dist/adapters/deepseek.d.ts.map +1 -1
- package/dist/adapters/deepseek.js +54 -1
- package/dist/adapters/gemini.d.ts +10 -0
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +78 -7
- package/dist/adapters/grok.d.ts +9 -0
- package/dist/adapters/grok.d.ts.map +1 -1
- package/dist/adapters/grok.js +67 -3
- package/dist/adapters/openai-compat.d.ts +6 -0
- package/dist/adapters/openai-compat.d.ts.map +1 -1
- package/dist/adapters/openai-compat.js +58 -1
- package/dist/adapters/openai-stream.d.ts +58 -0
- package/dist/adapters/openai-stream.d.ts.map +1 -0
- package/dist/adapters/openai-stream.js +120 -0
- package/dist/adapters/openai.d.ts +6 -0
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +60 -1
- package/dist/adapters/sampling.d.ts +28 -0
- package/dist/adapters/sampling.d.ts.map +1 -0
- package/dist/adapters/sampling.js +34 -0
- package/dist/adapters/tool-mapping.d.ts +92 -0
- package/dist/adapters/tool-mapping.d.ts.map +1 -0
- package/dist/adapters/tool-mapping.js +143 -0
- package/dist/artifacts/artifact.d.ts +137 -0
- package/dist/artifacts/artifact.d.ts.map +1 -0
- package/dist/artifacts/artifact.js +377 -0
- package/dist/artifacts/content-uploader.d.ts +87 -0
- package/dist/artifacts/content-uploader.d.ts.map +1 -0
- package/dist/artifacts/content-uploader.js +124 -0
- package/dist/artifacts/index.d.ts +63 -0
- package/dist/artifacts/index.d.ts.map +1 -0
- package/dist/artifacts/index.js +80 -0
- package/dist/artifacts/run-context.d.ts +129 -0
- package/dist/artifacts/run-context.d.ts.map +1 -0
- package/dist/artifacts/run-context.js +208 -0
- package/dist/artifacts/scrub.d.ts +60 -0
- package/dist/artifacts/scrub.d.ts.map +1 -0
- package/dist/artifacts/scrub.js +139 -0
- package/dist/artifacts/step.d.ts +26 -0
- package/dist/artifacts/step.d.ts.map +1 -0
- package/dist/artifacts/step.js +37 -0
- package/dist/artifacts/taint.d.ts +63 -0
- package/dist/artifacts/taint.d.ts.map +1 -0
- package/dist/artifacts/taint.js +76 -0
- package/dist/bakeoff/cache-sim.d.ts +99 -0
- package/dist/bakeoff/cache-sim.d.ts.map +1 -0
- package/dist/bakeoff/cache-sim.js +126 -0
- package/dist/bakeoff/eval-cases.d.ts +96 -0
- package/dist/bakeoff/eval-cases.d.ts.map +1 -0
- package/dist/bakeoff/eval-cases.js +105 -0
- package/dist/bakeoff/report.d.ts +41 -0
- package/dist/bakeoff/report.d.ts.map +1 -0
- package/dist/bakeoff/report.js +142 -0
- package/dist/bakeoff/samples.d.ts +15 -0
- package/dist/bakeoff/samples.d.ts.map +1 -1
- package/dist/bakeoff/samples.js +20 -0
- package/dist/cli.d.ts +1 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +134 -116
- package/dist/config.d.ts +100 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +104 -0
- package/dist/deprecations.d.ts +32 -0
- package/dist/deprecations.d.ts.map +1 -0
- package/dist/deprecations.js +59 -0
- package/dist/gateway/http-protocol.d.ts +175 -0
- package/dist/gateway/http-protocol.d.ts.map +1 -0
- package/dist/gateway/http-protocol.js +105 -0
- package/dist/index.d.ts +106 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +266 -138
- package/dist/init/finder.d.ts +15 -0
- package/dist/init/finder.d.ts.map +1 -1
- package/dist/init/finder.js +46 -0
- package/dist/init/init-cli.d.ts +10 -0
- package/dist/init/init-cli.d.ts.map +1 -0
- package/dist/init/init-cli.js +86 -0
- package/dist/init/observe-cli.d.ts +19 -0
- package/dist/init/observe-cli.d.ts.map +1 -0
- package/dist/init/observe-cli.js +73 -0
- package/dist/init/observe.d.ts +81 -0
- package/dist/init/observe.d.ts.map +1 -0
- package/dist/init/observe.js +245 -0
- package/dist/registry/build.d.ts +94 -1
- package/dist/registry/build.d.ts.map +1 -1
- package/dist/registry/build.js +125 -4
- package/dist/registry/cache-policy.d.ts +94 -0
- package/dist/registry/cache-policy.d.ts.map +1 -0
- package/dist/registry/cache-policy.js +238 -0
- package/dist/registry/call-notes.d.ts.map +1 -1
- package/dist/registry/call-notes.js +40 -2
- package/dist/registry/effective-rate.d.ts +151 -0
- package/dist/registry/effective-rate.d.ts.map +1 -0
- package/dist/registry/effective-rate.js +156 -0
- package/dist/registry/facts.d.ts.map +1 -1
- package/dist/registry/facts.js +16 -0
- package/dist/registry/parity.d.ts +26 -0
- package/dist/registry/parity.d.ts.map +1 -1
- package/dist/registry/parity.js +43 -2
- package/dist/registry/pricing-shape.d.ts +113 -0
- package/dist/registry/pricing-shape.d.ts.map +1 -0
- package/dist/registry/pricing-shape.js +123 -0
- package/dist/registry/pricing.d.ts +25 -1
- package/dist/registry/pricing.d.ts.map +1 -1
- package/dist/registry/pricing.js +64 -6
- package/dist/rigindex/bootstrap.d.ts +36 -0
- package/dist/rigindex/bootstrap.d.ts.map +1 -0
- package/dist/rigindex/bootstrap.js +97 -0
- package/dist/rigindex/fit.d.ts +24 -0
- package/dist/rigindex/fit.d.ts.map +1 -0
- package/dist/rigindex/fit.js +76 -0
- package/dist/rigindex/gates.d.ts +52 -0
- package/dist/rigindex/gates.d.ts.map +1 -0
- package/dist/rigindex/gates.js +109 -0
- package/dist/rigindex/graph.d.ts +31 -0
- package/dist/rigindex/graph.d.ts.map +1 -0
- package/dist/rigindex/graph.js +120 -0
- package/dist/rigindex/index.d.ts +23 -0
- package/dist/rigindex/index.d.ts.map +1 -0
- package/dist/rigindex/index.js +112 -0
- package/dist/rigindex/types.d.ts +76 -0
- package/dist/rigindex/types.d.ts.map +1 -0
- package/dist/rigindex/types.js +8 -0
- package/dist/routes/cache-lint.d.ts +41 -0
- package/dist/routes/cache-lint.d.ts.map +1 -0
- package/dist/routes/cache-lint.js +81 -0
- package/dist/routes/loader.d.ts +48 -0
- package/dist/routes/loader.d.ts.map +1 -1
- package/dist/routes/loader.js +167 -0
- package/dist/routes/resolved-bundle.d.ts +79 -0
- package/dist/routes/resolved-bundle.d.ts.map +1 -0
- package/dist/routes/resolved-bundle.js +148 -0
- package/dist/routes/sampling-config.d.ts +32 -0
- package/dist/routes/sampling-config.d.ts.map +1 -0
- package/dist/routes/sampling-config.js +91 -0
- package/dist/run/ajv.d.ts +22 -0
- package/dist/run/ajv.d.ts.map +1 -0
- package/dist/run/ajv.js +32 -0
- package/dist/run/cache-handle.d.ts +58 -0
- package/dist/run/cache-handle.d.ts.map +1 -0
- package/dist/run/cache-handle.js +54 -0
- package/dist/run/cache-stamp.d.ts +65 -0
- package/dist/run/cache-stamp.d.ts.map +1 -0
- package/dist/run/cache-stamp.js +0 -0
- package/dist/run/capture-write.d.ts +33 -0
- package/dist/run/capture-write.d.ts.map +1 -0
- package/dist/run/capture-write.js +55 -0
- package/dist/run/cost.d.ts.map +1 -1
- package/dist/run/cost.js +1 -1
- package/dist/run/dispatch.d.ts +99 -0
- package/dist/run/dispatch.d.ts.map +1 -0
- package/dist/run/dispatch.js +191 -0
- package/dist/run/raw.d.ts +130 -0
- package/dist/run/raw.d.ts.map +1 -0
- package/dist/run/raw.js +363 -0
- package/dist/run/record-attempt.d.ts +40 -0
- package/dist/run/record-attempt.d.ts.map +1 -0
- package/dist/run/record-attempt.js +99 -0
- package/dist/run/resolve.d.ts +75 -2
- package/dist/run/resolve.d.ts.map +1 -1
- package/dist/run/resolve.js +126 -4
- package/dist/run/run.d.ts +14 -0
- package/dist/run/run.d.ts.map +1 -1
- package/dist/run/run.js +0 -0
- package/dist/run/stream.d.ts +49 -0
- package/dist/run/stream.d.ts.map +1 -0
- package/dist/run/stream.js +87 -0
- package/dist/run/validate.d.ts.map +1 -1
- package/dist/run/validate.js +6 -7
- package/dist/savings/cache-ledger.d.ts +108 -0
- package/dist/savings/cache-ledger.d.ts.map +1 -0
- package/dist/savings/cache-ledger.js +174 -0
- package/dist/savings/cache-reconcile.d.ts +64 -0
- package/dist/savings/cache-reconcile.d.ts.map +1 -0
- package/dist/savings/cache-reconcile.js +59 -0
- package/dist/savings/cache-stats.d.ts +63 -0
- package/dist/savings/cache-stats.d.ts.map +1 -0
- package/dist/savings/cache-stats.js +108 -0
- package/dist/savings/emit.d.ts +13 -0
- package/dist/savings/emit.d.ts.map +1 -1
- package/dist/savings/emit.js +25 -0
- package/dist/savings/swap-execute.d.ts +14 -0
- package/dist/savings/swap-execute.d.ts.map +1 -1
- package/dist/savings/swap-execute.js +44 -1
- package/dist/savings/swaps-client.d.ts +14 -0
- package/dist/savings/swaps-client.d.ts.map +1 -1
- package/dist/savings/swaps-client.js +16 -2
- package/dist/settings/api-fetcher.d.ts +39 -0
- package/dist/settings/api-fetcher.d.ts.map +1 -0
- package/dist/settings/api-fetcher.js +92 -0
- package/dist/settings/index.d.ts +10 -0
- package/dist/settings/index.d.ts.map +1 -0
- package/dist/settings/index.js +15 -0
- package/dist/settings/task-settings.d.ts +91 -0
- package/dist/settings/task-settings.d.ts.map +1 -0
- package/dist/settings/task-settings.js +116 -0
- package/dist/status.d.ts +77 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +113 -0
- package/dist/telemetry/artifact-store.d.ts +48 -0
- package/dist/telemetry/artifact-store.d.ts.map +1 -0
- package/dist/telemetry/artifact-store.js +189 -0
- package/dist/telemetry/cache-miss-streak.d.ts +79 -0
- package/dist/telemetry/cache-miss-streak.d.ts.map +1 -0
- package/dist/telemetry/cache-miss-streak.js +99 -0
- package/dist/telemetry/capture.d.ts +3 -0
- package/dist/telemetry/capture.d.ts.map +1 -1
- package/dist/telemetry/capture.js +15 -8
- package/dist/telemetry/exporter-setup.d.ts +28 -0
- package/dist/telemetry/exporter-setup.d.ts.map +1 -0
- package/dist/telemetry/exporter-setup.js +34 -0
- package/dist/telemetry/exporter.d.ts +75 -0
- package/dist/telemetry/exporter.d.ts.map +1 -1
- package/dist/telemetry/exporter.js +324 -47
- package/dist/telemetry/health.d.ts +76 -0
- package/dist/telemetry/health.d.ts.map +1 -0
- package/dist/telemetry/health.js +92 -0
- package/dist/telemetry/ingest-protocol.d.ts +152 -3
- package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
- package/dist/telemetry/ingest-protocol.js +204 -4
- package/dist/telemetry/load-error-report.d.ts +24 -0
- package/dist/telemetry/load-error-report.d.ts.map +1 -0
- package/dist/telemetry/load-error-report.js +50 -0
- package/dist/telemetry/otel-map.d.ts +121 -0
- package/dist/telemetry/otel-map.d.ts.map +1 -0
- package/dist/telemetry/otel-map.js +224 -0
- package/dist/telemetry/otel-normalize.d.ts +104 -0
- package/dist/telemetry/otel-normalize.d.ts.map +1 -0
- package/dist/telemetry/otel-normalize.js +530 -0
- package/dist/telemetry/otlp-emitter.d.ts +52 -0
- package/dist/telemetry/otlp-emitter.d.ts.map +1 -0
- package/dist/telemetry/otlp-emitter.js +144 -0
- package/dist/telemetry/otlp-proto.d.ts +29 -0
- package/dist/telemetry/otlp-proto.d.ts.map +1 -0
- package/dist/telemetry/otlp-proto.js +266 -0
- package/dist/telemetry/sinks.d.ts +37 -1
- package/dist/telemetry/sinks.d.ts.map +1 -1
- package/dist/telemetry/sinks.js +121 -30
- package/dist/telemetry/store.d.ts +28 -1
- package/dist/telemetry/store.d.ts.map +1 -1
- package/dist/telemetry/store.js +218 -3
- package/dist/tenancy/api-key-format.d.ts +15 -5
- package/dist/tenancy/api-key-format.d.ts.map +1 -1
- package/dist/tenancy/api-key-format.js +24 -5
- package/dist/types/config-teaching.d.ts +62 -0
- package/dist/types/config-teaching.d.ts.map +1 -0
- package/dist/types/config-teaching.js +183 -0
- package/dist/types/provider.d.ts +147 -1
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/provider.js +21 -1
- package/dist/types/route.d.ts +79 -1
- package/dist/types/route.d.ts.map +1 -1
- package/dist/types/telemetry.d.ts +117 -0
- package/dist/types/telemetry.d.ts.map +1 -1
- package/dist/validate-cli.d.ts +12 -0
- package/dist/validate-cli.d.ts.map +1 -0
- package/dist/validate-cli.js +57 -0
- package/dist/verify/capture.d.ts +66 -0
- package/dist/verify/capture.d.ts.map +1 -0
- package/dist/verify/capture.js +180 -0
- package/dist/verify/cli-run.d.ts +23 -0
- package/dist/verify/cli-run.d.ts.map +1 -0
- package/dist/verify/cli-run.js +57 -0
- package/dist/verify/diff.d.ts +48 -0
- package/dist/verify/diff.d.ts.map +1 -0
- package/dist/verify/diff.js +157 -0
- package/dist/verify/index.d.ts +17 -0
- package/dist/verify/index.d.ts.map +1 -0
- package/dist/verify/index.js +24 -0
- package/dist/verify/report.d.ts +14 -0
- package/dist/verify/report.d.ts.map +1 -0
- package/dist/verify/report.js +88 -0
- package/dist/verify/types.d.ts +101 -0
- package/dist/verify/types.d.ts.map +1 -0
- package/dist/verify/types.js +17 -0
- package/dist/verify/verify.d.ts +29 -0
- package/dist/verify/verify.d.ts.map +1 -0
- package/dist/verify/verify.js +148 -0
- package/dist/watch/evaluate.d.ts +162 -3
- package/dist/watch/evaluate.d.ts.map +1 -1
- package/dist/watch/evaluate.js +61 -0
- package/dist/watch/monitor.d.ts +116 -0
- package/dist/watch/monitor.d.ts.map +1 -0
- package/dist/watch/monitor.js +199 -0
- package/dist/watch/proposals.d.ts +20 -4
- package/dist/watch/proposals.d.ts.map +1 -1
- package/dist/watch/proposals.js +31 -3
- package/dist/watch/run.d.ts +33 -0
- package/dist/watch/run.d.ts.map +1 -1
- package/dist/watch/run.js +39 -0
- package/package.json +7 -1
- package/registry/LICENSE +20 -0
- package/registry/leaderboard.json +3639 -0
- package/registry/registry.json +7263 -0
- package/dist/telemetry/supabase-exporter.d.ts +0 -64
- package/dist/telemetry/supabase-exporter.d.ts.map +0 -1
- 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;
|
|
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,
|
|
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"}
|
package/dist/registry/facts.js
CHANGED
|
@@ -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;
|