@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,172 @@
|
|
|
1
|
+
-- Three labs we hold keys for and had no rows against (#188).
|
|
2
|
+
--
|
|
3
|
+
-- All three are on Cloudflare's unified `/compat` endpoint, so this is the tier-1
|
|
4
|
+
-- shape [`upstreams.md`](../docs/upstreams.md) describes — a key, a migration
|
|
5
|
+
-- and a conformance run — with one addition: **xAI has no `upstreams` row**, and
|
|
6
|
+
-- gets one here. That is tier 3, and it is still no request-path code.
|
|
7
|
+
--
|
|
8
|
+
-- Every figure fetched 2026-09-02 UTC from the host's own pages, cited per
|
|
9
|
+
-- section. The wall clock read after midnight IST; the dates are the 2nd because
|
|
10
|
+
-- `pricesInForce` resolves against `date('now')`, which is UTC. Same trap as
|
|
11
|
+
-- 0020, 0021, 0050 and 0052.
|
|
12
|
+
--
|
|
13
|
+
-- **`data_retention` is read for two of the three, and NULL for the third
|
|
14
|
+
-- because its terms could not be read.** The first draft of this migration left
|
|
15
|
+
-- all ten NULL and called that a principle; it was not one. The rule is that a
|
|
16
|
+
-- term is written only when somebody read it on the host's page, and nobody had
|
|
17
|
+
-- looked. Raised in review of PR #206, and the reviewer was right.
|
|
18
|
+
--
|
|
19
|
+
-- Anthropic and Groq both answer plainly and are recorded per model below.
|
|
20
|
+
-- **xAI could not be read**: `x.ai/legal/privacy-policy` and
|
|
21
|
+
-- `x.ai/legal/terms-of-service-enterprise` both answer 403 to an ordinary
|
|
22
|
+
-- fetch, and `docs.x.ai/docs/data-privacy` is a 404. So those four rows keep
|
|
23
|
+
-- NULL, which fails closed — refused by any lane carrying
|
|
24
|
+
-- `no-training-on-data` — and the attempts are recorded here so the next person
|
|
25
|
+
-- does not repeat them. #195 owns the column across the whole catalogue.
|
|
26
|
+
|
|
27
|
+
-- ── xAI ───────────────────────────────────────────────────────────────────────
|
|
28
|
+
-- **The upstream id must be `grok`, not `xai`.** `upstreamModelString` builds
|
|
29
|
+
-- `<upstream_id>/<model>` and that string is what the gateway parses to select a
|
|
30
|
+
-- provider; Cloudflare's slug is `grok` and its documented model form is
|
|
31
|
+
-- `grok/{model}`. An `xai` row would produce `xai/grok-4.6`, which the gateway
|
|
32
|
+
-- does not recognise. The derived secret is therefore `PROVIDER_KEY_GROK`, which
|
|
33
|
+
-- is what is set on both environments.
|
|
34
|
+
INSERT INTO upstreams (id, name, kind) VALUES ('grok', 'xAI', 'compat');
|
|
35
|
+
|
|
36
|
+
-- Prices and windows: https://docs.x.ai/docs/models.
|
|
37
|
+
--
|
|
38
|
+
-- Every model is priced in two tiers by prompt size, and the boundary is stated
|
|
39
|
+
-- as **"< 200k" / "≥ 200k"** — so `prompt_tokens_from` is 200000, at the
|
|
40
|
+
-- boundary. Note this differs from Gemini's `> 200k` in 0049, which starts at
|
|
41
|
+
-- 200001. The two hosts genuinely differ by one token and the column means "at
|
|
42
|
+
-- least this long", so both are written as the page states rather than made
|
|
43
|
+
-- uniform.
|
|
44
|
+
--
|
|
45
|
+
-- No page states a maximum output for any Grok model, so `max_output` is NULL
|
|
46
|
+
-- and `checkLimits` derives a bound from the window (0023). `data_retention` is
|
|
47
|
+
-- NULL for the reason in the header: xAI's legal pages refuse an ordinary
|
|
48
|
+
-- fetch, so nobody has read the terms. Not an assumption that they train — an
|
|
49
|
+
-- absence of evidence either way, which is what NULL means.
|
|
50
|
+
INSERT INTO models (id, upstream_id, upstream_model, max_context, max_output) VALUES
|
|
51
|
+
('grok/grok-4.6', 'grok', 'grok-4.6', 500000, NULL),
|
|
52
|
+
('grok/grok-4.5', 'grok', 'grok-4.5', 500000, NULL),
|
|
53
|
+
('grok/grok-4.3', 'grok', 'grok-4.3', 1000000, NULL),
|
|
54
|
+
('grok/grok-build-0.1', 'grok', 'grok-build-0.1', 256000, NULL);
|
|
55
|
+
|
|
56
|
+
-- The `grok-4.20-0309-*` variants are left out: the pricing page gives them "the
|
|
57
|
+
-- same pricing as grok-4.3" rather than a row of their own, and a rate quoted by
|
|
58
|
+
-- reference to another model is not a rate this table should hold. They also
|
|
59
|
+
-- carry a dated snapshot id, so they are a pin of something already listed.
|
|
60
|
+
-- `grok-imagine-*` generate images and video and are not chat models.
|
|
61
|
+
|
|
62
|
+
INSERT INTO price_observations
|
|
63
|
+
(observed_at, model, effective_from, prompt_tokens_from, peak_utc,
|
|
64
|
+
input_micros_per_mtok, output_micros_per_mtok, cached_input_micros_per_mtok, source, note)
|
|
65
|
+
VALUES
|
|
66
|
+
('2026-09-02 00:00:00', 'grok/grok-4.6', '2026-09-02', 0, '',
|
|
67
|
+
2000000, 6000000, 500000, 'https://docs.x.ai/docs/models', 'First observation: xAI added to the catalogue (0053).'),
|
|
68
|
+
('2026-09-02 00:00:00', 'grok/grok-4.6', '2026-09-02', 200000, '',
|
|
69
|
+
4000000, 12000000, 1000000, 'https://docs.x.ai/docs/models', 'Long-prompt tier, stated as prompts at or above 200k tokens.'),
|
|
70
|
+
('2026-09-02 00:00:00', 'grok/grok-4.5', '2026-09-02', 0, '',
|
|
71
|
+
2000000, 6000000, 300000, 'https://docs.x.ai/docs/models', 'First observation: xAI added to the catalogue (0053).'),
|
|
72
|
+
('2026-09-02 00:00:00', 'grok/grok-4.5', '2026-09-02', 200000, '',
|
|
73
|
+
4000000, 12000000, 600000, 'https://docs.x.ai/docs/models', 'Long-prompt tier, stated as prompts at or above 200k tokens.'),
|
|
74
|
+
('2026-09-02 00:00:00', 'grok/grok-4.3', '2026-09-02', 0, '',
|
|
75
|
+
1250000, 2500000, 200000, 'https://docs.x.ai/docs/models', 'First observation: xAI added to the catalogue (0053).'),
|
|
76
|
+
('2026-09-02 00:00:00', 'grok/grok-4.3', '2026-09-02', 200000, '',
|
|
77
|
+
2500000, 5000000, 400000, 'https://docs.x.ai/docs/models', 'Long-prompt tier, stated as prompts at or above 200k tokens.'),
|
|
78
|
+
('2026-09-02 00:00:00', 'grok/grok-build-0.1', '2026-09-02', 0, '',
|
|
79
|
+
1000000, 2000000, 200000, 'https://docs.x.ai/docs/models', 'First observation: xAI added to the catalogue (0053).'),
|
|
80
|
+
('2026-09-02 00:00:00', 'grok/grok-build-0.1', '2026-09-02', 200000, '',
|
|
81
|
+
2000000, 4000000, 400000, 'https://docs.x.ai/docs/models', 'Long-prompt tier, stated as prompts at or above 200k tokens.');
|
|
82
|
+
|
|
83
|
+
-- ── Anthropic ─────────────────────────────────────────────────────────────────
|
|
84
|
+
-- Prices: https://platform.claude.com/docs/en/about-claude/pricing.
|
|
85
|
+
-- Ids, windows and maximum output: the models overview on the same host.
|
|
86
|
+
--
|
|
87
|
+
-- `cached_input` is the **cache read** rate, which is the one a served request
|
|
88
|
+
-- pays. The page also prints two cache *write* rates (1.25x and 2x base, by TTL)
|
|
89
|
+
-- and this table has no column for either, so a cached-write-heavy workload is
|
|
90
|
+
-- under-counted here. Recorded rather than hidden: it is the same shape of gap
|
|
91
|
+
-- as the audio rates 0049 excluded Gemini rows over, and it belongs to whoever
|
|
92
|
+
-- takes #5.
|
|
93
|
+
--
|
|
94
|
+
-- Claude Fable 5.1 is the exception the page flags itself: its cache read is
|
|
95
|
+
-- 0.025x base rather than the usual 0.1x, which is why $10 input pairs with
|
|
96
|
+
-- $0.25 rather than $1.
|
|
97
|
+
-- `retained` on the host's own words: *"By default, we will not use your inputs
|
|
98
|
+
-- or outputs from our commercial products to train our models."* Retention with
|
|
99
|
+
-- no training right is exactly what the middle value means.
|
|
100
|
+
INSERT INTO models (id, upstream_id, upstream_model, max_context, max_output,
|
|
101
|
+
data_retention, data_retention_source, data_retention_as_of) VALUES
|
|
102
|
+
('anthropic/claude-fable-5-1', 'anthropic', 'claude-fable-5-1', 1000000, 128000,
|
|
103
|
+
'retained', 'https://privacy.claude.com/en/articles/7996868-is-my-data-used-for-model-training', '2026-09-02'),
|
|
104
|
+
('anthropic/claude-opus-5', 'anthropic', 'claude-opus-5', 1000000, 128000,
|
|
105
|
+
'retained', 'https://privacy.claude.com/en/articles/7996868-is-my-data-used-for-model-training', '2026-09-02'),
|
|
106
|
+
('anthropic/claude-sonnet-5', 'anthropic', 'claude-sonnet-5', 1000000, 128000,
|
|
107
|
+
'retained', 'https://privacy.claude.com/en/articles/7996868-is-my-data-used-for-model-training', '2026-09-02'),
|
|
108
|
+
-- The one id here that is dated. Its alias `claude-haiku-4-5` resolves to this
|
|
109
|
+
-- snapshot, and 0020's rule is that the table pins what the alias points at.
|
|
110
|
+
('anthropic/claude-haiku-4-5-20251001', 'anthropic', 'claude-haiku-4-5-20251001', 200000, 64000,
|
|
111
|
+
'retained', 'https://privacy.claude.com/en/articles/7996868-is-my-data-used-for-model-training', '2026-09-02');
|
|
112
|
+
|
|
113
|
+
INSERT INTO price_observations
|
|
114
|
+
(observed_at, model, effective_from, prompt_tokens_from, peak_utc,
|
|
115
|
+
input_micros_per_mtok, output_micros_per_mtok, cached_input_micros_per_mtok, source, note)
|
|
116
|
+
VALUES
|
|
117
|
+
('2026-09-02 00:00:00', 'anthropic/claude-fable-5-1', '2026-09-02', 0, '',
|
|
118
|
+
10000000, 50000000, 250000, 'https://platform.claude.com/docs/en/about-claude/pricing',
|
|
119
|
+
'First observation: Anthropic added to the catalogue (0053). Cache read is 0.025x base on this model, which the page states explicitly.'),
|
|
120
|
+
('2026-09-02 00:00:00', 'anthropic/claude-opus-5', '2026-09-02', 0, '',
|
|
121
|
+
5000000, 25000000, 500000, 'https://platform.claude.com/docs/en/about-claude/pricing',
|
|
122
|
+
'First observation: Anthropic added to the catalogue (0053).'),
|
|
123
|
+
('2026-09-02 00:00:00', 'anthropic/claude-sonnet-5', '2026-09-02', 0, '',
|
|
124
|
+
2000000, 10000000, 200000, 'https://platform.claude.com/docs/en/about-claude/pricing',
|
|
125
|
+
'First observation: Anthropic added to the catalogue (0053). The page notes the scheduled rise to $3/$15 on 2026-09-01 will not occur, so this is the standard rate rather than an introductory one.'),
|
|
126
|
+
('2026-09-02 00:00:00', 'anthropic/claude-haiku-4-5-20251001', '2026-09-02', 0, '',
|
|
127
|
+
1000000, 5000000, 100000, 'https://platform.claude.com/docs/en/about-claude/pricing',
|
|
128
|
+
'First observation: Anthropic added to the catalogue (0053).');
|
|
129
|
+
|
|
130
|
+
-- ── Groq ──────────────────────────────────────────────────────────────────────
|
|
131
|
+
-- Prices, windows and maximum output: https://console.groq.com/docs/models.
|
|
132
|
+
--
|
|
133
|
+
-- **Two rows out of nine, and the reason is worth reading.** Groq's API serves
|
|
134
|
+
-- nine chat models to this key, and the console prices exactly two of them. The
|
|
135
|
+
-- Llama rows say "Contact Sales", and `groq/compound`, `groq/compound-mini`,
|
|
136
|
+
-- `qwen/qwen3.6-27b`, `qwen/qwen3.8-27b` and `allam-2-7b` print no rate at all.
|
|
137
|
+
-- An unpriced model is charged its **whole reservation** rather than its usage,
|
|
138
|
+
-- so inserting them would quietly distort every budget they touched.
|
|
139
|
+
--
|
|
140
|
+
-- The two that are priced are both models this catalogue already holds through
|
|
141
|
+
-- Workers AI, and that is the point rather than a redundancy to trim. Groq is
|
|
142
|
+
-- **cheaper on both axes** — $0.15/$0.60 against Workers AI's $0.35/$0.75 for
|
|
143
|
+
-- the 120b — on a separate billing relationship, which is exactly the
|
|
144
|
+
-- independence argument #188 makes from the day the DeepSeek account went
|
|
145
|
+
-- overdrawn and every `deepseek/…` request answered 402 for twenty hours.
|
|
146
|
+
--
|
|
147
|
+
-- Neither price row states a cached-input rate, so it stays NULL and a cache hit
|
|
148
|
+
-- is billed at the full input rate. The safe direction, and still drift.
|
|
149
|
+
-- `retained`, and Groq's services agreement is the clearest of the three:
|
|
150
|
+
-- *"Groq is not permitted to use Inputs or Outputs for training or fine-tuning
|
|
151
|
+
-- any AI Model Services or other models, unless explicitly granted permission
|
|
152
|
+
-- or instructed by Customer."* Its privacy policy is the wrong document and says
|
|
153
|
+
-- so — it *"does not apply to the information that we process as a 'data
|
|
154
|
+
-- processor' on behalf of customers"* — which is worth knowing before reading it
|
|
155
|
+
-- and concluding nothing.
|
|
156
|
+
INSERT INTO models (id, upstream_id, upstream_model, max_context, max_output,
|
|
157
|
+
data_retention, data_retention_source, data_retention_as_of) VALUES
|
|
158
|
+
('groq/openai/gpt-oss-120b', 'groq', 'openai/gpt-oss-120b', 131072, 65536,
|
|
159
|
+
'retained', 'https://console.groq.com/docs/legal/services-agreement', '2026-09-02'),
|
|
160
|
+
('groq/openai/gpt-oss-20b', 'groq', 'openai/gpt-oss-20b', 131072, 65536,
|
|
161
|
+
'retained', 'https://console.groq.com/docs/legal/services-agreement', '2026-09-02');
|
|
162
|
+
|
|
163
|
+
INSERT INTO price_observations
|
|
164
|
+
(observed_at, model, effective_from, prompt_tokens_from, peak_utc,
|
|
165
|
+
input_micros_per_mtok, output_micros_per_mtok, cached_input_micros_per_mtok, source, note)
|
|
166
|
+
VALUES
|
|
167
|
+
('2026-09-02 00:00:00', 'groq/openai/gpt-oss-120b', '2026-09-02', 0, '',
|
|
168
|
+
150000, 600000, NULL, 'https://console.groq.com/docs/models',
|
|
169
|
+
'First observation: Groq added to the catalogue (0053).'),
|
|
170
|
+
('2026-09-02 00:00:00', 'groq/openai/gpt-oss-20b', '2026-09-02', 0, '',
|
|
171
|
+
75000, 300000, NULL, 'https://console.groq.com/docs/models',
|
|
172
|
+
'First observation: Groq added to the catalogue (0053).');
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
-- A service key that can change a tenant's tier and do nothing else (#203).
|
|
2
|
+
--
|
|
3
|
+
-- The gap: #191 made a tier changeable and gave it one caller, `npm run
|
|
4
|
+
-- tier:set` — a person at a terminal holding a Cloudflare API token and a
|
|
5
|
+
-- checkout of this repo. So a customer who outgrows Free is still stopped at
|
|
6
|
+
-- the write, and the answer is still "ask us", which arrives at the moment
|
|
7
|
+
-- they are trying to give us money. `setTier` already does the hard parts: it
|
|
8
|
+
-- refuses a downgrade the tenant does not fit and it writes the audit row
|
|
9
|
+
-- that was #191's actual gap. What it lacks is a caller that is not a laptop.
|
|
10
|
+
--
|
|
11
|
+
-- 0034 said a tier is the name that ties a subscription to its quotas, so the
|
|
12
|
+
-- thing that learns a subscription changed is the thing that should set it —
|
|
13
|
+
-- the payments plane, beside `credits`, `balance` and `provision`, rather than
|
|
14
|
+
-- a platform-operator role invented under `/admin` to carry one command (#91).
|
|
15
|
+
--
|
|
16
|
+
-- `purpose` gains a fourth value on 0038's terms, not beyond them: one
|
|
17
|
+
-- capability per key, issued for one value and never two. `tier` reads a
|
|
18
|
+
-- tenant's tier, caps and usage and sets it; it mints nothing, creates
|
|
19
|
+
-- nothing, and cannot see a balance. A `provision` key is refused at the tier
|
|
20
|
+
-- write for 0042's reason turned around — creating a namespace and changing
|
|
21
|
+
-- what it is entitled to are separate powers, and the second one has a price
|
|
22
|
+
-- attached.
|
|
23
|
+
--
|
|
24
|
+
-- The one route the two share is the uid → tenants lookup, which both a
|
|
25
|
+
-- provision and a tier key may make: it answers "which tenants does this
|
|
26
|
+
-- person already have", which is the question each of them must ask *before*
|
|
27
|
+
-- writing anything, and neither can act on it without its own capability.
|
|
28
|
+
--
|
|
29
|
+
-- Naming that as the widening it is: it is the first read a `provision` key
|
|
30
|
+
-- gets of state it did not write, so a leaked provisioning credential now
|
|
31
|
+
-- learns which tenants a uid it guesses belongs to. Accepted because that key
|
|
32
|
+
-- already learns an owner uid from `tenant_owner_conflict` and creates the
|
|
33
|
+
-- tenants it would be asking about, and because the alternative — a second
|
|
34
|
+
-- credential on the provisioning screen for one read — is the shape 0038 was
|
|
35
|
+
-- written to avoid. The trigger to split it is a provision key held by anybody
|
|
36
|
+
-- who is not the party that provisions.
|
|
37
|
+
--
|
|
38
|
+
-- SQLite cannot widen a CHECK in place, so the table is rebuilt, and 0038's
|
|
39
|
+
-- and 0042's note on why that is safe applies unchanged: the name and every
|
|
40
|
+
-- column stay identical, so code deployed before this migration and code
|
|
41
|
+
-- deployed after read exactly the same shape. Widening is the safe direction
|
|
42
|
+
-- on its own — no existing row can fail the new CHECK, and a Worker deployed
|
|
43
|
+
-- before this simply never matches a `tier` row, because `authenticate` names
|
|
44
|
+
-- the purposes it will accept in the lookup itself.
|
|
45
|
+
CREATE TABLE service_keys_new (
|
|
46
|
+
id TEXT PRIMARY KEY,
|
|
47
|
+
prefix TEXT NOT NULL UNIQUE,
|
|
48
|
+
key_hash TEXT NOT NULL,
|
|
49
|
+
purpose TEXT NOT NULL CHECK (purpose IN ('credits', 'balance', 'provision', 'tier')),
|
|
50
|
+
created_by TEXT NOT NULL,
|
|
51
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
52
|
+
revoked_at TEXT,
|
|
53
|
+
last_used_at TEXT
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
INSERT INTO service_keys_new (id, prefix, key_hash, purpose, created_by, created_at, revoked_at, last_used_at)
|
|
57
|
+
SELECT id, prefix, key_hash, purpose, created_by, created_at, revoked_at, last_used_at FROM service_keys;
|
|
58
|
+
|
|
59
|
+
DROP TABLE service_keys;
|
|
60
|
+
ALTER TABLE service_keys_new RENAME TO service_keys;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
-- What happens when the caller states no `max_tokens` (#217).
|
|
2
|
+
--
|
|
3
|
+
-- #219 stopped the reservation guessing: an unread output cap became unknown
|
|
4
|
+
-- rather than the whole context window, which had produced a 4,456,448µ bound
|
|
5
|
+
-- on `bb-adm/code-review` for requests that cost about 30µ. What it left is a
|
|
6
|
+
-- request nobody can bound — served on the flat floor for internal traffic,
|
|
7
|
+
-- refused outright for a billed tenant.
|
|
8
|
+
--
|
|
9
|
+
-- Filling `models.max_output` was the obvious next step and turns out to rest
|
|
10
|
+
-- on a premise that does not hold. Read 2026-09-04: **no host in this catalogue
|
|
11
|
+
-- publishes an output ceiling.** Cloudflare's per-model pages publish a context
|
|
12
|
+
-- window and `max_tokens` as a parameter with `default: 256`; Mistral's API
|
|
13
|
+
-- reference publishes one rule, "the token count of your prompt plus
|
|
14
|
+
-- `max_tokens` cannot exceed the model's context length"; Meta publishes
|
|
15
|
+
-- neither. So there is nothing to read into the column that exists.
|
|
16
|
+
--
|
|
17
|
+
-- What there *is* to read is a different fact, and the question it answers is
|
|
18
|
+
-- the one that actually governs an unbounded request: **what does this platform
|
|
19
|
+
-- do when the caller sends nothing.** Two columns, because the answer lives at
|
|
20
|
+
-- two levels and pretending otherwise is what made `max_output` ambiguous:
|
|
21
|
+
--
|
|
22
|
+
-- upstreams.default_output the platform's own API default. Cloudflare's is
|
|
23
|
+
-- 256, documented identically on every model page
|
|
24
|
+
-- — verified on four spanning four families
|
|
25
|
+
-- (llama-3.3-70b-instruct-fp8-fast, gpt-oss-120b,
|
|
26
|
+
-- qwq-32b, mistral-small-3.1-24b-instruct) on
|
|
27
|
+
-- 2026-09-04. It is a property of the API, not of
|
|
28
|
+
-- any model, which is why it belongs here.
|
|
29
|
+
--
|
|
30
|
+
-- models.default_output an override for a model that differs from its
|
|
31
|
+
-- platform. Null means inherit. Nothing sets it
|
|
32
|
+
-- today; it exists so the first model that
|
|
33
|
+
-- contradicts its host is a row rather than a
|
|
34
|
+
-- rewrite.
|
|
35
|
+
--
|
|
36
|
+
-- **Null at both levels stays unbounded, deliberately.** Mistral and Meta
|
|
37
|
+
-- publish no default, and their effective behaviour is to run to the context
|
|
38
|
+
-- window — which is exactly the bound #219 removed for being true and useless.
|
|
39
|
+
-- Recording "we read it and there is nothing" is the honest state, and it keeps
|
|
40
|
+
-- those requests on the path that says so: the flat floor internally, and a
|
|
41
|
+
-- `request_unbounded` a billed tenant can act on. `docs/upstreams.md` takes the
|
|
42
|
+
-- same position on `data_retention`: the read-and-absent case and the not-yet-
|
|
43
|
+
-- read case look identical in the column and are distinguished here, in the
|
|
44
|
+
-- migration, rather than by a second column nothing would branch on.
|
|
45
|
+
--
|
|
46
|
+
-- The value is not a ceiling and must never be used as one. `checkLimits` still
|
|
47
|
+
-- validates a caller's own `max_tokens` against `max_output` and the window; a
|
|
48
|
+
-- default is what happens in the absence of a request, not a limit on one.
|
|
49
|
+
--
|
|
50
|
+
-- Positive or absent, on both: zero is not a default, it is a model that emits
|
|
51
|
+
-- nothing, and a negative one is a typo that would bound every request at the
|
|
52
|
+
-- flat floor while looking like a limit that had been read. Raised by review of
|
|
53
|
+
-- PR #221, and the same guard `projects.default_max_tokens` carries below.
|
|
54
|
+
ALTER TABLE upstreams ADD COLUMN default_output INTEGER
|
|
55
|
+
CHECK (default_output IS NULL OR default_output > 0);
|
|
56
|
+
ALTER TABLE models ADD COLUMN default_output INTEGER
|
|
57
|
+
CHECK (default_output IS NULL OR default_output > 0);
|
|
58
|
+
|
|
59
|
+
UPDATE upstreams SET default_output = 256 WHERE id = 'workers-ai';
|
|
60
|
+
|
|
61
|
+
-- The per-project cap, for a caller that would rather state it once than on
|
|
62
|
+
-- every request (#217).
|
|
63
|
+
--
|
|
64
|
+
-- A billed tenant is refused an unbounded request, because a reservation that
|
|
65
|
+
-- is not a bound is the gap a wallet cannot survive (#96) — and the alternative
|
|
66
|
+
-- considered first was for Cruise to inject a platform-wide `max_tokens` on
|
|
67
|
+
-- everyone's behalf. That was rejected on this repo's own evidence: #142
|
|
68
|
+
-- documents sixty production rows where a cap on a reasoning model produced a
|
|
69
|
+
-- completion of pure reasoning and no content at all — a 200, a bill, and
|
|
70
|
+
-- nothing to show. Imposing a cap manufactures that failure for requests that
|
|
71
|
+
-- did not ask for one, and silent truncation is a worse answer than a refusal
|
|
72
|
+
-- naming what to send.
|
|
73
|
+
--
|
|
74
|
+
-- So the cap is the project's own decision, opt-in, null meaning no default.
|
|
75
|
+
-- Where it is set, Cruise sends it upstream, which is what makes it a *bound*
|
|
76
|
+
-- rather than an assumption: the figure the reservation holds is the figure the
|
|
77
|
+
-- provider was told to stop at.
|
|
78
|
+
ALTER TABLE projects ADD COLUMN default_max_tokens INTEGER
|
|
79
|
+
CHECK (default_max_tokens IS NULL OR default_max_tokens > 0);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
-- What a row *is*, now that not every row will be a chat model (#226, #220).
|
|
2
|
+
--
|
|
3
|
+
-- `models` has never needed to say. Every route is chat, so every row was, and
|
|
4
|
+
-- nothing had a reason to ask. `docs/modalities.md` decided 2026-09-04 that the
|
|
5
|
+
-- data plane grows past chat — images first, then speech, then embeddings —
|
|
6
|
+
-- and the moment one such row exists two things go wrong silently:
|
|
7
|
+
--
|
|
8
|
+
-- The chat route forwards a chat body to it. The provider's 400 looks like
|
|
9
|
+
-- the caller's fault, which is the shape docs/v0.md §3 already names for a
|
|
10
|
+
-- misconfigured `upstream_model`, and nothing on our side said no.
|
|
11
|
+
--
|
|
12
|
+
-- `/v1/models` lists it as a chat-capable row with every capability NULL —
|
|
13
|
+
-- which reads as "unmeasured", the honest state for a chat model nobody has
|
|
14
|
+
-- run the suite against, and is instead a model the suite could never
|
|
15
|
+
-- measure that way at all.
|
|
16
|
+
--
|
|
17
|
+
-- One column, closed set, defaulted to the only value that has ever been true.
|
|
18
|
+
-- Every existing row is `chat`, and that is a fact rather than a placeholder:
|
|
19
|
+
-- 0050 and 0052 left `muse-image-1.0` and `muse-voice-transcribe-1.0` out of
|
|
20
|
+
-- the catalogue precisely because there was nowhere to say what they were.
|
|
21
|
+
--
|
|
22
|
+
-- The set is the four modalities the decision names and no more. A fifth is a
|
|
23
|
+
-- migration, on purpose — `job` in 0029 took the same position, so that a new
|
|
24
|
+
-- word is a decision somebody wrote down rather than a string somebody typed.
|
|
25
|
+
-- Video is deliberately absent: #220 says nobody is blocked on it, and a value
|
|
26
|
+
-- nothing can route would read exactly like one something can.
|
|
27
|
+
--
|
|
28
|
+
-- What reads it, in this migration's companion change: the chat route refuses
|
|
29
|
+
-- any other value with `unsupported_capability` (the request needs a capability
|
|
30
|
+
-- the model lacks, and the code exists); a lane write refuses members of two
|
|
31
|
+
-- modalities, so a lane is never mixed; `/v1/models` and `/admin/models` carry
|
|
32
|
+
-- it; the conformance runner measures chat rows only, since its checks are
|
|
33
|
+
-- chat checks and a fail recorded against an image model would blame the
|
|
34
|
+
-- wrong thing. No new route, no new price unit, no new capability column —
|
|
35
|
+
-- those are #227 to #231, and each assumes this.
|
|
36
|
+
ALTER TABLE models ADD COLUMN modality TEXT NOT NULL DEFAULT 'chat'
|
|
37
|
+
CHECK (modality IN ('chat', 'image', 'speech', 'embedding'));
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
-- The first non-chat rows that a route can reach, and the bound that makes one
|
|
2
|
+
-- billable (#227, slice 2 of #220, decided in `docs/modalities.md`).
|
|
3
|
+
--
|
|
4
|
+
-- 0056 gave a row a modality and nothing else; every value but `chat` named a
|
|
5
|
+
-- thing the data plane could not serve. This is the first that it can.
|
|
6
|
+
--
|
|
7
|
+
-- ## What the host charges, read 2026-09-04
|
|
8
|
+
--
|
|
9
|
+
-- The issue that opened this assumed images are priced per image. They are not,
|
|
10
|
+
-- on this host: `gpt-image-*` prices a generation **per token** — text prompt
|
|
11
|
+
-- tokens in, image tokens out — and the response carries both in `usage`. So
|
|
12
|
+
-- the three columns `price_observations` already has price an image generation
|
|
13
|
+
-- exactly, and slice 2 needs **no new price unit**. That is the whole reason
|
|
14
|
+
-- images come before speech, which does need one (#228).
|
|
15
|
+
--
|
|
16
|
+
-- Source: https://developers.openai.com/api/docs/pricing, fetched 2026-09-04
|
|
17
|
+
-- **UTC** — and this is the first migration here where that word earns its
|
|
18
|
+
-- keep. The wall clock that wrote it read 01:47 on the **5th**, in +05:30, and
|
|
19
|
+
-- the two are the same instant. Every date below is the UTC one, because that
|
|
20
|
+
-- is what `date('now')` returns and therefore what `effective_from <= date('now')`
|
|
21
|
+
-- compares against: a row dated the 5th is a row not yet in force, so the
|
|
22
|
+
-- catalogue would have read these three as unpriced for another four hours.
|
|
23
|
+
-- 0021 corrected exactly this after the fact; here it was caught by the test
|
|
24
|
+
-- that prices every inserted model, which is the check 0021 left behind.
|
|
25
|
+
--
|
|
26
|
+
-- Three rows, and the exclusions are 0049's rule again — the current catalogue
|
|
27
|
+
-- minus what is retiring, aliased, or unpriceable:
|
|
28
|
+
--
|
|
29
|
+
-- gpt-image-1 superseded by 2, and dearest of the four on output
|
|
30
|
+
-- ($40/Mtok against $30). A weekly conformance bill for
|
|
31
|
+
-- a model a price-first lane would never pick.
|
|
32
|
+
-- chatgpt-image-latest an alias, priced identically to 1.5. 0020's rule: an
|
|
33
|
+
-- alias moving is a rate change with no signal.
|
|
34
|
+
--
|
|
35
|
+
-- **The image *input* rate is deliberately not recorded.** The page prints one
|
|
36
|
+
-- ($8.00/Mtok on 2 and 1.5, $2.50 on mini) and it applies to an *edit*, where a
|
|
37
|
+
-- reference image is sent in. This ledger has one input rate per row, so writing
|
|
38
|
+
-- the image rate would misprice every generation and writing both is what
|
|
39
|
+
-- `price_components` exists for (#228). Until that lands, `/v1/images/edits` is
|
|
40
|
+
-- not served at all — a route that cannot bill correctly is better absent than
|
|
41
|
+
-- present and under-counting, which is the direction 0011 exists to prevent.
|
|
42
|
+
-- `src/images.ts` refuses a *generation* whose response reports image input
|
|
43
|
+
-- tokens for the same reason: it charges the reservation rather than a figure it
|
|
44
|
+
-- knows is short.
|
|
45
|
+
|
|
46
|
+
INSERT INTO models (id, upstream_id, upstream_model, modality,
|
|
47
|
+
data_retention, data_retention_source, data_retention_as_of) VALUES
|
|
48
|
+
('openai/gpt-image-2', 'openai', 'gpt-image-2', 'image',
|
|
49
|
+
'retained', 'https://developers.openai.com/api/docs/guides/your-data', '2026-09-04'),
|
|
50
|
+
('openai/gpt-image-1.5', 'openai', 'gpt-image-1.5', 'image',
|
|
51
|
+
'retained', 'https://developers.openai.com/api/docs/guides/your-data', '2026-09-04'),
|
|
52
|
+
('openai/gpt-image-1-mini', 'openai', 'gpt-image-1-mini', 'image',
|
|
53
|
+
'retained', 'https://developers.openai.com/api/docs/guides/your-data', '2026-09-04');
|
|
54
|
+
|
|
55
|
+
-- `max_context` and `max_output` are NULL on all three, and that is a fact
|
|
56
|
+
-- rather than a gap. Both mean tokens of a *completion*, and the host publishes
|
|
57
|
+
-- neither for an image model: what bounds an image request is how many output
|
|
58
|
+
-- tokens one image at one size and quality costs, which is the table below.
|
|
59
|
+
-- `checkLimits` reads those two columns and is a chat gate; the image route
|
|
60
|
+
-- reads the variants instead (`src/routing-image.ts`).
|
|
61
|
+
|
|
62
|
+
INSERT INTO price_observations
|
|
63
|
+
(observed_at, model, effective_from, prompt_tokens_from, peak_utc,
|
|
64
|
+
input_micros_per_mtok, output_micros_per_mtok, cached_input_micros_per_mtok, source, note)
|
|
65
|
+
VALUES
|
|
66
|
+
('2026-09-04 00:00:00', 'openai/gpt-image-2', '2026-09-04', 0, '',
|
|
67
|
+
5000000, 30000000, 1250000, 'https://developers.openai.com/api/docs/pricing',
|
|
68
|
+
'First observation: the first image models in the catalogue (0057). Text input, image output, cached text input. The image *input* rate is not this column — see the migration.'),
|
|
69
|
+
('2026-09-04 00:00:00', 'openai/gpt-image-1.5', '2026-09-04', 0, '',
|
|
70
|
+
5000000, 32000000, 1250000, 'https://developers.openai.com/api/docs/pricing',
|
|
71
|
+
'First observation: the first image models in the catalogue (0057).'),
|
|
72
|
+
('2026-09-04 00:00:00', 'openai/gpt-image-1-mini', '2026-09-04', 0, '',
|
|
73
|
+
2000000, 8000000, 200000, 'https://developers.openai.com/api/docs/pricing',
|
|
74
|
+
'First observation: the first image models in the catalogue (0057).');
|
|
75
|
+
|
|
76
|
+
-- No long-context tier, unlike the 5.6 rows 0049 wrote: the page prints one rate
|
|
77
|
+
-- per column for these and the models' own pages state no threshold. Batch
|
|
78
|
+
-- pricing is printed beside it at half, and is not recorded for 0049's reason —
|
|
79
|
+
-- Cruise sends standard requests, and a second rate with nothing selecting
|
|
80
|
+
-- between them is a chance to bill the wrong one.
|
|
81
|
+
|
|
82
|
+
-- ── The bound ─────────────────────────────────────────────────────────────────
|
|
83
|
+
--
|
|
84
|
+
-- A reservation must cover what a request could cost before the response says
|
|
85
|
+
-- what it did. For chat that figure is `max_tokens` or the model's published
|
|
86
|
+
-- output cap. For an image it is **the output tokens one image costs at the size
|
|
87
|
+
-- and quality asked for**, times `n`.
|
|
88
|
+
--
|
|
89
|
+
-- **The host does not publish that grid.** Checked 2026-09-04: the pricing page
|
|
90
|
+
-- gives one output rate per model in dollars per million tokens and says *"for
|
|
91
|
+
-- image generation cost estimates, use the calculator in the image generation
|
|
92
|
+
-- guide"*; the guide lists the sizes and the quality tiers and no token counts;
|
|
93
|
+
-- the model pages carry none. There is a calculator and no table.
|
|
94
|
+
--
|
|
95
|
+
-- So the figure is **measured, not fetched** — which is this repository's rule
|
|
96
|
+
-- for capabilities already, arriving at prices' door. A variant is a row here
|
|
97
|
+
-- because a generation at that size and quality was made and its `usage`
|
|
98
|
+
-- read. That has three consequences worth stating before they surprise someone:
|
|
99
|
+
--
|
|
100
|
+
-- Nothing is seeded. This migration writes **no variant rows**, so until
|
|
101
|
+
-- conformance runs there is not one image request Cruise will bound — which
|
|
102
|
+
-- is moot, because `measured_at` is NULL on all three rows and an unmeasured
|
|
103
|
+
-- model is not routable anyway. The order is 0049's: inserting is not
|
|
104
|
+
-- admitting.
|
|
105
|
+
--
|
|
106
|
+
-- A variant nobody has measured is refused rather than guessed, with
|
|
107
|
+
-- `limit_exceeded` naming what *is* measured. Over-reserving refuses a
|
|
108
|
+
-- request that would have fit; guessing low sells an image for less than it
|
|
109
|
+
-- cost, and on a $30/Mtok output rate at 4K the two are not close.
|
|
110
|
+
--
|
|
111
|
+
-- `size: "auto"` and `quality: "auto"` — the host's own defaults, and what an
|
|
112
|
+
-- OpenAI client sends when it names neither — bound at the **dearest measured
|
|
113
|
+
-- variant** for that model. The host picks; we hold enough for its worst
|
|
114
|
+
-- choice and give back the rest at settlement. Refusing `auto` instead would
|
|
115
|
+
-- mean an unmodified OpenAI client cannot call this route at all, which is
|
|
116
|
+
-- the product.
|
|
117
|
+
--
|
|
118
|
+
-- One row per (model, size, quality) rather than a column of the dearest, which
|
|
119
|
+
-- #227 left open. The dearest alone would bound every request at 4K high — an
|
|
120
|
+
-- order of magnitude over a 1024² low — and a cap that refuses affordable
|
|
121
|
+
-- requests is the failure #219 spent a migration undoing. The grid is also what
|
|
122
|
+
-- `/v1/models` publishes as the sizes and tiers a caller may ask for, and a
|
|
123
|
+
-- single number cannot say that.
|
|
124
|
+
|
|
125
|
+
CREATE TABLE model_image_variants (
|
|
126
|
+
model TEXT NOT NULL REFERENCES models(id),
|
|
127
|
+
-- As the caller sends them, and as the host names them: `1024x1024`,
|
|
128
|
+
-- `1536x1024`, `low`, `high`. Never `auto` — that is the absence of a choice,
|
|
129
|
+
-- resolved against these rows rather than stored as one of them.
|
|
130
|
+
size TEXT NOT NULL CHECK (size != 'auto'),
|
|
131
|
+
quality TEXT NOT NULL CHECK (quality != 'auto'),
|
|
132
|
+
-- What one image at this variant cost in image output tokens, from the
|
|
133
|
+
-- response's `usage.output_tokens` at `n = 1`.
|
|
134
|
+
output_tokens INTEGER NOT NULL CHECK (output_tokens > 0),
|
|
135
|
+
measured_at TEXT NOT NULL,
|
|
136
|
+
-- The run that measured it, as `models.measured_by` records one.
|
|
137
|
+
measured_by TEXT,
|
|
138
|
+
-- Where the figure came from. `conformance` is the only writer today; a URL
|
|
139
|
+
-- would mean the host started publishing the grid, which would be better.
|
|
140
|
+
source TEXT NOT NULL DEFAULT 'conformance',
|
|
141
|
+
PRIMARY KEY (model, size, quality)
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
-- The catalogue reads every variant of a model at once, and the bound reads
|
|
145
|
+
-- every variant of every candidate in a chain. Both are covered by the primary
|
|
146
|
+
-- key's leading column, so there is no second index here on purpose.
|
|
147
|
+
|
|
148
|
+
-- ── The transport, which is not measured and is why this says so ──────────────
|
|
149
|
+
--
|
|
150
|
+
-- Cloudflare's unified `/compat` endpoint serves chat completions and nothing
|
|
151
|
+
-- else, so an image request cannot take the path every other request in this
|
|
152
|
+
-- repository takes. The provider path is documented for `chat/completions` and
|
|
153
|
+
-- `responses` and says to replace `api.openai.com/v1` with the gateway prefix,
|
|
154
|
+
-- which implies but does not state that `images/generations` proxies through.
|
|
155
|
+
--
|
|
156
|
+
-- #227 asks for that to be measured before the route relies on it. **It has not
|
|
157
|
+
-- been, and could not be from here.** Probed 2026-09-04 against
|
|
158
|
+
-- `gateway.ai.cloudflare.com/v1/<account>/gw-sensitive-staging/openai/…`: both
|
|
159
|
+
-- gateways authenticate, so every path — `compat/chat/completions`,
|
|
160
|
+
-- `openai/images/generations`, and a deliberately nonsensical one — answers the
|
|
161
|
+
-- same `AiGatewayError 2009 Unauthorized` before any routing decision is
|
|
162
|
+
-- visible. The measurement needs `AI_GATEWAY_TOKEN`, which is a Worker secret
|
|
163
|
+
-- and cannot be read back, so it happens from a deployed Worker or not at all.
|
|
164
|
+
-- The first conformance run against `PROVIDER_KEY_OPENAI` is that run.
|
|
165
|
+
--
|
|
166
|
+
-- What keeps the guess from costing a tenant a request: conformance generates
|
|
167
|
+
-- *through* `/v1/images/generations` with its own key, and a variant row is
|
|
168
|
+
-- written only from a `usage` that came back. Until one exists the limits gate
|
|
169
|
+
-- refuses every request to the model, so no tenant request can reach a
|
|
170
|
+
-- transport that has not already carried a measured generation.
|
|
171
|
+
--
|
|
172
|
+
-- What that costs if the guess is wrong is one row, not a code change:
|
|
173
|
+
-- `src/images.ts` sends a direct request whenever `upstreams.base_url` is set
|
|
174
|
+
-- and a gateway provider-path request otherwise, which is the same `isDirect`
|
|
175
|
+
-- split 0050 built for Meta. Setting `base_url` on the `openai` upstream moves
|
|
176
|
+
-- every image request off the gateway; it also moves chat, which is why that is
|
|
177
|
+
-- a decision for whoever measures rather than a default written here.
|