@bytesbrains/cruise 0.12.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/{budget-FIRNSN67.mjs → budget-IIC4NJUU.mjs} +6 -5
  2. package/dist/{chunk-FGLMW5IY.mjs → chunk-2Z62Z53P.mjs} +2 -2
  3. package/dist/chunk-5AFYJTLS.mjs +62 -0
  4. package/dist/{chunk-WS6NFRIU.mjs → chunk-5LRJO4Z3.mjs} +1 -1
  5. package/dist/chunk-7ZUKADIS.mjs +131 -0
  6. package/dist/{chunk-LALZUKEC.mjs → chunk-A4DIVNYV.mjs} +1 -0
  7. package/dist/chunk-CDBZLSL7.mjs +65 -0
  8. package/dist/chunk-D3K25SBO.mjs +69 -0
  9. package/dist/chunk-NR4K7AUA.mjs +17 -0
  10. package/dist/{chunk-GUZNI3NS.mjs → chunk-PNJVYZ3Y.mjs} +3 -56
  11. package/dist/chunk-TJR2U73N.mjs +12 -0
  12. package/dist/{chunk-THEXUHQC.mjs → chunk-UBM65ZNR.mjs} +1 -0
  13. package/dist/{chunk-JZPUTXM7.mjs → chunk-Y2I46CTD.mjs} +113 -1
  14. package/dist/{compress-3DWG35BY.mjs → compress-QAEEBNVF.mjs} +1 -2
  15. package/dist/{conformance-NPFDYCQL.mjs → conformance-H2DRKMVX.mjs} +4 -5
  16. package/dist/cruise.mjs +132 -32
  17. package/dist/demo-seed-PRVDIWCM.mjs +59 -0
  18. package/dist/doctor-LVMXEX35.mjs +802 -0
  19. package/dist/{fallback-P5KK55M4.mjs → fallback-HRVI5XCK.mjs} +7 -6
  20. package/dist/journey-JXQ3VYCT.mjs +136 -0
  21. package/dist/{keys-FWPQPMTW.mjs → keys-S5QOOVMS.mjs} +3 -4
  22. package/dist/{ledger-XBGWXY6C.mjs → ledger-DWOMA7WD.mjs} +2 -3
  23. package/dist/{member-ZBV4YG2M.mjs → member-R7HUJK45.mjs} +1 -2
  24. package/dist/metrics-LCO4YNPW.mjs +144 -0
  25. package/dist/{report-VRYJ3W5L.mjs → report-CLNJPK6Z.mjs} +2 -3
  26. package/dist/secrets-check-6PWXXJM7.mjs +65 -0
  27. package/dist/{service-key-N2RZ7FMZ.mjs → service-key-E6SGTLWA.mjs} +1 -2
  28. package/dist/{smoke-ZNYX6RO5.mjs → smoke-DFNUPTZ7.mjs} +1 -2
  29. package/dist/staging-seed-647DRUW5.mjs +41 -0
  30. package/dist/{sweep-SZX7OIDK.mjs → sweep-5O32ZU4T.mjs} +2 -3
  31. package/dist/{tenant-4YDGI23Q.mjs → tenant-DRN6BAOI.mjs} +13 -9
  32. package/dist/{tier-4L3SPLFP.mjs → tier-JSY5OU65.mjs} +1 -2
  33. package/package.json +2 -2
  34. package/dist/chunk-7NQHBXVZ.mjs +0 -104
  35. package/dist/chunk-ICJVJSTC.mjs +0 -42
  36. package/dist/chunk-N3YKWKOA.mjs +0 -9
@@ -2,17 +2,19 @@
2
2
  // Built by packages/cli/build.ts.
3
3
  import {
4
4
  DEMO_ENVIRONMENT
5
- } from "./chunk-N3YKWKOA.mjs";
5
+ } from "./chunk-D3K25SBO.mjs";
6
+ import "./chunk-CDBZLSL7.mjs";
6
7
  import {
7
8
  demoChargeStatements
8
9
  } from "./chunk-RT4R2W6H.mjs";
