@bytesbrains/cruise-worker 0.0.0-stage → 0.12.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/LICENSE +202 -0
- package/README.md +37 -2
- package/index.d.ts +14 -0
- package/index.mjs +41 -0
- package/migrations/0001_virtual_keys.sql +102 -0
- package/migrations/0002_seed_projects.sql +16 -0
- package/migrations/0003_budgets.sql +43 -0
- package/migrations/0004_tenant_isolation_and_integer_money.sql +49 -0
- package/migrations/0005_model_prices.sql +42 -0
- package/migrations/0006_budget_reservation.sql +38 -0
- package/migrations/0007_routing_table.sql +93 -0
- package/migrations/0008_measurement_provenance.sql +24 -0
- package/migrations/0009_conformance.sql +37 -0
- package/migrations/0010_conformance_key.sql +20 -0
- package/migrations/0011_correct_workers_ai_price.sql +30 -0
- package/migrations/0012_rate_limits.sql +28 -0
- package/migrations/0013_deepseek_and_mistral.sql +42 -0
- package/migrations/0014_cost_ledger.sql +104 -0
- package/migrations/0015_memberships.sql +33 -0
- package/migrations/0016_latency.sql +23 -0
- package/migrations/0017_ledger_provider_index.sql +8 -0
- package/migrations/0018_fallback_chains.sql +34 -0
- package/migrations/0019_rate_buckets.sql +27 -0
- package/migrations/0020_mistral_price_and_pin.sql +62 -0
- package/migrations/0021_mistral_price_date_is_utc.sql +35 -0
- package/migrations/0022_drop_unenforced_key_budget.sql +32 -0
- package/migrations/0023_model_limits.sql +48 -0
- package/migrations/0024_conditional_prices.sql +84 -0
- package/migrations/0025_catalogue_from_keyed_hosts.sql +172 -0
- package/migrations/0026_lanes.sql +83 -0
- package/migrations/0027_lane_alternatives.sql +36 -0
- package/migrations/0028_lanes_tenant_scope.sql +66 -0
- package/migrations/0029_lane_jobs.sql +146 -0
- package/migrations/0030_lane_requires.sql +38 -0
- package/migrations/0031_tenant_billable.sql +15 -0
- package/migrations/0032_cache_hit_billing.sql +35 -0
- package/migrations/0033_sell_markup.sql +37 -0
- package/migrations/0034_tiers_and_lane_quotas.sql +38 -0
- package/migrations/0035_wallet_scope.sql +68 -0
- package/migrations/0036_credit_grants.sql +53 -0
- package/migrations/0037_reconciliation.sql +45 -0
- package/migrations/0038_balance_service_keys.sql +35 -0
- package/migrations/0039_deepseek_peak_is_weekdays.sql +77 -0
- package/migrations/0040_glm_5_3_on_workers_ai.sql +63 -0
- package/migrations/0041_mistral_sweep_2026_08_29.sql +89 -0
- package/migrations/0042_provision_service_keys.sql +50 -0
- package/migrations/0043_tier_project_quota.sql +32 -0
- package/migrations/0044_audit_tenant_scope.sql +49 -0
- package/migrations/0045_audit_backfill_derivable.sql +42 -0
- package/migrations/0046_measurement_note.sql +24 -0
- package/migrations/0047_ledger_max_tokens.sql +21 -0
- package/migrations/0048_price_observations.sql +140 -0
- package/migrations/0049_openai_and_gemini.sql +239 -0
- package/migrations/0050_meta_direct_upstream.sql +127 -0
- package/migrations/0051_upstream_credential_and_operator.sql +74 -0
- package/migrations/0052_muse_spark_1_3.sql +71 -0
- package/migrations/0053_anthropic_groq_and_xai.sql +172 -0
- package/migrations/0054_tier_service_keys.sql +60 -0
- package/migrations/0055_output_defaults.sql +79 -0
- package/migrations/0056_model_modality.sql +37 -0
- package/migrations/0057_openai_image_models.sql +177 -0
- package/migrations/0058_price_components.sql +230 -0
- package/migrations/0059_speech.sql +151 -0
- package/migrations/0060_embeddings.sql +131 -0
- package/migrations/0061_data_retention_backfill.sql +121 -0
- package/migrations/0062_gemini_embedding_disabled.sql +125 -0
- package/migrations/0063_google_provider_path_prefix.sql +59 -0
- package/migrations/0064_economics_service_keys.sql +51 -0
- package/migrations/0065_lane_selection.sql +66 -0
- package/migrations/0066_tenant_default_selection.sql +42 -0
- package/migrations/0067_graduated_selection.sql +103 -0
- package/migrations/0068_cache_scope.sql +73 -0
- package/migrations/0069_lane_cache_settings.sql +50 -0
- package/migrations/0070_lane_member_quality_rank.sql +98 -0
- package/migrations/0071_key_cache_pool.sql +60 -0
- package/migrations/0072_cache_digest.sql +51 -0
- package/migrations/0073_lane_max_chain.sql +51 -0
- package/migrations/0074_hunter_builder_lanes.sql +149 -0
- package/migrations/0075_hunter_builder_frontline.sql +36 -0
- package/migrations/0076_deepseek_flash.sql +86 -0
- package/migrations/0077_gpt_6_astra.sql +35 -0
- package/migrations/0078_astra_deep_reasoning.sql +20 -0
- package/migrations/0079_gemini_flash_lite_extraction.sql +15 -0
- package/migrations/0080_priced_by.sql +24 -0
- package/migrations/0081_grok_imagine.sql +81 -0
- package/migrations/0082_markup_three_percent.sql +32 -0
- package/migrations/0083_markup_per_tenant_and_tier.sql +57 -0
- package/migrations/0084_coding_lanes_frontier.sql +93 -0
- package/migrations/0085_minute_period_and_demo_charge.sql +77 -0
- package/migrations/0086_project_sentry.sql +41 -0
- package/migrations/0087_lane_router.sql +71 -0
- package/migrations/0088_openai_frontier_coding_lanes.sql +54 -0
- package/migrations/0089_agentic_coding_drops_codestral.sql +44 -0
- package/migrations/0090_board_followups_2026_10.sql +196 -0
- package/migrations/0091_opus_5_5_coding_lanes.sql +43 -0
- package/migrations/0092_deepseek_peak_holidays.sql +83 -0
- package/migrations/0093_tool_choice_forced.sql +26 -0
- package/migrations/0094_board_followups_2026_10_08.sql +115 -0
- package/migrations/0095_request_compression.sql +43 -0
- package/migrations/0096_lane_compress.sql +36 -0
- package/migrations/0097_tenant_default_compress.sql +34 -0
- package/migrations/0098_ledger_session.sql +47 -0
- package/migrations/0099_wallet_low_water_backfill.sql +47 -0
- package/migrations/0100_rag_grounded_answers_lane.sql +92 -0
- package/migrations/0101_perplexity_agent_api.sql +125 -0
- package/migrations/0102_web_search_job_and_flag.sql +137 -0
- package/migrations/0103_openai_web_search_price.sql +44 -0
- package/migrations/0104_web_search_lane.sql +82 -0
- package/migrations/0105_web_search_lane_billed.sql +22 -0
- package/migrations/0106_reservations_journal.sql +64 -0
- package/migrations/0107_session_cap.sql +65 -0
- package/migrations/0108_ledger_attempts.sql +41 -0
- package/migrations/0109_gemini_retention.sql +39 -0
- package/migrations/0110_gemini_4_argon.sql +110 -0
- package/migrations/0111_gemini_4_argon_lanes.sql +80 -0
- package/migrations/0112_ledger_affinity.sql +42 -0
- package/migrations/0113_contributor_lane_namespace.sql +125 -0
- package/migrations/0114_meta_contributor_models.sql +55 -0
- package/migrations/0115_oss_chat_assistant_lane.sql +41 -0
- package/migrations/0116_promo_credits.sql +63 -0
- package/migrations/0117_oss_mirror_lanes.sql +85 -0
- package/migrations/0118_project_management_job.sql +112 -0
- package/migrations/0119_oss_project_management_lane.sql +29 -0
- package/migrations/0120_moonshot_k3_1_pro.sql +107 -0
- package/migrations/0121_jobs.sql +100 -0
- package/migrations/0122_batch_prices.sql +188 -0
- package/migrations/0123_batch_files.sql +50 -0
- package/migrations/0124_batch_run.sql +21 -0
- package/migrations/0125_anthropic_path_prefix.sql +22 -0
- package/package.json +37 -4
- package/worker/284906b51fc0a3173ea7d301234907f30e834f54-cruise-mark-64.png +0 -0
- package/worker/2c39de526314484775fdfc503fac50c437d21e4e-cruise-logo-240.png +0 -0
- package/worker/63fb63e2d8ae6d8b68228768ef6c91ab8f0c0100-cruise-logo-480.png +0 -0
- package/worker/VERSION +1 -0
- package/worker/b23bfbc792e8710d3b74284a0a1b8ab044c18897-logo-dark.png +0 -0
- package/worker/index.js +31451 -0
- package/wrangler.template.jsonc +48 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
-- A price that is not per token, held once (#228, slice 3 of #220; the design
|
|
2
|
+
-- #207 asked for). Decided in docs/modalities.md section 3.
|
|
3
|
+
--
|
|
4
|
+
-- `price_observations` holds three rates and every one is per million tokens.
|
|
5
|
+
-- That was the whole truth about every host in the catalogue until the labs
|
|
6
|
+
-- arrived, and it stopped being true in three places at once: Perplexity
|
|
7
|
+
-- charges a fee **per request** on top of tokens, varying with the search
|
|
8
|
+
-- context size the body names; Anthropic bills a web search **per search**,
|
|
9
|
+
-- reported in the response's `server_tool_use`; and speech (slice 4) is per
|
|
10
|
+
-- character. #207 measured what ignoring this costs — a `sonar` row priced on
|
|
11
|
+
-- tokens alone silently misses $5–$14 per thousand requests, plausibly the
|
|
12
|
+
-- larger half of a short query's bill — and the direction is the one 0011 made
|
|
13
|
+
-- a migration of its own over: under-counting, which lets spend past a cap
|
|
14
|
+
-- while reporting the cap as unbreached.
|
|
15
|
+
--
|
|
16
|
+
-- ## The shape
|
|
17
|
+
--
|
|
18
|
+
-- A second append-only log **beside** `price_observations`, not a widening of
|
|
19
|
+
-- it. Same two time axes — `effective_from` is when the vendor's rate applied,
|
|
20
|
+
-- `observed_at` when we recorded it — same `retracted` tombstone, same view
|
|
21
|
+
-- over the latest surviving observation per key, for 0048's reason: a
|
|
22
|
+
-- correction and a price change must not look alike in the data. The token
|
|
23
|
+
-- resolver in `src/pricing.ts` is right for tokens and stays the one path to a
|
|
24
|
+
-- token rate; this is the one path to everything else.
|
|
25
|
+
--
|
|
26
|
+
-- `component` is a **closed vocabulary**, CHECK-constrained, and each entry
|
|
27
|
+
-- states where its count comes from (`src/pricing-components.ts` is the code
|
|
28
|
+
-- that reads each one):
|
|
29
|
+
--
|
|
30
|
+
-- request one per request; `variant` is the search context size
|
|
31
|
+
-- search the response's `server_tool_use.web_search_requests`
|
|
32
|
+
-- character the request's `input`, for speech
|
|
33
|
+
-- image the request's `n`; `variant` is `size/quality`
|
|
34
|
+
-- image_input the response's `input_tokens_details.image_tokens`
|
|
35
|
+
-- audio_input the response's usage, for the Gemini rows 0049 excluded
|
|
36
|
+
-- cache_write the response's usage; `variant` is the cache TTL
|
|
37
|
+
-- second the request, for audio embeddings
|
|
38
|
+
-- frame the request, for video embeddings
|
|
39
|
+
--
|
|
40
|
+
-- **`text_input`, `text_output` and `cached_input` are deliberately not in the
|
|
41
|
+
-- set.** They are the three token columns by definition, and a component row
|
|
42
|
+
-- restating one of them would be a second place a token rate lives — the
|
|
43
|
+
-- tenant-isolation argument (0004) applied to prices: one path to a fact, so
|
|
44
|
+
-- two rows cannot disagree. The CHECK refuses them, and a test holds it to that.
|
|
45
|
+
--
|
|
46
|
+
-- `variant` is what selects the rate and is **read from the request, never
|
|
47
|
+
-- pinned by Cruise**: Perplexity's `web_search_options.search_context_size`,
|
|
48
|
+
-- an image's `size` and `quality`. '' is the row that applies when the body
|
|
49
|
+
-- names nothing and the host has one rate. A body naming a variant no row
|
|
50
|
+
-- carries is bounded and settled at the **dearest** row of that component —
|
|
51
|
+
-- over-counting, the safe direction — and the ledger records which applied.
|
|
52
|
+
--
|
|
53
|
+
-- `per` is stored rather than implied by the unit. A token rate is sub-micro
|
|
54
|
+
-- per token and needs the million; an image is tens of thousands of micros
|
|
55
|
+
-- and needs nothing; a host quoting "per 1,000 characters" is recorded as it
|
|
56
|
+
-- quotes, so a row can be held against the page it came from by eye.
|
|
57
|
+
--
|
|
58
|
+
-- ## Rules the code keeps
|
|
59
|
+
--
|
|
60
|
+
-- A request's cost is its token part plus the sum of its components.
|
|
61
|
+
-- `ledger.cost_micros` stays the total; `ledger_components` below keeps the
|
|
62
|
+
-- units, so a month reconciles against an invoice per unit. Summing the
|
|
63
|
+
-- units away on write is the failure 0014 named for tokens.
|
|
64
|
+
--
|
|
65
|
+
-- The reservation covers any rate settlement can name. The **count** is
|
|
66
|
+
-- exact for a request-counted component and a ceiling for a response-counted
|
|
67
|
+
-- one — one request fee, every search the body's `max_uses` allows. The
|
|
68
|
+
-- **rate** is the row the body asks for, except where the host chooses the
|
|
69
|
+
-- variant and reports it back, which is Perplexity's context size: there the
|
|
70
|
+
-- bound holds the dearest row whatever was asked, because the host bills
|
|
71
|
+
-- what it applied and settling at the asked-for rate would under-bill. A
|
|
72
|
+
-- response-counted component with no stated ceiling cannot be bounded, and
|
|
73
|
+
-- that is refused a billed tenant as `request_unbounded` — the same answer
|
|
74
|
+
-- a chat request with no `max_tokens` gets on a model with no output cap.
|
|
75
|
+
--
|
|
76
|
+
-- A component in the vocabulary with **no reader in the code** fails
|
|
77
|
+
-- closed: a model carrying such a row cannot be bounded for a billed tenant
|
|
78
|
+
-- and settles at its reservation for an internal one. The vocabulary is the
|
|
79
|
+
-- design's, the readers are this slice's, and the gap between them must
|
|
80
|
+
-- refuse rather than under-bill. `request`, `search`, `character` and
|
|
81
|
+
-- `image` are read today.
|
|
82
|
+
|
|
83
|
+
CREATE TABLE price_components (
|
|
84
|
+
observed_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
85
|
+
model TEXT NOT NULL,
|
|
86
|
+
effective_from TEXT NOT NULL,
|
|
87
|
+
component TEXT NOT NULL CHECK (component IN (
|
|
88
|
+
'request', 'search', 'character', 'image',
|
|
89
|
+
'image_input', 'audio_input', 'cache_write', 'second', 'frame')),
|
|
90
|
+
variant TEXT NOT NULL DEFAULT '',
|
|
91
|
+
-- What `per` of the unit costs. Zero is a real rate: a host that waives a
|
|
92
|
+
-- fee on one variant says so with a row rather than with an absence.
|
|
93
|
+
micros INTEGER NOT NULL CHECK (micros >= 0),
|
|
94
|
+
-- The count one `micros` buys: 1 request, 1000 searches, 1 image.
|
|
95
|
+
per INTEGER NOT NULL CHECK (per > 0),
|
|
96
|
+
source TEXT NOT NULL,
|
|
97
|
+
note TEXT,
|
|
98
|
+
retracted INTEGER NOT NULL DEFAULT 0 CHECK (retracted IN (0, 1)),
|
|
99
|
+
PRIMARY KEY (model, effective_from, component, variant, observed_at)
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
CREATE INDEX idx_price_components_lookup ON price_components (model, effective_from);
|
|
103
|
+
|
|
104
|
+
-- The latest surviving observation per key, as `model_prices` is over
|
|
105
|
+
-- `price_observations`. Every reader reads this; nothing writes it.
|
|
106
|
+
CREATE VIEW model_components AS
|
|
107
|
+
SELECT c.model,
|
|
108
|
+
c.effective_from,
|
|
109
|
+
c.component,
|
|
110
|
+
c.variant,
|
|
111
|
+
c.micros,
|
|
112
|
+
c.per,
|
|
113
|
+
c.source,
|
|
114
|
+
c.observed_at AS recorded_at
|
|
115
|
+
FROM price_components c
|
|
116
|
+
WHERE c.retracted = 0
|
|
117
|
+
AND c.observed_at = (
|
|
118
|
+
SELECT max(x.observed_at)
|
|
119
|
+
FROM price_components x
|
|
120
|
+
WHERE x.model = c.model
|
|
121
|
+
AND x.effective_from = c.effective_from
|
|
122
|
+
AND x.component = c.component
|
|
123
|
+
AND x.variant = c.variant
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
-- What each request paid per unit, beside the row that holds its total. One
|
|
127
|
+
-- row per component and variant that applied; a request with no components
|
|
128
|
+
-- has none. `micros` is at **list** — the rate in force, before a cache
|
|
129
|
+
-- discount and before a tenant's markup — because the invoice this
|
|
130
|
+
-- reconciles against is the host's, and the host bills list. Written in the
|
|
131
|
+
-- same `db.batch` as the ledger row, so a request cannot have its units
|
|
132
|
+
-- without its total or its total without its units.
|
|
133
|
+
CREATE TABLE ledger_components (
|
|
134
|
+
ledger_id TEXT NOT NULL REFERENCES ledger(id),
|
|
135
|
+
component TEXT NOT NULL CHECK (component IN (
|
|
136
|
+
'request', 'search', 'character', 'image',
|
|
137
|
+
'image_input', 'audio_input', 'cache_write', 'second', 'frame')),
|
|
138
|
+
variant TEXT NOT NULL DEFAULT '',
|
|
139
|
+
count INTEGER NOT NULL CHECK (count >= 0),
|
|
140
|
+
micros INTEGER NOT NULL CHECK (micros >= 0),
|
|
141
|
+
PRIMARY KEY (ledger_id, component, variant)
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
-- ## First rows: Perplexity's request fee and Anthropic's search fee (#207)
|
|
145
|
+
--
|
|
146
|
+
-- Read 2026-09-05 IST from each host's page. That is 2026-09-04 21:00 UTC, which
|
|
147
|
+
-- is the date these rows carry, because a row dated a day ahead of the Worker's
|
|
148
|
+
-- clock applies to nothing (0021). **Perplexity's figures moved between #207's
|
|
149
|
+
-- reading of 2026-09-02 and this one**: that issue quoted $5/$8/$12 per
|
|
150
|
+
-- thousand requests for every Sonar model, and the page now prints those for
|
|
151
|
+
-- `sonar` alone, with `sonar-pro` and `sonar-reasoning-pro` at $6/$10/$14. Three
|
|
152
|
+
-- days apart, on the page this repo's whole pricing argument rests on — which
|
|
153
|
+
-- is the argument for `observed_at` in one sentence.
|
|
154
|
+
--
|
|
155
|
+
-- No Perplexity model is in `models`, and these rows put none there. #207's
|
|
156
|
+
-- fourth question — whether search-grounded models are worth routing at all —
|
|
157
|
+
-- is still open, and its comment of 2026-09-03 notes their chat-completions
|
|
158
|
+
-- endpoint is supported only until 2026-09-27, so an upstream added the
|
|
159
|
+
-- ordinary way would be a migration and a rewrite in that order. The rows cost
|
|
160
|
+
-- nothing while nothing routes to them, and they are the shape's first
|
|
161
|
+
-- consumer: a `request` fee with a variant, bounded exactly, settled at the
|
|
162
|
+
-- context size the response's usage reports.
|
|
163
|
+
--
|
|
164
|
+
-- The Cruise id is `perplexity/<model>`, as every upstream's rows are named,
|
|
165
|
+
-- so the day a `models` row lands the price is already there.
|
|
166
|
+
--
|
|
167
|
+
-- `sonar-deep-research` is **not** here. It bills per search query ($5 per
|
|
168
|
+
-- 1,000) and the count is bounded by nothing in the request — the model
|
|
169
|
+
-- searches as it sees fit, with no `max_uses` — so a `search` row would reserve
|
|
170
|
+
-- nothing and settle from `usage.num_search_queries`: correct for an internal
|
|
171
|
+
-- tenant and unbounded for a billed one. It also bills citation and reasoning
|
|
172
|
+
-- tokens as separate categories this log has no component for. Both stay
|
|
173
|
+
-- #207's to decide, and are recorded here so the omission is a decision.
|
|
174
|
+
INSERT INTO price_components
|
|
175
|
+
(observed_at, model, effective_from, component, variant, micros, per, source, note)
|
|
176
|
+
VALUES
|
|
177
|
+
('2026-09-04 21:00:00', 'perplexity/sonar', '2026-09-04', 'request', 'low', 5000000, 1000,
|
|
178
|
+
'https://docs.perplexity.ai/getting-started/pricing',
|
|
179
|
+
'First observation (0058): per-request fee at search_context_size low. Read 2026-09-05; #207 quoted the same figure on 2026-09-02.'),
|
|
180
|
+
('2026-09-04 21:00:00', 'perplexity/sonar', '2026-09-04', 'request', 'medium', 8000000, 1000,
|
|
181
|
+
'https://docs.perplexity.ai/getting-started/pricing', 'First observation (0058).'),
|
|
182
|
+
('2026-09-04 21:00:00', 'perplexity/sonar', '2026-09-04', 'request', 'high', 12000000, 1000,
|
|
183
|
+
'https://docs.perplexity.ai/getting-started/pricing', 'First observation (0058).'),
|
|
184
|
+
|
|
185
|
+
('2026-09-04 21:00:00', 'perplexity/sonar-pro', '2026-09-04', 'request', 'low', 6000000, 1000,
|
|
186
|
+
'https://docs.perplexity.ai/getting-started/pricing',
|
|
187
|
+
'First observation (0058). #207 read $5/$8/$12 on 2026-09-02; the page prints $6/$10/$14 for this model on 2026-09-05.'),
|
|
188
|
+
('2026-09-04 21:00:00', 'perplexity/sonar-pro', '2026-09-04', 'request', 'medium', 10000000, 1000,
|
|
189
|
+
'https://docs.perplexity.ai/getting-started/pricing', 'First observation (0058).'),
|
|
190
|
+
('2026-09-04 21:00:00', 'perplexity/sonar-pro', '2026-09-04', 'request', 'high', 14000000, 1000,
|
|
191
|
+
'https://docs.perplexity.ai/getting-started/pricing', 'First observation (0058).'),
|
|
192
|
+
|
|
193
|
+
('2026-09-04 21:00:00', 'perplexity/sonar-reasoning-pro', '2026-09-04', 'request', 'low', 6000000, 1000,
|
|
194
|
+
'https://docs.perplexity.ai/getting-started/pricing',
|
|
195
|
+
'First observation (0058). #207 read $5/$8/$12 on 2026-09-02; the page prints $6/$10/$14 for this model on 2026-09-05.'),
|
|
196
|
+
('2026-09-04 21:00:00', 'perplexity/sonar-reasoning-pro', '2026-09-04', 'request', 'medium', 10000000, 1000,
|
|
197
|
+
'https://docs.perplexity.ai/getting-started/pricing', 'First observation (0058).'),
|
|
198
|
+
('2026-09-04 21:00:00', 'perplexity/sonar-reasoning-pro', '2026-09-04', 'request', 'high', 14000000, 1000,
|
|
199
|
+
'https://docs.perplexity.ai/getting-started/pricing', 'First observation (0058).');
|
|
200
|
+
|
|
201
|
+
-- Anthropic's web search: $10 per 1,000 searches on every model 0053 holds,
|
|
202
|
+
-- "plus standard token costs for search-generated content" — which the token
|
|
203
|
+
-- columns already bill, since search results are counted as input tokens. The
|
|
204
|
+
-- count is `usage.server_tool_use.web_search_requests`, a search that errors
|
|
205
|
+
-- is not billed, and `max_uses` on the tool is the ceiling the reservation
|
|
206
|
+
-- holds. Whether that field survives the gateway's `/compat` translation is a
|
|
207
|
+
-- measurement for the first conformance run against a held credential, not an
|
|
208
|
+
-- assumption here: a response carrying no count where the request allowed
|
|
209
|
+
-- searches settles at the reservation rather than at zero.
|
|
210
|
+
--
|
|
211
|
+
-- **Cache writes remain a known gap.** 0053 recorded the cache *read* rate in
|
|
212
|
+
-- `cached_input_micros_per_mtok` and noted the page's two cache *write* rates
|
|
213
|
+
-- (1.25x and 2x base, by TTL) had no column. They now have a component,
|
|
214
|
+
-- `cache_write` with `variant` = TTL, and no rows — because there is also no
|
|
215
|
+
-- reader yet: `usage.cache_creation_input_tokens` is not parsed, and a row
|
|
216
|
+
-- with no reader fails closed rather than billing. Writing the rows is the
|
|
217
|
+
-- smaller half; reading the count is the half that bills, and it lands with
|
|
218
|
+
-- the reader.
|
|
219
|
+
INSERT INTO price_components
|
|
220
|
+
(observed_at, model, effective_from, component, variant, micros, per, source, note)
|
|
221
|
+
VALUES
|
|
222
|
+
('2026-09-04 21:00:00', 'anthropic/claude-fable-5-1', '2026-09-04', 'search', '', 10000000, 1000,
|
|
223
|
+
'https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool',
|
|
224
|
+
'First observation (0058): web search, $10 per 1,000 searches, counted from usage.server_tool_use.web_search_requests.'),
|
|
225
|
+
('2026-09-04 21:00:00', 'anthropic/claude-opus-5', '2026-09-04', 'search', '', 10000000, 1000,
|
|
226
|
+
'https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool', 'First observation (0058).'),
|
|
227
|
+
('2026-09-04 21:00:00', 'anthropic/claude-sonnet-5', '2026-09-04', 'search', '', 10000000, 1000,
|
|
228
|
+
'https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool', 'First observation (0058).'),
|
|
229
|
+
('2026-09-04 21:00:00', 'anthropic/claude-haiku-4-5-20251001', '2026-09-04', 'search', '', 10000000, 1000,
|
|
230
|
+
'https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool', 'First observation (0058).');
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
-- Speech: the first route whose whole bill is known before the request is sent,
|
|
2
|
+
-- and the first host this repo reaches through an adapter rather than a URL
|
|
3
|
+
-- (#229, slice 4 of #220, decided in `docs/modalities.md`).
|
|
4
|
+
--
|
|
5
|
+
-- 0058 built the log for a price that is not per token and gave it two consumers
|
|
6
|
+
-- that route nowhere. This is the first that routes: `tts-1` and `tts-1-hd` on a
|
|
7
|
+
-- host the gateway already carries, then ElevenLabs on a path of our own.
|
|
8
|
+
--
|
|
9
|
+
-- ## What the hosts charge, read 2026-09-05 IST
|
|
10
|
+
--
|
|
11
|
+
-- That is 2026-09-04 21:47 UTC, which is the date every row below carries —
|
|
12
|
+
-- 0057's lesson, a third time: `date('now')` is UTC and a row dated the 5th is
|
|
13
|
+
-- a row not yet in force for another two hours.
|
|
14
|
+
--
|
|
15
|
+
-- **OpenAI** prices `tts-1` at $15.00 and `tts-1-hd` at $30.00 **per 1M
|
|
16
|
+
-- characters**, on the pricing page under audio. So `per = 1000000`, as the
|
|
17
|
+
-- page quotes it. `gpt-4o-mini-tts` is on the same page at $0.60 per 1M text
|
|
18
|
+
-- tokens in and $12.00 per 1M audio tokens out — per token, with audio output
|
|
19
|
+
-- priced above text — and is **not here**: the `audio_output` reading of the
|
|
20
|
+
-- component log does not exist yet, and a row priced on the text columns alone
|
|
21
|
+
-- would under-count the audio by twenty times.
|
|
22
|
+
--
|
|
23
|
+
-- **ElevenLabs** bills text to speech per character and prints the rate **per
|
|
24
|
+
-- 1,000 characters** on its API pricing page: $0.10 for v3 and Multilingual v2,
|
|
25
|
+
-- $0.05 for Flash and v3 Conversational. So `per = 1000` — recorded as quoted,
|
|
26
|
+
-- which is the reason `per` is a column rather than a convention (0058).
|
|
27
|
+
--
|
|
28
|
+
-- Three ElevenLabs rows and the exclusions are 0049's rule: `eleven_turbo_v2_5`
|
|
29
|
+
-- and `eleven_turbo_v2` are listed deprecated on the models page;
|
|
30
|
+
-- `eleven_flash_v2` is superseded by 2.5 at the same price with a smaller
|
|
31
|
+
-- limit; `eleven_v3_conversational` publishes no character limit to hold the
|
|
32
|
+
-- request to. Each of those is a row the day somebody asks for it.
|
|
33
|
+
--
|
|
34
|
+
-- ## The count is known before the request is sent
|
|
35
|
+
--
|
|
36
|
+
-- For every row here the bill is `characters × rate` and nothing else: the
|
|
37
|
+
-- count is the length of the request's `input`, exact, and the response's usage
|
|
38
|
+
-- is irrelevant to it. Two things follow that the route relies on:
|
|
39
|
+
--
|
|
40
|
+
-- The reservation **is** the settlement. There is no ceiling and no
|
|
41
|
+
-- give-back; `x-cruise-budget-reserved` is the charge to the micro.
|
|
42
|
+
--
|
|
43
|
+
-- `usage_reported` is not consulted. `docs/modalities.md` section 4 narrows
|
|
44
|
+
-- that flag to a statement about *where a count comes from*, and no count
|
|
45
|
+
-- here comes from the response — so a speech row is billable to a billed
|
|
46
|
+
-- tenant with the flag NULL, and `src/billing-block.ts` says so per modality
|
|
47
|
+
-- rather than per response.
|
|
48
|
+
--
|
|
49
|
+
-- ## `max_input_chars`, and why it is a column
|
|
50
|
+
--
|
|
51
|
+
-- Both hosts refuse a request over a stated character ceiling: OpenAI at 4,096
|
|
52
|
+
-- (API reference, `input`: "The maximum length is 4096 characters"), ElevenLabs
|
|
53
|
+
-- per model on its models page. The route holds the request to it for the
|
|
54
|
+
-- reason `checkLimits` holds `max_tokens` to `max_output` and the image route
|
|
55
|
+
-- holds `n` to 10: the figure multiplies the reservation, and a request the
|
|
56
|
+
-- host would refuse anyway must not first hold — or be refused *for* — a bound
|
|
57
|
+
-- the size of its whole text. NULL means the host states none, and a chat row
|
|
58
|
+
-- is NULL because the question does not arise for it.
|
|
59
|
+
--
|
|
60
|
+
-- Never written from memory: every figure below names the page it was read
|
|
61
|
+
-- from and the date. 0011 and 0020 are why.
|
|
62
|
+
|
|
63
|
+
ALTER TABLE models ADD COLUMN max_input_chars INTEGER
|
|
64
|
+
CHECK (max_input_chars IS NULL OR max_input_chars > 0);
|
|
65
|
+
|
|
66
|
+
-- ── ElevenLabs, reached directly ──────────────────────────────────────────────
|
|
67
|
+
--
|
|
68
|
+
-- Not a gateway provider in any form, so `base_url` as 0050 built it for Meta.
|
|
69
|
+
-- What is new is that the wire format is ElevenLabs' own — the voice is a path
|
|
70
|
+
-- segment, the model a body field, the response raw audio — and
|
|
71
|
+
-- `src/speech-elevenlabs.ts` is the mapping from OpenAI's `audio/speech` shape
|
|
72
|
+
-- onto it. That mapping is the adapter, and it is the first this repo has
|
|
73
|
+
-- needed; `docs/upstreams.md` tier 4 said a lab speaking a different wire
|
|
74
|
+
-- format is real code, and this is the instance.
|
|
75
|
+
--
|
|
76
|
+
-- The credential is `PROVIDER_KEY_ELEVENLABS`, sent as the host's own
|
|
77
|
+
-- `xi-api-key` header rather than as a bearer token, and the header set is a
|
|
78
|
+
-- fresh object for a third party — never the gateway's with keys deleted.
|
|
79
|
+
--
|
|
80
|
+
-- `data_retention` is NULL and must not be guessed: the terms are a property
|
|
81
|
+
-- of the plan the key bills on, so whoever sets the secret reads them and
|
|
82
|
+
-- writes what they read, as 0049 asked for Google.
|
|
83
|
+
|
|
84
|
+
INSERT INTO upstreams (id, name, kind, base_url) VALUES
|
|
85
|
+
('elevenlabs', 'ElevenLabs', 'provider', 'https://api.elevenlabs.io/v1');
|
|
86
|
+
|
|
87
|
+
INSERT INTO models (id, upstream_id, upstream_model, modality, max_input_chars,
|
|
88
|
+
data_retention, data_retention_source, data_retention_as_of) VALUES
|
|
89
|
+
-- OpenAI: the same your-data page 0057 read for the image rows, one day
|
|
90
|
+
-- earlier; the terms are the account's, not the model's.
|
|
91
|
+
('openai/tts-1', 'openai', 'tts-1', 'speech', 4096,
|
|
92
|
+
'retained', 'https://developers.openai.com/api/docs/guides/your-data', '2026-09-04'),
|
|
93
|
+
('openai/tts-1-hd', 'openai', 'tts-1-hd', 'speech', 4096,
|
|
94
|
+
'retained', 'https://developers.openai.com/api/docs/guides/your-data', '2026-09-04'),
|
|
95
|
+
-- ElevenLabs: limits from https://elevenlabs.io/docs/models, read 2026-09-05.
|
|
96
|
+
('elevenlabs/eleven_multilingual_v2', 'elevenlabs', 'eleven_multilingual_v2', 'speech', 10000,
|
|
97
|
+
NULL, NULL, NULL),
|
|
98
|
+
('elevenlabs/eleven_flash_v2_5', 'elevenlabs', 'eleven_flash_v2_5', 'speech', 40000,
|
|
99
|
+
NULL, NULL, NULL),
|
|
100
|
+
('elevenlabs/eleven_v3', 'elevenlabs', 'eleven_v3', 'speech', 5000,
|
|
101
|
+
NULL, NULL, NULL);
|
|
102
|
+
|
|
103
|
+
-- `max_context` and `max_output` are NULL on all five, as on the image rows:
|
|
104
|
+
-- both count tokens of a completion, and a speech model has none.
|
|
105
|
+
--
|
|
106
|
+
-- No `price_observations` row for any of these, and that is the point rather
|
|
107
|
+
-- than a gap. A speech model has no token part — not a token part of zero
|
|
108
|
+
-- recorded as a fact, but no term at all — so a row of zeros here would be a
|
|
109
|
+
-- number nobody fetched and, worse, would read as "priced" to every gate the
|
|
110
|
+
-- moment somebody forgot the character row. The gate reads the component log
|
|
111
|
+
-- for this modality (`src/billing-block.ts`), and the catalogue prints the
|
|
112
|
+
-- token rates as zero beside the character line because that is what a speech
|
|
113
|
+
-- request pays per token.
|
|
114
|
+
|
|
115
|
+
INSERT INTO price_components
|
|
116
|
+
(observed_at, model, effective_from, component, variant, micros, per, source, note)
|
|
117
|
+
VALUES
|
|
118
|
+
('2026-09-04 21:47:00', 'openai/tts-1', '2026-09-04', 'character', '', 15000000, 1000000,
|
|
119
|
+
'https://developers.openai.com/api/docs/pricing',
|
|
120
|
+
'First observation (0059): $15.00 per 1M characters, read 2026-09-05 IST. The count is the request''s input; the reservation is the settlement.'),
|
|
121
|
+
('2026-09-04 21:47:00', 'openai/tts-1-hd', '2026-09-04', 'character', '', 30000000, 1000000,
|
|
122
|
+
'https://developers.openai.com/api/docs/pricing',
|
|
123
|
+
'First observation (0059): $30.00 per 1M characters.'),
|
|
124
|
+
|
|
125
|
+
('2026-09-04 21:47:00', 'elevenlabs/eleven_multilingual_v2', '2026-09-04', 'character', '', 100000, 1000,
|
|
126
|
+
'https://elevenlabs.io/pricing/api',
|
|
127
|
+
'First observation (0059): $0.10 per 1,000 characters, quoted per thousand and recorded as quoted.'),
|
|
128
|
+
('2026-09-04 21:47:00', 'elevenlabs/eleven_flash_v2_5', '2026-09-04', 'character', '', 50000, 1000,
|
|
129
|
+
'https://elevenlabs.io/pricing/api',
|
|
130
|
+
'First observation (0059): $0.05 per 1,000 characters — the models page says "50% lower price per character for API generations".'),
|
|
131
|
+
('2026-09-04 21:47:00', 'elevenlabs/eleven_v3', '2026-09-04', 'character', '', 100000, 1000,
|
|
132
|
+
'https://elevenlabs.io/pricing/api',
|
|
133
|
+
'First observation (0059): $0.10 per 1,000 characters.');
|
|
134
|
+
|
|
135
|
+
-- ── The transport, which is measured before it is relied on ───────────────────
|
|
136
|
+
--
|
|
137
|
+
-- OpenAI's rows take the gateway's provider path, `…/openai/audio/speech`, on
|
|
138
|
+
-- the same footing as 0057's image rows: documented for chat and responses,
|
|
139
|
+
-- implied for the rest, and not measurable from outside a deployed Worker
|
|
140
|
+
-- because both gateways authenticate before they route. The first conformance
|
|
141
|
+
-- run against `PROVIDER_KEY_OPENAI` is the measurement, and the fallback is a
|
|
142
|
+
-- `base_url` on the `openai` upstream row rather than a change of code —
|
|
143
|
+
-- which would move chat and images direct too, and is therefore a decision
|
|
144
|
+
-- for whoever measures.
|
|
145
|
+
--
|
|
146
|
+
-- Nothing is seeded routable. Every row here is unmeasured, and the speech
|
|
147
|
+
-- check set (`src/conformance-speech.ts`) is what measures it: the response is
|
|
148
|
+
-- audio in the format asked for and non-empty, for a fixed input. No chat
|
|
149
|
+
-- flag applies, so a speech row that passes carries `measured_at` and every
|
|
150
|
+
-- capability column NULL — which for this modality is *not applicable* rather
|
|
151
|
+
-- than *unmeasured*, and the reference says so.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
-- Embeddings: the modality that already fitted, and the one gate that would
|
|
2
|
+
-- have refused every one of them (#230, slice 5 of #220, decided in
|
|
3
|
+
-- `docs/modalities.md`).
|
|
4
|
+
--
|
|
5
|
+
-- The cheapest of the three non-chat routes, and last for that reason. An
|
|
6
|
+
-- embedding is priced **per input token with no output**, which the three
|
|
7
|
+
-- columns `price_observations` has held since 0048 express exactly: an
|
|
8
|
+
-- `input_micros_per_mtok` and an `output_micros_per_mtok` of **0**. That zero
|
|
9
|
+
-- is a fact rather than a placeholder — an embedding produces a vector, and a
|
|
10
|
+
-- vector is not completion tokens — and it is why this slice needs no new
|
|
11
|
+
-- price unit, no component row, and no new column on the price log.
|
|
12
|
+
--
|
|
13
|
+
-- ## What the hosts charge, read 2026-09-05
|
|
14
|
+
--
|
|
15
|
+
-- The wall clock that wrote this read 11:12 IST and the UTC date is the same
|
|
16
|
+
-- day, so 0021's trap does not apply here and every date below is 2026-09-05.
|
|
17
|
+
-- Stated rather than assumed, because it has been the other way twice: 0057
|
|
18
|
+
-- and 0059 were both written after midnight UTC-plus-five-thirty and had to
|
|
19
|
+
-- carry the previous day's date.
|
|
20
|
+
--
|
|
21
|
+
-- text-embedding-3-small $0.02 / 1M tokens 1536 dimensions
|
|
22
|
+
-- text-embedding-3-large $0.13 / 1M tokens 3072 dimensions
|
|
23
|
+
-- gemini-embedding-001 $0.15 / 1M tokens 3072 dimensions
|
|
24
|
+
--
|
|
25
|
+
-- Sources: https://developers.openai.com/api/docs/pricing and its embeddings
|
|
26
|
+
-- guide for the dimensions and the 8,192-token input limit;
|
|
27
|
+
-- https://ai.google.dev/gemini-api/docs/pricing and
|
|
28
|
+
-- https://ai.google.dev/gemini-api/docs/embeddings for Google's. Both fetched
|
|
29
|
+
-- 2026-09-05.
|
|
30
|
+
--
|
|
31
|
+
-- ## Two rows deliberately absent
|
|
32
|
+
--
|
|
33
|
+
-- **`text-embedding-ada-002`** is the previous generation: $0.10 per 1M
|
|
34
|
+
-- tokens against `3-small`'s $0.02, at the same 1536 dimensions, and the
|
|
35
|
+
-- host's own guide says a `3-large` embedding shortened to 256 outperforms an
|
|
36
|
+
-- unshortened ada-002 at 1536. Five times the price for less. 0049's rule —
|
|
37
|
+
-- the current catalogue minus what is retiring or superseded.
|
|
38
|
+
--
|
|
39
|
+
-- **`gemini-embedding-2-preview`** is the multimodal one, and leaving it out
|
|
40
|
+
-- is the same decision 0059 made about `gpt-4o-mini-tts` and 0049 made about
|
|
41
|
+
-- Gemini's audio input. Its bill has **per-image, per-second and per-frame**
|
|
42
|
+
-- rates beside the text one, and `price_components` has no reader for any of
|
|
43
|
+
-- the three (0058: a component in the vocabulary with no reader fails closed).
|
|
44
|
+
-- A row carrying its text rate alone would price an image embedding at the
|
|
45
|
+
-- text rate, which is under-counting — the direction 0011 exists to prevent
|
|
46
|
+
-- and the one a cap cannot survive. #230 asked for "Gemini's text embedding,
|
|
47
|
+
-- text input only"; the text-only model *is* that row, and the multimodal one
|
|
48
|
+
-- waits for the readers rather than being half-priced.
|
|
49
|
+
--
|
|
50
|
+
-- ## The dimension is a published fact, and conformance needs it
|
|
51
|
+
--
|
|
52
|
+
-- What a check means for this modality is "a vector of the published
|
|
53
|
+
-- dimension" (`docs/modalities.md` section 4), and until now nothing recorded
|
|
54
|
+
-- what that dimension is. It is a property of the model as the host states
|
|
55
|
+
-- it, like `max_context` and `max_output` beside it, so it goes in the same
|
|
56
|
+
-- table rather than in the check — which would put a number nobody fetched
|
|
57
|
+
-- into code, the failure 0011 and 0020 both name.
|
|
58
|
+
--
|
|
59
|
+
-- It is also the figure a caller sizes a vector column by, so `/v1/models`
|
|
60
|
+
-- publishes it. `dimensions` on the request shortens the vector where the
|
|
61
|
+
-- host supports it (OpenAI's `text-embedding-3` and later, Google's
|
|
62
|
+
-- `output_dimensionality`); this column is what comes back when nothing asks.
|
|
63
|
+
|
|
64
|
+
ALTER TABLE models ADD COLUMN dimensions INTEGER
|
|
65
|
+
CHECK (dimensions IS NULL OR dimensions > 0);
|
|
66
|
+
|
|
67
|
+
INSERT INTO models (id, upstream_id, upstream_model, modality, max_context, dimensions,
|
|
68
|
+
data_retention, data_retention_source, data_retention_as_of) VALUES
|
|
69
|
+
('openai/text-embedding-3-small', 'openai', 'text-embedding-3-small', 'embedding', 8192, 1536,
|
|
70
|
+
'retained', 'https://developers.openai.com/api/docs/guides/your-data', '2026-09-05'),
|
|
71
|
+
('openai/text-embedding-3-large', 'openai', 'text-embedding-3-large', 'embedding', 8192, 3072,
|
|
72
|
+
'retained', 'https://developers.openai.com/api/docs/guides/your-data', '2026-09-05'),
|
|
73
|
+
-- `data_retention` NULL on Google for 0049's reason and not for want of
|
|
74
|
+
-- looking: the terms follow the account tier rather than the model, so
|
|
75
|
+
-- whoever sets `PROVIDER_KEY_GOOGLE_AI_STUDIO` reads which tier that key
|
|
76
|
+
-- bills on and writes what they read.
|
|
77
|
+
('google-ai-studio/gemini-embedding-001', 'google-ai-studio', 'gemini-embedding-001', 'embedding', 2048, 3072,
|
|
78
|
+
NULL, NULL, NULL);
|
|
79
|
+
|
|
80
|
+
-- `max_context` is the host's stated **input** limit — 8,192 tokens on both
|
|
81
|
+
-- OpenAI rows, 2,048 on Google's — and it is recorded rather than enforced.
|
|
82
|
+
-- Enforcing it would need a token count for the input, and the only counter on
|
|
83
|
+
-- the request path is the byte bound, which over-counts English roughly four
|
|
84
|
+
-- times: refusing on it would refuse legitimate work, which is the trap
|
|
85
|
+
-- `checkLimits` already describes for `max_context` on chat. The host refuses
|
|
86
|
+
-- an over-long input loudly, and a loud refusal is what that rule prefers.
|
|
87
|
+
--
|
|
88
|
+
-- `max_output` is NULL on all three and that is the same fact as on the image
|
|
89
|
+
-- and speech rows: it counts tokens of a completion, and there is none.
|
|
90
|
+
|
|
91
|
+
INSERT INTO price_observations
|
|
92
|
+
(observed_at, model, effective_from, prompt_tokens_from, peak_utc,
|
|
93
|
+
input_micros_per_mtok, output_micros_per_mtok, cached_input_micros_per_mtok, source, note)
|
|
94
|
+
VALUES
|
|
95
|
+
('2026-09-05 05:42:00', 'openai/text-embedding-3-small', '2026-09-05', 0, '',
|
|
96
|
+
20000, 0, NULL, 'https://developers.openai.com/api/docs/pricing',
|
|
97
|
+
'First observation (0060): $0.02 per 1M input tokens. The output rate is 0 because an embedding has no completion — a fact, not a placeholder.'),
|
|
98
|
+
('2026-09-05 05:42:00', 'openai/text-embedding-3-large', '2026-09-05', 0, '',
|
|
99
|
+
130000, 0, NULL, 'https://developers.openai.com/api/docs/pricing',
|
|
100
|
+
'First observation (0060): $0.13 per 1M input tokens.'),
|
|
101
|
+
('2026-09-05 05:42:00', 'google-ai-studio/gemini-embedding-001', '2026-09-05', 0, '',
|
|
102
|
+
150000, 0, NULL, 'https://ai.google.dev/gemini-api/docs/pricing',
|
|
103
|
+
'First observation (0060): $0.15 per 1M input tokens, the text-only Gemini embedding. The multimodal gemini-embedding-2 is not in this catalogue — see the migration.');
|
|
104
|
+
|
|
105
|
+
-- `cached_input_micros_per_mtok` is NULL rather than 0: neither host prints a
|
|
106
|
+
-- cached rate for an embedding, and NULL is "not published" where 0 would be
|
|
107
|
+
-- "free". `priceUsage` charges the full input rate for a cached token it has
|
|
108
|
+
-- no rate for, which over-counts, and over-counting is the safe direction.
|
|
109
|
+
--
|
|
110
|
+
-- ## What this makes true of the whole catalogue
|
|
111
|
+
--
|
|
112
|
+
-- With `/v1/embeddings` served, **every modality the schema allows has a
|
|
113
|
+
-- route**. `blocked_by: 'unrouted'` — the state 0056 introduced for a row
|
|
114
|
+
-- catalogued ahead of its route — is now unreachable, and stays in the code
|
|
115
|
+
-- and in the published enum because the next modality will need it. The
|
|
116
|
+
-- assertion that keeps that honest is in `test/modality.test.ts`, and it fails
|
|
117
|
+
-- the day a fifth value is added to the CHECK constraint without a route,
|
|
118
|
+
-- which is exactly when somebody should be reading `docs/modalities.md`.
|
|
119
|
+
--
|
|
120
|
+
-- ## The transport, measured before it is relied on
|
|
121
|
+
--
|
|
122
|
+
-- The same open question 0057 and 0059 left, for the third time: `/compat` is
|
|
123
|
+
-- chat-only, so an embedding request takes the gateway's provider path, which
|
|
124
|
+
-- is documented for chat and implied for the rest. It could not be measured
|
|
125
|
+
-- from outside a deployed Worker — both gateways authenticate before they
|
|
126
|
+
-- route — so the first conformance run against a held credential is the
|
|
127
|
+
-- measurement. Google's OpenAI-compatibility layer does document
|
|
128
|
+
-- `client.embeddings.create` against `gemini-embedding-001`
|
|
129
|
+
-- (https://ai.google.dev/gemini-api/docs/openai, read 2026-09-05), so what is
|
|
130
|
+
-- unmeasured is the gateway leg rather than the host's own shape. The
|
|
131
|
+
-- fallback, as before, is a `base_url` on the upstream row rather than code.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
-- What each host does with a prompt, written down for the first time (#195).
|
|
2
|
+
--
|
|
3
|
+
-- `models.data_retention` has existed since 0007 and **no migration wrote it
|
|
4
|
+
-- until the labs arrived**. The gate that reads it is correct and has been
|
|
5
|
+
-- since 0030 — `no-training-on-data` allows `none` and `retained`, so NULL
|
|
6
|
+
-- fails closed — and it has been guarding nothing, because a gate with no data
|
|
7
|
+
-- refuses good models and catches no bad ones. That is the third instance of
|
|
8
|
+
-- one shape in this repository: `max_output` before 0023, `ACCOUNT_ID_SALT`
|
|
9
|
+
-- before #172, and this.
|
|
10
|
+
--
|
|
11
|
+
-- It has been harmless by luck rather than by design. Measured on production
|
|
12
|
+
-- 2026-09-05: **50 of 69 enabled rows carry no value**, and every enabled lane
|
|
13
|
+
-- has at least one such member — `bb/chat-assistant`, `bb/agentic-coding`,
|
|
14
|
+
-- `bb/code-completion`, `bb/code-review` and `bb-adm/code-review` among them.
|
|
15
|
+
-- Allocation is price-first, so end-user content goes to whichever of those is
|
|
16
|
+
-- cheapest this hour, with no retention gate consulted, which is the failure
|
|
17
|
+
-- `docs/auto-mode.md` names in as many words.
|
|
18
|
+
--
|
|
19
|
+
-- ## What was read, 2026-09-05, and it splits cleanly in two
|
|
20
|
+
--
|
|
21
|
+
-- **Two hosts state their terms for the API plainly, and those are written
|
|
22
|
+
-- below.** The other four make the answer depend on a setting only the account
|
|
23
|
+
-- holder can see, and for those NULL stays — with the reason recorded, which is
|
|
24
|
+
-- the difference between "nobody looked" and "looked, and it depends on us".
|
|
25
|
+
--
|
|
26
|
+
-- Retention is a property of the **host**, not of the model, which is why each
|
|
27
|
+
-- statement below updates by `upstream_id` rather than listing ids. A model
|
|
28
|
+
-- moving hosts would move its answer with it, and a row added to one of these
|
|
29
|
+
-- upstreams later inherits nothing — which is what the test in
|
|
30
|
+
-- `test/catalogue.test.ts` is for.
|
|
31
|
+
|
|
32
|
+
-- ── Workers AI: `none` ────────────────────────────────────────────────────────
|
|
33
|
+
-- https://developers.cloudflare.com/workers-ai/platform/data-usage/, read
|
|
34
|
+
-- 2026-09-05:
|
|
35
|
+
--
|
|
36
|
+
-- "Cloudflare does not use your Customer Content to (1) train any AI models
|
|
37
|
+
-- made available on Workers AI or (2) improve any Cloudflare or third-party
|
|
38
|
+
-- services, and would not do so unless we received your explicit consent."
|
|
39
|
+
--
|
|
40
|
+
-- "Your Customer Content for Workers AI may be stored by Cloudflare if you
|
|
41
|
+
-- specifically use a storage service (e.g., R2, KV, DO, Vectorize, etc.) in
|
|
42
|
+
-- conjunction with Workers AI."
|
|
43
|
+
--
|
|
44
|
+
-- So the prompt is not retained unless *we* store it, and Cruise stores no
|
|
45
|
+
-- payload: `gw-sensitive` is the default gateway and logs none, which is the
|
|
46
|
+
-- invariant at the top of CLAUDE.md. `none` rather than `retained`, and the
|
|
47
|
+
-- distinction is real — `retained` would claim a copy exists somewhere.
|
|
48
|
+
|
|
49
|
+
UPDATE models
|
|
50
|
+
SET data_retention = 'none',
|
|
51
|
+
data_retention_source = 'https://developers.cloudflare.com/workers-ai/platform/data-usage/',
|
|
52
|
+
data_retention_as_of = '2026-09-05'
|
|
53
|
+
WHERE upstream_id = 'workers-ai';
|
|
54
|
+
|
|
55
|
+
-- ── xAI: `retained` ───────────────────────────────────────────────────────────
|
|
56
|
+
-- https://docs.x.ai/developers/faq/security, read 2026-09-05:
|
|
57
|
+
--
|
|
58
|
+
-- "All API requests and responses are stored on our servers (encrypted at
|
|
59
|
+
-- rest) for 30 days for auditing purposes in the event of suspected abuse or
|
|
60
|
+
-- misuse."
|
|
61
|
+
--
|
|
62
|
+
-- "xAI never trains on your API inputs or outputs without your explicit
|
|
63
|
+
-- permission."
|
|
64
|
+
--
|
|
65
|
+
-- Kept and not trained on, which is exactly what `retained` means. The thirty
|
|
66
|
+
-- days and the encryption are not modelled — the column has three values and a
|
|
67
|
+
-- fourth would be a schema change for a fact no gate reads.
|
|
68
|
+
|
|
69
|
+
UPDATE models
|
|
70
|
+
SET data_retention = 'retained',
|
|
71
|
+
data_retention_source = 'https://docs.x.ai/developers/faq/security',
|
|
72
|
+
data_retention_as_of = '2026-09-05'
|
|
73
|
+
WHERE upstream_id = 'grok';
|
|
74
|
+
|
|
75
|
+
-- ── Mistral and DeepSeek: NULL stays, and this is the finding ─────────────────
|
|
76
|
+
--
|
|
77
|
+
-- Both carry **production traffic today**, and neither can be given a value
|
|
78
|
+
-- from its own pages, because for both the answer is an account setting.
|
|
79
|
+
--
|
|
80
|
+
-- **Mistral.** help.mistral.ai, "Do you use my user data to train your
|
|
81
|
+
-- artificial intelligence models?", read 2026-09-05: on the API's free mode
|
|
82
|
+
-- *"we may use your data (input and output) to train our artificial
|
|
83
|
+
-- intelligence models"*, with a right to opt out; on pay-as-you-go
|
|
84
|
+
-- *"customers retain full control over this processing and have the right to
|
|
85
|
+
-- opt out at any time"*. So the default is training and the remedy is a
|
|
86
|
+
-- setting. Nine rows, and `mistral/mistral-small-latest`, `codestral-2508`,
|
|
87
|
+
-- `mistral-medium-2604`, `ministral-3b-2512` and `ministral-8b-2512` are
|
|
88
|
+
-- members of four live lanes between them.
|
|
89
|
+
--
|
|
90
|
+
-- **DeepSeek.** The Open Platform Terms of Service say nothing about training
|
|
91
|
+
-- or retention at all — section 4.2 assigns rights in Inputs and Outputs and
|
|
92
|
+
-- stops there. The privacy policy is not API-specific and says data is used
|
|
93
|
+
-- *"to train and improve our technology, such as our machine learning models
|
|
94
|
+
-- and algorithms"*, with *"the right to opt-out of using your Personal Data
|
|
95
|
+
-- for training our models"*. Three rows, in three live lanes.
|
|
96
|
+
--
|
|
97
|
+
-- **Neither is written as `trains`**, and that is deliberate rather than
|
|
98
|
+
-- generous. `trains` would be a claim about what our account actually does,
|
|
99
|
+
-- and nobody has read our own opt-out state; a guess in the safe direction is
|
|
100
|
+
-- still a guess, and 0011 and 0020 are both migrations undoing exactly that.
|
|
101
|
+
-- NULL and `trains` are identical at the gate — both fail `no-training-on-data`
|
|
102
|
+
-- closed — so nothing is served differently by leaving it. What differs is
|
|
103
|
+
-- what a person reads, and NULL with this comment beside it is the honest one.
|
|
104
|
+
--
|
|
105
|
+
-- **What resolves them is not a migration.** Whoever holds each account opens
|
|
106
|
+
-- it, reads the training setting, and writes what they read — the same act
|
|
107
|
+
-- 0049 asked for on Google and 0059 on ElevenLabs. Until then these ten rows
|
|
108
|
+
-- are the reason a lane requiring `no-training-on-data` is nearly empty.
|
|
109
|
+
|
|
110
|
+
-- Google AI Studio (five rows, 0049) and ElevenLabs (three rows, 0059) are
|
|
111
|
+
-- untouched here for the same reason and already carry it in their own
|
|
112
|
+
-- migrations: Google's terms follow the account tier, ElevenLabs' the plan.
|
|
113
|
+
|
|
114
|
+
-- ## What this migration deliberately does not change
|
|
115
|
+
--
|
|
116
|
+
-- **Allocation.** A lane still ranks price-first and consults retention only
|
|
117
|
+
-- where the lane carries the requirement. Ranking an unknown-retention member
|
|
118
|
+
-- below a known-safe one is the change #195 asks for next, and it is a change
|
|
119
|
+
-- to what live traffic does with a cost attached — `bb/chat-assistant` would
|
|
120
|
+
-- start preferring Workers AI rows over cheaper Mistral ones — so it is a
|
|
121
|
+
-- decision with a price, made by somebody, not a line smuggled into a backfill.
|