10
+ import "./chunk-NR4K7AUA.mjs";
9
11
  import {
10
12
  formatUsd,
11
13
  readBudget,
12
14
  setBudget,
13
15
  usdToMicros
14
- } from "./chunk-THEXUHQC.mjs";
15
- import "./chunk-LALZUKEC.mjs";
16
+ } from "./chunk-UBM65ZNR.mjs";
17
+ import "./chunk-A4DIVNYV.mjs";
16
18
  import {
17
19
  connect,
18
20
  environmentOf,
@@ -20,8 +22,7 @@ import {
20
22
  parseArgs,
21
23
  requireString,
22
24
  run
23
- } from "./chunk-7NQHBXVZ.mjs";
24
- import "./chunk-JZPUTXM7.mjs";
25
+ } from "./chunk-Y2I46CTD.mjs";
25
26
 
26
27
  // scripts/budget.ts
27
28
  run(async () => {
@@ -6,11 +6,11 @@ import {
6
6
  import {
7
7
  tokenCount,
8
8
  usageFromJson
9
- } from "./chunk-ICJVJSTC.mjs";
9
+ } from "./chunk-7ZUKADIS.mjs";
10
10
  import {
11
11
  checkLimits,
12
12
  requiredCapabilities
13
- } from "./chunk-LALZUKEC.mjs";
13
+ } from "./chunk-A4DIVNYV.mjs";
14
14
 
15
15
  // src/reservation-search.ts
16
16
  function searchInputBound(model, body, promptTokens) {
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ // Built by packages/cli/build.ts.
3
+ import {
4
+ DEMO_ENVIRONMENT
5
+ } from "./chunk-D3K25SBO.mjs";
6
+
7
+ // src/tenants-prepaid.ts
8
+ var STARTING_WALLET_SOFT_MICROS = 1e6;
9
+ function prepaidOn(environment) {
10
+ return environment !== DEMO_ENVIRONMENT;
11
+ }
12
+ function walletInsert(db, tenantId, actor) {
13
+ return db.prepare(
14
+ `INSERT INTO budgets (scope, subject, period, soft_micros, hard_micros, updated_by)
15
+ VALUES ('tenant', ?1, 'lifetime', ?2, (SELECT COALESCE(sum(micros), 0) FROM credits WHERE tenant_id = ?1), ?3)`
16
+ ).bind(tenantId, STARTING_WALLET_SOFT_MICROS, actor);
17
+ }
18
+ var SIGNUP_MADE = `EXISTS (SELECT 1 FROM audit_events a
19
+ WHERE a.action = 'tenant.provision' AND a.subject = t.id
20
+ AND substr(a.actor, 1, 12) = 'key:cru_svc_')`;
21
+ async function unprepaidSignups(db) {
22
+ const { results } = await db.prepare(
23
+ `SELECT t.id, t.billable
24
+ FROM tenants t
25
+ WHERE ${SIGNUP_MADE}
26
+ AND NOT EXISTS (SELECT 1 FROM budgets b WHERE b.scope = 'tenant' AND b.subject = t.id)
27
+ ORDER BY t.created_at, t.id`
28
+ ).all();
29
+ return results.map((row) => ({ id: row.id, billable: row.billable === 1 }));
30
+ }
31
+ async function prepaySignups(db, environment, actor) {
32
+ if (!prepaidOn(environment)) {
33
+ throw new Error(
34
+ "Signup on the demo is not prepaid: every price there is zero, and a zero wallet would refuse every request. Nothing was changed."
35
+ );
36
+ }
37
+ const pending = await unprepaidSignups(db);
38
+ for (const tenant of pending) {
39
+ await db.batch([
40
+ walletInsert(db, tenant.id, actor),
41
+ ...tenant.billable ? [] : [db.prepare(`UPDATE tenants SET billable = 1 WHERE id = ?`).bind(tenant.id)],
42
+ db.prepare(
43
+ `INSERT INTO audit_events (id, actor, action, subject, detail, tenant_id)
44
+ VALUES (?, ?, 'tenant.prepay', ?, ?, ?)`
45
+ ).bind(
46
+ crypto.randomUUID(),
47
+ actor,
48
+ tenant.id,
49
+ JSON.stringify({ wallet_written: true, billable_set: !tenant.billable }),
50
+ tenant.id
51
+ )
52
+ ]);
53
+ }
54
+ return pending;
55
+ }
56
+
57
+ export {
58
+ prepaidOn,
59
+ walletInsert,
60
+ unprepaidSignups,
61
+ prepaySignups
62
+ };
@@ -2,7 +2,7 @@
2
2
  // Built by packages/cli/build.ts.
3
3
  import {
4
4
  MODALITIES
5
- } from "./chunk-LALZUKEC.mjs";
5
+ } from "./chunk-A4DIVNYV.mjs";
6
6
 
7
7
  // src/conformance-plan.ts
8
8
  function shardOf(items, index, total) {
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env node
2
+ // Built by packages/cli/build.ts.
3
+
4
+ // src/pricing-usage.ts
5
+ var IMPLAUSIBLE_TOKENS = 1e8;
6
+ function tokenCount(value) {
7
+ if (typeof value !== "number") return null;
8
+ if (!Number.isSafeInteger(value)) return null;
9
+ if (value < 0 || value > IMPLAUSIBLE_TOKENS) return null;
10
+ return value;
11
+ }
12
+ function usageFromJson(body) {
13
+ const usage = body?.usage;
14
+ if (!usage) return null;
15
+ const prompt = tokenCount(usage.prompt_tokens);
16
+ const completion = tokenCount(usage.completion_tokens);
17
+ if (prompt === null || completion === null) {
18
+ console.warn(
19
+ `Refusing implausible usage from upstream: prompt=${String(usage.prompt_tokens)} completion=${String(usage.completion_tokens)}. Recorded as no_usage and charged the reservation.`
20
+ );
21
+ return null;
22
+ }
23
+ const reasoning = usage.completion_tokens_details?.reasoning_tokens;
24
+ const cached = usage.prompt_tokens_details?.cached_tokens ?? usage.prompt_cache_hit_tokens;
25
+ const searches = tokenCount(usage.server_tool_use?.web_search_requests ?? usage.num_search_queries);
26
+ const contextSize = typeof usage.search_context_size === "string" ? usage.search_context_size : void 0;
27
+ return {
28
+ prompt_tokens: prompt,
29
+ completion_tokens: completion,
30
+ // These two only ever refine a figure already accepted, so an implausible
31
+ // value drops to zero rather than discarding the whole reading.
32
+ reasoning_tokens: tokenCount(reasoning) ?? 0,
33
+ cached_input_tokens: tokenCount(cached) ?? 0,
34
+ ...searches === null ? {} : { web_search_requests: searches },
35
+ ...contextSize === void 0 ? {} : { search_context_size: contextSize }
36
+ };
37
+ }
38
+
39
+ // src/peak-calendar.ts
40
+ var SQL_OPERAND = /^(?:\?\d+|[a-z_]+(?:\.[a-z_]+)?)$/;
41
+ function peakExemptColumn(model, day) {
42
+ for (const operand of [model, day]) {
43
+ if (!SQL_OPERAND.test(operand)) throw new Error(`peakExemptColumn: not a column or bind parameter: ${operand}`);
44
+ }
45
+ return `EXISTS (SELECT 1 FROM peak_exempt_days e JOIN models m ON m.upstream_id = e.upstream_id
46
+ WHERE m.id = ${model} AND e.day = ${day}) AS peak_exempt`;
47
+ }
48
+
49
+ // src/pricing.ts
50
+ var TOKENS_PER_MTOK = 1e6;
51
+ var DAYS = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
52
+ var WINDOW = /^(?:([a-z]{3})-([a-z]{3})@)?(\d{2}):(\d{2})-(\d{2}):(\d{2})$/;
53
+ function windowContains(windows, at) {
54
+ const minute = at.getUTCHours() * 60 + at.getUTCMinutes();
55
+ const today = at.getUTCDay();
56
+ for (const window of windows.split(",")) {
57
+ const match = WINDOW.exec(window.trim().toLowerCase());
58
+ if (match === null) continue;
59
+ const [, fromDay, toDay] = match;
60
+ if (fromDay !== void 0 && toDay !== void 0) {
61
+ const from = DAYS.indexOf(fromDay);
62
+ const to = DAYS.indexOf(toDay);
63
+ if (from < 0 || to < 0) continue;
64
+ const onDay = from <= to ? today >= from && today <= to : today >= from || today <= to;
65
+ if (!onDay) continue;
66
+ }
67
+ const start = Number(match[3]) * 60 + Number(match[4]);
68
+ const end = Number(match[5]) * 60 + Number(match[6]);
69
+ const inside = start <= end ? minute >= start && minute < end : minute >= start || minute < end;
70
+ if (inside) return true;
71
+ }
72
+ return false;
73
+ }
74
+ function resolvePrice(rows, promptTokens, at) {
75
+ const tiers = [...new Set(rows.map((r) => r.prompt_tokens_from))].filter((from) => from <= promptTokens).sort((a, b) => b - a);
76
+ for (const tier of tiers) {
77
+ const candidates = rows.filter((r) => r.prompt_tokens_from === tier);
78
+ const peak = candidates.find((r) => r.peak_utc !== "" && !r.peak_exempt && windowContains(r.peak_utc, at));
79
+ if (peak !== void 0) return peak;
80
+ const base = candidates.find((r) => r.peak_utc === "");
81
+ if (base !== void 0) return base;
82
+ }
83
+ return null;
84
+ }
85
+ async function priceRowsInForce(db, model, at) {
86
+ const day = at.toISOString().slice(0, 10);
87
+ const { results } = await db.prepare(
88
+ `SELECT input_micros_per_mtok, output_micros_per_mtok, cached_input_micros_per_mtok,
89
+ prompt_tokens_from, peak_utc, ${peakExemptColumn("?1", "?2")}
90
+ FROM model_prices
91
+ WHERE model = ?1
92
+ AND effective_from = (SELECT max(effective_from) FROM model_prices
93
+ WHERE model = ?1 AND effective_from <= ?2)`
94
+ ).bind(model, day).all();
95
+ return results;
96
+ }
97
+ async function hitBasisPointsInForce(db, at = /* @__PURE__ */ new Date()) {
98
+ return (await hitFractionInForce(db, at))?.basis_points ?? null;
99
+ }
100
+ async function hitFractionInForce(db, at = /* @__PURE__ */ new Date()) {
101
+ const day = at.toISOString().slice(0, 10);
102
+ return db.prepare(
103
+ `SELECT hit_basis_points AS basis_points, effective_from FROM cache_billing
104
+ WHERE effective_from <= ?1 ORDER BY effective_from DESC LIMIT 1`
105
+ ).bind(day).first();
106
+ }
107
+ function hitCharge(listMicros, basisPoints) {
108
+ if (basisPoints === null) return listMicros;
109
+ return Math.ceil(listMicros * basisPoints / 1e4);
110
+ }
111
+ async function priceUsage(db, model, usage, at = /* @__PURE__ */ new Date()) {
112
+ return priceFromRows(await priceRowsInForce(db, model, at), usage, at);
113
+ }
114
+ function priceFromRows(rows, usage, at) {
115
+ const price = resolvePrice(rows, usage.prompt_tokens, at);
116
+ if (price === null) return { micros: 0, priced: false };
117
+ const cached = Math.min(usage.cached_input_tokens ?? 0, usage.prompt_tokens);
118
+ const fresh = usage.prompt_tokens - cached;
119
+ const cachedRate = price.cached_input_micros_per_mtok ?? price.input_micros_per_mtok;
120
+ const input = fresh * price.input_micros_per_mtok + cached * cachedRate;
121
+ const output = usage.completion_tokens * price.output_micros_per_mtok;
122
+ return { micros: Math.round((input + output) / TOKENS_PER_MTOK), priced: true };
123
+ }
124
+
125
+ export {
126
+ tokenCount,
127
+ usageFromJson,
128
+ hitBasisPointsInForce,
129
+ hitCharge,
130
+ priceUsage
131
+ };
@@ -133,5 +133,6 @@ export {
133
133
  requiredCapabilities,
134
134
  MODALITIES,
135
135
  lookupModel,
136
+ FRESHLY_MEASURED_SQL,
136
137
  checkLimits
137
138
  };
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ // Built by packages/cli/build.ts.
3
+ import {
4
+ FRESHLY_MEASURED_SQL
5
+ } from "./chunk-A4DIVNYV.mjs";
6
+
7
+ // src/catalogue-mirror.ts
8
+ async function productionRoutable(production) {
9
+ const { results } = await production.prepare(
10
+ `SELECT id, modality, tools, parallel_tool_calls, tool_choice_forced, web_search, streaming,
11
+ usage_reported, vision, json_schema, measured_at
12
+ FROM models
13
+ WHERE enabled = 1 AND ${FRESHLY_MEASURED_SQL}
14
+ ORDER BY id`
15
+ ).all();
16
+ return results;
17
+ }
18
+ function requireSource(measured, source) {
19
+ if (measured.length === 0) {
20
+ throw new Error(`${source} has no routable model \u2014 refusing to copy that over this catalogue. Check ${source} first.`);
21
+ }
22
+ return measured;
23
+ }
24
+ function mirrorStatements(db, measured, measuredBy) {
25
+ return [
26
+ db.prepare(`UPDATE models SET measured_at = NULL, measured_by = NULL, measurement_source = NULL`),
27
+ ...measured.map(
28
+ (m) => db.prepare(
29
+ `UPDATE models
30
+ SET tools = ?2, parallel_tool_calls = ?3, streaming = ?4, usage_reported = ?5,
31
+ vision = ?6, json_schema = ?7, tool_choice_forced = ?8, web_search = ?9,
32
+ measured_at = ?10, measured_by = ?11, measurement_source = 'bootstrap'
33
+ WHERE id = ?1 AND enabled = 1`
34
+ ).bind(
35
+ m.id,
36
+ m.tools,
37
+ m.parallel_tool_calls,
38
+ m.streaming,
39
+ m.usage_reported,
40
+ m.vision,
41
+ m.json_schema,
42
+ m.tool_choice_forced,
43
+ m.web_search,
44
+ m.measured_at,
45
+ measuredBy
46
+ )
47
+ )
48
+ ];
49
+ }
50
+ var STAGING_ENVIRONMENT = "staging";
51
+ async function mirrorStaging(staging, measured) {
52
+ const results = await staging.batch(mirrorStatements(staging, measured, "mirror:production"));
53
+ const routable = [];
54
+ const absent = [];
55
+ measured.forEach((m, i) => ((results[i + 1]?.meta?.changes ?? 0) > 0 ? routable : absent).push(m.id));
56
+ return { routable, absent };
57
+ }
58
+
59
+ export {
60
+ productionRoutable,
61
+ requireSource,
62
+ mirrorStatements,
63
+ STAGING_ENVIRONMENT,
64
+ mirrorStaging
65
+ };
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ // Built by packages/cli/build.ts.
3
+ import {
4
+ mirrorStatements
5
+ } from "./chunk-CDBZLSL7.mjs";
6
+
7
+ // src/demo-catalogue.ts
8
+ var DEMO_EMULATOR_BASE = "https://no-provider.bytesbrains.net/direct";
9
+ var DEMO_SERVABLE_MODALITIES = ["chat"];
10
+ var DEMO_ENVIRONMENT = "demo";
11
+ async function seedDemoCatalogue(demo, measured, emulatorBase) {
12
+ const servable = new Set(DEMO_SERVABLE_MODALITIES);
13
+ const routable = measured.filter((m) => servable.has(m.modality));
14
+ const unservable = measured.filter((m) => !servable.has(m.modality)).map((m) => m.id);
15
+ const statements = [
16
+ // Every upstream, including ones no enabled model points at: a row left
17
+ // addressing the real gateway is a row a future migration can make routable
18
+ // without anybody re-running this.
19
+ demo.prepare(`UPDATE upstreams SET base_url = ?1`).bind(emulatorBase),
20
+ // Zero, not absent. `doctor.ts:102` fails a routable model with no price
21
+ // row — reserving it at a flat rate is "a way to buy an arbitrarily
22
+ // expensive request for five cents" — so the demo needs prices that exist
23
+ // and cost nothing, which is a different thing from no prices.
24
+ //
25
+ // `price_observations` and not `model_prices`: 0048 turned the latter into a
26
+ // view over the former — the observation log, so a correction is a row
27
+ // rather than an overwrite — and D1 refuses a write to a view outright.
28
+ // Every observation is zeroed, retracted ones included, because the view
29
+ // picks the latest surviving row per key and an untouched retraction that
30
+ // is later un-retracted would surface a real price on the demo.
31
+ demo.prepare(
32
+ `UPDATE price_observations
33
+ SET input_micros_per_mtok = 0, output_micros_per_mtok = 0,
34
+ cached_input_micros_per_mtok = 0, source = 'demo-seed: the demo bills nothing'`
35
+ ),
36
+ // The non-token half (0058): a request, a search, a character, an image.
37
+ // `micros >= 0` is a CHECK and zero is a real rate there by design. This one
38
+ // *is* the table — `model_components` is its view — so it is written direct.
39
+ demo.prepare(`UPDATE price_components SET micros = 0`),
40
+ // The floor, off. Not `setBudget`, which does not touch this column.
41
+ demo.prepare(`UPDATE budgets SET reserve_micros = 0`),
42
+ // Clear, then stamp each model with production's flags and date
43
+ // (`catalogue-mirror.ts`). Last, because the per-model results are read
44
+ // back positionally after the clear.
45
+ ...mirrorStatements(demo, routable, "demo-seed")
46
+ ];
47
+ const FIXED = statements.length - routable.length;
48
+ const results = await demo.batch(statements);
49
+ const changes = (at) => results[at]?.meta?.changes ?? 0;
50
+ const applied = [];
51
+ const absent = [];
52
+ routable.forEach((m, i) => (changes(FIXED + i) > 0 ? applied : absent).push(m.id));
53
+ return {
54
+ routable: applied,
55
+ absent,
56
+ unservable,
57
+ upstreams: changes(0),
58
+ pricedRows: changes(1),
59
+ componentRows: changes(2),
60
+ budgets: changes(3)
61
+ };
62
+ }
63
+
64
+ export {
65
+ DEMO_EMULATOR_BASE,
66
+ DEMO_SERVABLE_MODALITIES,
67
+ DEMO_ENVIRONMENT,
68
+ seedDemoCatalogue
69
+ };
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ // Built by packages/cli/build.ts.
3
+
4
+ // src/config.ts
5
+ var PROVIDER_KEY_PREFIX = "PROVIDER_KEY_";
6
+ var CREDENTIAL_FREE_ENVIRONMENTS = ["demo"];
7
+ var REQUIRED_SECRETS = [
8
+ "AI_GATEWAY_TOKEN",
9
+ "ACCOUNT_ID_SALT",
10
+ "FIREBASE_PROJECT_ID"
11
+ ];
12
+
13
+ export {
14
+ PROVIDER_KEY_PREFIX,
15
+ CREDENTIAL_FREE_ENVIRONMENTS,
16
+ REQUIRED_SECRETS
17
+ };
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  // Built by packages/cli/build.ts.
3
3
  import {
4
- DEMO_ENVIRONMENT
5
- } from "./chunk-N3YKWKOA.mjs";
4
+ walletInsert
5
+ } from "./chunk-5AFYJTLS.mjs";
6
6
  import {
7
7
  assertUid
8
8
  } from "./chunk-2NVDAK5J.mjs";
@@ -10,57 +10,7 @@ import {
10
10
  CONTRIBUTOR_LANE_PREFIX,
11
11
  LANE_PREFIX,
12
12
  SLUG
13
- } from "./chunk-FGLMW5IY.mjs";
14
-
15
- // src/tenants-prepaid.ts
16
- var STARTING_WALLET_SOFT_MICROS = 1e6;
17
- function prepaidOn(environment) {
18
- return environment !== DEMO_ENVIRONMENT;
19
- }
20
- function walletInsert(db, tenantId, actor) {
21
- return db.prepare(
22
- `INSERT INTO budgets (scope, subject, period, soft_micros, hard_micros, updated_by)
23
- VALUES ('tenant', ?1, 'lifetime', ?2, (SELECT COALESCE(sum(micros), 0) FROM credits WHERE tenant_id = ?1), ?3)`
24
- ).bind(tenantId, STARTING_WALLET_SOFT_MICROS, actor);
25
- }
26
- var SIGNUP_MADE = `EXISTS (SELECT 1 FROM audit_events a
27
- WHERE a.action = 'tenant.provision' AND a.subject = t.id
28
- AND substr(a.actor, 1, 12) = 'key:cru_svc_')`;
29
- async function unprepaidSignups(db) {
30
- const { results } = await db.prepare(
31
- `SELECT t.id, t.billable
32
- FROM tenants t
33
- WHERE ${SIGNUP_MADE}
34
- AND NOT EXISTS (SELECT 1 FROM budgets b WHERE b.scope = 'tenant' AND b.subject = t.id)
35
- ORDER BY t.created_at, t.id`
36
- ).all();
37
- return results.map((row) => ({ id: row.id, billable: row.billable === 1 }));
38
- }
39
- async function prepaySignups(db, environment, actor) {
40
- if (!prepaidOn(environment)) {
41
- throw new Error(
42
- "Signup on the demo is not prepaid: every price there is zero, and a zero wallet would refuse every request. Nothing was changed."
43
- );
44
- }
45
- const pending = await unprepaidSignups(db);
46
- for (const tenant of pending) {
47
- await db.batch([
48
- walletInsert(db, tenant.id, actor),
49
- ...tenant.billable ? [] : [db.prepare(`UPDATE tenants SET billable = 1 WHERE id = ?`).bind(tenant.id)],
50
- db.prepare(
51
- `INSERT INTO audit_events (id, actor, action, subject, detail, tenant_id)
52
- VALUES (?, ?, 'tenant.prepay', ?, ?, ?)`
53
- ).bind(
54
- crypto.randomUUID(),
55
- actor,
56
- tenant.id,
57
- JSON.stringify({ wallet_written: true, billable_set: !tenant.billable }),
58
- tenant.id
59
- )
60
- ]);
61
- }
62
- return pending;
63
- }
13
+ } from "./chunk-2Z62Z53P.mjs";
64
14
 
65
15
  // src/tenants-validate.ts
66
16
  var GLOBAL_NAMESPACES = [LANE_PREFIX, CONTRIBUTOR_LANE_PREFIX].map((prefix) => prefix.slice(0, -1));
@@ -258,9 +208,6 @@ async function listTenants(db) {
258
208
  }
259
209
 
260
210
  export {
261
- prepaidOn,
262
- unprepaidSignups,
263
- prepaySignups,
264
211
  assertGateway,
265
212
  provisionTenant,
266
213
  listTenants
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ // Built by packages/cli/build.ts.
3
+
4
+ // src/upstream-health.ts
5
+ var HEALTH_BURN_DAYS = 7;
6
+ var BILLING_GOOGLE = "PROVIDER_BILLING_KEY_GOOGLE_AI_STUDIO";
7
+ var HEALTH_BILLING_SECRETS = [BILLING_GOOGLE];
8
+
9
+ export {
10
+ HEALTH_BURN_DAYS,
11
+ HEALTH_BILLING_SECRETS
12
+ };
@@ -91,6 +91,7 @@ async function setBudget(db, projectId, limits, actor, also = []) {
91
91
  }
92
92
 
93
93
  export {
94
+ MICROS_PER_USD,
94
95
  usdToMicros,
95
96
  formatUsd,
96
97
  readBudget,
@@ -147,6 +147,39 @@ var HttpStatement = class _HttpStatement {
147
147
  }
148
148
  };
149
149
 
150
+ // scripts/wrangler-config.ts
151
+ function stripJsonc(raw) {
152
+ return JSON.parse(raw.replace(/(^|[^:])\/\/.*$/gm, "$1").replace(/\/\*[\s\S]*?\*\//g, ""));
153
+ }
154
+ function resolveScope(config, environment) {
155
+ const root = config;
156
+ const scope = environment === "top" ? root : root.env?.[environment];
157
+ if (!scope) throw new Error(`No such environment in wrangler.jsonc: ${environment}`);
158
+ return scope;
159
+ }
160
+ function resolveWorker(config, environment) {
161
+ const root = config;
162
+ const scope = resolveScope(config, environment);
163
+ const scriptName = scope.name ?? root.name;
164
+ if (!scriptName) throw new Error(`No worker name for environment: ${environment}`);
165
+ if (!root.account_id) throw new Error("No account_id in wrangler.jsonc");
166
+ return { accountId: root.account_id, scriptName };
167
+ }
168
+ function resolveDataset(config, environment) {
169
+ const dataset = resolveScope(config, environment).analytics_engine_datasets?.[0]?.dataset;
170
+ if (!dataset) throw new Error(`No analytics_engine_datasets entry for environment: ${environment}`);
171
+ return dataset;
172
+ }
173
+ function resolveDatabaseId(config, environment) {
174
+ const databaseId = resolveScope(config, environment).d1_databases?.[0]?.database_id;
175
+ if (!databaseId) throw new Error(`No d1_databases entry for environment: ${environment}`);
176
+ return databaseId;
177
+ }
178
+
179
+ // scripts/cli-support.ts
180
+ import { readFileSync as readFileSync2 } from "node:fs";
181
+ import path2 from "node:path";
182
+
150
183
  // scripts/d1-local.ts
151
184
  import { readFileSync } from "node:fs";
152
185
  import path from "node:path";
@@ -230,7 +263,86 @@ var LocalStatement = class _LocalStatement {
230
263
  }
231
264
  };
232
265
 
266
+ // scripts/cli-support.ts
267
+ function parseArgs(argv) {
268
+ const args = {};
269
+ for (let i = 0; i < argv.length; i++) {
270
+ const token = argv[i];
271
+ if (!token?.startsWith("--")) continue;
272
+ const name = token.slice(2);
273
+ const next = argv[i + 1];
274
+ if (next === void 0 || next.startsWith("--")) args[name] = true;
275
+ else {
276
+ args[name] = next;
277
+ i++;
278
+ }
279
+ }
280
+ return args;
281
+ }
282
+ function requireString(args, name) {
283
+ const value = args[name];
284
+ if (typeof value !== "string" || value.length === 0) throw new Error(`Missing required --${name}`);
285
+ return value;
286
+ }
287
+ function optionalNumber(args, name) {
288
+ const value = args[name];
289
+ if (typeof value !== "string") return null;
290
+ const parsed = Number(value);
291
+ if (!Number.isFinite(parsed)) throw new Error(`--${name} must be a number, got ${value}`);
292
+ return parsed;
293
+ }
294
+ function loadWranglerConfig() {
295
+ return stripJsonc(readFileSync2(wranglerConfigPath(), "utf8"));
296
+ }
297
+ function wranglerConfigPath() {
298
+ return process.env.CRUISE_WRANGLER_CONFIG || path2.join(import.meta.dirname, "..", "wrangler.jsonc");
299
+ }
300
+ function requireToken() {
301
+ const token = process.env.CLOUDFLARE_API_TOKEN;
302
+ if (!token) throw new Error("CLOUDFLARE_API_TOKEN is not set");
303
+ return token;
304
+ }
305
+ function workerScript(environment) {
306
+ const token = requireToken();
307
+ return { ...resolveWorker(loadWranglerConfig(), environment), token };
308
+ }
309
+ function connect(environment) {
310
+ if (environment === LOCAL_ENVIRONMENT) return new LocalD1();
311
+ const token = requireToken();
312
+ if (environment === CLOUDFLARE_ENVIRONMENT) {
313
+ const accountId2 = process.env.CRUISE_D1_ACCOUNT_ID;
314
+ const databaseId = process.env.CRUISE_D1_DATABASE_ID;
315
+ if (!accountId2 || !databaseId) throw new Error("--env cloudflare needs CRUISE_D1_ACCOUNT_ID and CRUISE_D1_DATABASE_ID");
316
+ return new HttpD1(accountId2, databaseId, token);
317
+ }
318
+ const config = loadWranglerConfig();
319
+ const { accountId } = resolveWorker(config, environment);
320
+ return new HttpD1(accountId, resolveDatabaseId(config, environment), token);
321
+ }
322
+ var LOCAL_ENVIRONMENT = "local";
323
+ var CLOUDFLARE_ENVIRONMENT = "cloudflare";
324
+ function environmentOf(args) {
325
+ return typeof args.env === "string" ? args.env : "staging";
326
+ }
327
+ function run(main) {
328
+ main().catch((error) => {
329
+ console.error(error instanceof Error ? error.message : error);
330
+ process.exitCode = 1;
331
+ });
332
+ }
333
+
233
334
  export {
234
335
  HttpD1,
235
- LocalD1
336
+ LocalD1,
337
+ resolveScope,
338
+ resolveDataset,
339
+ parseArgs,
340
+ requireString,
341
+ optionalNumber,
342
+ loadWranglerConfig,
343
+ wranglerConfigPath,
344
+ workerScript,
345
+ connect,
346
+ environmentOf,
347
+ run
236
348
  };
@@ -8,8 +8,7 @@ import {
8
8
  parseArgs,
9
9
  requireString,
10
10
  run
11
- } from "./chunk-7NQHBXVZ.mjs";
12
- import "./chunk-JZPUTXM7.mjs";
11
+ } from "./chunk-Y2I46CTD.mjs";
13
12
 
14
13
  // src/compress-setting.ts
15
14
  async function setProjectCompression(db, projectId, enabled, actor) {
@@ -4,11 +4,11 @@ import {
4
4
  listFlag,
5
5
  planShard,
6
6
  selectModels
7
- } from "./chunk-WS6NFRIU.mjs";
7
+ } from "./chunk-5LRJO4Z3.mjs";
8
8
  import {
9
9
  usageFromJson
10
- } from "./chunk-ICJVJSTC.mjs";
11
- import "./chunk-LALZUKEC.mjs";
10
+ } from "./chunk-7ZUKADIS.mjs";
11
+ import "./chunk-A4DIVNYV.mjs";
12
12
  import {
13
13
  parseKeyPrefix
14
14
  } from "./chunk-5TVD3ZAI.mjs";
@@ -18,8 +18,7 @@ import {
18
18
  parseArgs,
19
19
  requireString,
20
20
  run
21
- } from "./chunk-7NQHBXVZ.mjs";
22
- import "./chunk-JZPUTXM7.mjs";
21
+ } from "./chunk-Y2I46CTD.mjs";
23
22
 
24
23
  // src/conformance-tools.ts
25
24
  var TOOL_NAME = "get_weather";