@bytesbrains/cruise 0.0.0-stage → 0.12.1

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 (41) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +59 -2
  3. package/dist/budget-FIRNSN67.mjs +132 -0
  4. package/dist/chunk-2FB2T3UI.mjs +122 -0
  5. package/dist/chunk-2NVDAK5J.mjs +76 -0
  6. package/dist/chunk-42ERHAJD.mjs +79 -0
  7. package/dist/chunk-4447IUTF.mjs +7 -0
  8. package/dist/chunk-5TVD3ZAI.mjs +69 -0
  9. package/dist/chunk-7NQHBXVZ.mjs +104 -0
  10. package/dist/chunk-BNP63KB2.mjs +42 -0
  11. package/dist/chunk-FGLMW5IY.mjs +575 -0
  12. package/dist/chunk-GUZNI3NS.mjs +267 -0
  13. package/dist/chunk-HQDQTXRX.mjs +57 -0
  14. package/dist/chunk-ICJVJSTC.mjs +42 -0
  15. package/dist/chunk-JZPUTXM7.mjs +236 -0
  16. package/dist/chunk-LALZUKEC.mjs +137 -0
  17. package/dist/chunk-N3YKWKOA.mjs +9 -0
  18. package/dist/chunk-O7TNRKSA.mjs +2 -0
  19. package/dist/chunk-RT4R2W6H.mjs +21 -0
  20. package/dist/chunk-SYTYOTO4.mjs +18 -0
  21. package/dist/chunk-T3ICD43U.mjs +15 -0
  22. package/dist/chunk-THEXUHQC.mjs +98 -0
  23. package/dist/chunk-UNXM6FSM.mjs +83 -0
  24. package/dist/chunk-WS6NFRIU.mjs +66 -0
  25. package/dist/chunk-XOH3XKM3.mjs +126 -0
  26. package/dist/compress-3DWG35BY.mjs +49 -0
  27. package/dist/conformance-NPFDYCQL.mjs +922 -0
  28. package/dist/cruise.mjs +693 -0
  29. package/dist/fallback-P5KK55M4.mjs +139 -0
  30. package/dist/keys-FWPQPMTW.mjs +127 -0
  31. package/dist/ledger-XBGWXY6C.mjs +97 -0
  32. package/dist/member-ZBV4YG2M.mjs +74 -0
  33. package/dist/report-VRYJ3W5L.mjs +414 -0
  34. package/dist/service-key-N2RZ7FMZ.mjs +59 -0
  35. package/dist/smoke-ZNYX6RO5.mjs +105 -0
  36. package/dist/sweep-SZX7OIDK.mjs +33 -0
  37. package/dist/tenant-4YDGI23Q.mjs +122 -0
  38. package/dist/tier-4L3SPLFP.mjs +116 -0
  39. package/package.json +38 -4
  40. package/templates/docker-compose.yml +66 -0
  41. package/templates/env.example +44 -0
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env node
2
+ // Built by packages/cli/build.ts.
3
+
4
+ // src/d1-util.ts
5
+ var D1_TRANSIENT_PATTERN = /network connection lost/i;
6
+ var RETRY_ATTEMPTS = 2;
7
+ var RETRY_DELAY_MS = 50;
8
+ async function withD1Retry(fn) {
9
+ let lastError;
10
+ for (let attempt = 0; attempt <= RETRY_ATTEMPTS; attempt++) {
11
+ try {
12
+ return await fn();
13
+ } catch (err) {
14
+ const message = err instanceof Error ? err.message : String(err);
15
+ if (!D1_TRANSIENT_PATTERN.test(message)) throw err;
16
+ lastError = err;
17
+ if (attempt < RETRY_ATTEMPTS) {
18
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
19
+ }
20
+ }
21
+ }
22
+ throw lastError;
23
+ }
24
+
25
+ // src/capabilities.ts
26
+ function requiredCapabilities(body) {
27
+ const needed = [];
28
+ const hasTools = Array.isArray(body.tools) && body.tools.length > 0;
29
+ if (hasTools) needed.push("tools");
30
+ if (body.parallel_tool_calls === true) needed.push("parallel_tool_calls");
31
+ if (hasTools && forcesToolChoice(body.tool_choice)) needed.push("tool_choice_forced");
32
+ if (asksToSearch(body)) needed.push("web_search");
33
+ if (body.stream === true) needed.push("streaming");
34
+ const format = body.response_format;
35
+ if (format?.type === "json_schema") needed.push("json_schema");
36
+ if (Array.isArray(body.messages)) {
37
+ for (const message of body.messages) {
38
+ if (!Array.isArray(message?.content)) continue;
39
+ for (const part of message.content) {
40
+ if (part?.type === "image_url" || part?.type === "input_image") {
41
+ needed.push("vision");
42
+ break;
43
+ }
44
+ }
45
+ if (needed.includes("vision")) break;
46
+ }
47
+ }
48
+ return needed;
49
+ }
50
+ function forcesToolChoice(choice) {
51
+ if (choice === "required") return true;
52
+ return typeof choice === "object" && choice !== null && choice.type === "function";
53
+ }
54
+ function asksToSearch(body) {
55
+ if (Array.isArray(body.tools)) {
56
+ for (const tool of body.tools) {
57
+ if (typeof tool?.type === "string" && tool.type.startsWith("web_search")) return true;
58
+ }
59
+ }
60
+ return body.web_search_options !== void 0 && body.web_search_options !== null;
61
+ }
62
+
63
+ // src/routing.ts
64
+ var MODALITIES = ["chat", "image", "speech", "embedding"];
65
+ var flag = (value) => value === null ? null : value === 1;
66
+ function toRoutedModel(row) {
67
+ return {
68
+ id: row.id,
69
+ upstreamId: row.upstream_id,
70
+ upstreamModel: row.upstream_model,
71
+ upstreamKind: row.upstream_kind,
72
+ upstreamBaseUrl: row.upstream_base_url,
73
+ upstreamPathPrefix: row.upstream_path_prefix,
74
+ enabled: row.enabled === 1,
75
+ modality: row.modality,
76
+ pricedBy: row.priced_by,
77
+ tools: flag(row.tools),
78
+ parallelToolCalls: flag(row.parallel_tool_calls),
79
+ streaming: flag(row.streaming),
80
+ usageReported: flag(row.usage_reported),
81
+ vision: flag(row.vision),
82
+ jsonSchema: flag(row.json_schema),
83
+ toolChoiceForced: flag(row.tool_choice_forced),
84
+ webSearch: flag(row.web_search),
85
+ maxContext: row.max_context,
86
+ maxOutput: row.max_output,
87
+ maxInputChars: row.max_input_chars,
88
+ dimensions: row.dimensions,
89
+ defaultOutput: row.default_output,
90
+ dataRetention: row.data_retention,
91
+ measuredAt: row.measured_at,
92
+ measurementNote: row.measurement_note ?? null,
93
+ measurementSource: row.measurement_source,
94
+ createdAt: row.created_at
95
+ };
96
+ }
97
+ var SELECT_MODEL = `SELECT m.id, m.upstream_id, m.upstream_model, m.enabled, m.modality, m.priced_by,
98
+ m.tools, m.parallel_tool_calls, m.streaming, m.usage_reported, m.vision, m.json_schema, m.tool_choice_forced, m.web_search,
99
+ m.max_context, m.max_output, m.max_input_chars, m.dimensions,
100
+ COALESCE(m.default_output, u.default_output) AS default_output,
101
+ m.data_retention, m.measured_at,
102
+ m.measurement_source, m.measurement_note, m.created_at, u.kind AS upstream_kind,
103
+ u.base_url AS upstream_base_url, u.provider_path_prefix AS upstream_path_prefix
104
+ FROM models m
105
+ JOIN upstreams u ON u.id = m.upstream_id
106
+ WHERE u.enabled = 1`;
107
+ async function lookupModel(db, id) {
108
+ const row = await withD1Retry(() => db.prepare(`${SELECT_MODEL} AND m.id = ?`).bind(id).first());
109
+ return row === null ? null : toRoutedModel(row);
110
+ }
111
+ var MEASUREMENT_MAX_AGE_DAYS = 30;
112
+ var FRESHLY_MEASURED_SQL = `measured_at IS NOT NULL
113
+ AND julianday('now') - julianday(measured_at) <= ${MEASUREMENT_MAX_AGE_DAYS}`;
114
+ function checkLimits(model, body) {
115
+ const requested = body.max_tokens ?? body.max_completion_tokens;
116
+ if (typeof requested !== "number") return { ok: true };
117
+ if (model.maxOutput !== null && requested > model.maxOutput) {
118
+ return {
119
+ ok: false,
120
+ message: `Model '${model.id}' supports at most ${model.maxOutput} output tokens, and ${requested} were requested.`
121
+ };
122
+ }
123
+ if (model.maxOutput === null && model.maxContext !== null && requested > model.maxContext) {
124
+ return {
125
+ ok: false,
126
+ message: `Model '${model.id}' has a context window of ${model.maxContext} tokens, and ${requested} output tokens were requested. No separate output limit is published for it.`
127
+ };
128
+ }
129
+ return { ok: true };
130
+ }
131
+
132
+ export {
133
+ requiredCapabilities,
134
+ MODALITIES,
135
+ lookupModel,
136
+ checkLimits
137
+ };
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ // Built by packages/cli/build.ts.
3
+
4
+ // src/demo-catalogue.ts
5
+ var DEMO_ENVIRONMENT = "demo";
6
+
7
+ export {
8
+ DEMO_ENVIRONMENT
9
+ };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ // Built by packages/cli/build.ts.
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ // Built by packages/cli/build.ts.
3
+
4
+ // src/demo-charge.ts
5
+ function demoChargeStatements(db, projectId, micros, actor) {
6
+ if (!Number.isInteger(micros) || micros < 0) throw new Error(`demo charge must be integer micros >= 0, got ${micros}`);
7
+ return [
8
+ db.prepare(
9
+ `UPDATE budgets SET demo_charge_micros = ?2, reserve_micros = 0, updated_by = ?3, updated_at = datetime('now')
10
+ WHERE scope = 'project' AND subject = ?1`
11
+ ).bind(projectId, micros, actor),
12
+ db.prepare(
13
+ `INSERT INTO audit_events (id, actor, action, subject, detail, tenant_id)
14
+ SELECT ?1, ?2, 'budget.demo_charge', ?3, ?4, (SELECT tenant_id FROM projects WHERE id = ?3)`
15
+ ).bind(crypto.randomUUID(), actor, projectId, JSON.stringify({ demo_charge_micros: micros, reserve_micros: 0 }))
16
+ ];
17
+ }
18
+
19
+ export {
20
+ demoChargeStatements
21
+ };
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ // Built by packages/cli/build.ts.
3
+ import {
4
+ sha256Hex
5
+ } from "./chunk-5TVD3ZAI.mjs";
6
+
7
+ // src/reliability.ts
8
+ var RATE_LIMIT_COOLDOWN_MS = 5 * 6e4;
9
+
10
+ // src/affinity.ts
11
+ var SESSION_RE = /^[A-Za-z0-9._:-]{1,128}$/;
12
+ var isSessionId = (raw) => SESSION_RE.test(raw);
13
+ var sessionDigest = (session) => sha256Hex(session);
14
+
15
+ export {
16
+ isSessionId,
17
+ sessionDigest
18
+ };
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ // Built by packages/cli/build.ts.
3
+
4
+ // src/cache-pool.ts
5
+ var CACHE_POOLS = ["platform", "tenant", "project"];
6
+ var DEFAULT_CACHE_POOL = "project";
7
+ function isCachePool(value) {
8
+ return typeof value === "string" && CACHE_POOLS.includes(value);
9
+ }
10
+
11
+ export {
12
+ CACHE_POOLS,
13
+ DEFAULT_CACHE_POOL,
14
+ isCachePool
15
+ };
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env node
2
+ // Built by packages/cli/build.ts.
3
+
4
+ // src/budget.ts
5
+ var MICROS_PER_USD = 1e6;
6
+ function usdToMicros(usd) {
7
+ return Math.round(usd * MICROS_PER_USD);
8
+ }
9
+ function microsToUsd(micros) {
10
+ return micros / MICROS_PER_USD;
11
+ }
12
+ function formatUsd(micros, decimals = 2) {
13
+ return microsToUsd(micros).toFixed(decimals);
14
+ }
15
+ function periodStartExpr(period) {
16
+ if (period === "minute") return `strftime('%Y-%m-%d %H:%M', 'now')`;
17
+ return period === "month" ? `strftime('%Y-%m-01', 'now')` : `date('now')`;
18
+ }
19
+ async function readBudget(db, projectId) {
20
+ const configured = await db.prepare(
21
+ `SELECT b.period, b.soft_micros, b.hard_micros, b.session_hard_micros, p.budget_behaviour
22
+ FROM projects p
23
+ LEFT JOIN budgets b ON b.scope = 'project' AND b.subject = p.id
24
+ WHERE p.id = ?`
25
+ ).bind(projectId).first();
26
+ if (configured === null) return null;
27
+ const period = configured.period ?? "day";
28
+ const spendRow = await db.prepare(
29
+ `SELECT ${periodStartExpr(period)} AS period_start,
30
+ COALESCE((SELECT spend_micros FROM spend
31
+ WHERE scope = 'project' AND subject = ?1 AND period = ?2
32
+ AND period_start = ${periodStartExpr(period)}), 0) AS spend_micros`
33
+ ).bind(projectId, period).first();
34
+ const spendMicros = spendRow?.spend_micros ?? 0;
35
+ const softMicros = configured.soft_micros;
36
+ const hardMicros = configured.hard_micros;
37
+ let state = "ok";
38
+ if (hardMicros !== null && spendMicros >= hardMicros) state = "hard";
39
+ else if (softMicros !== null && spendMicros >= softMicros) state = "soft";
40
+ const behaviour = configured.budget_behaviour;
41
+ let action = "serve";
42
+ if (state === "hard") action = behaviour === "degrade" ? "degrade" : "refuse";
43
+ return {
44
+ projectId,
45
+ period,
46
+ periodStart: spendRow?.period_start ?? "",
47
+ spendMicros,
48
+ softMicros,
49
+ hardMicros,
50
+ sessionHardMicros: configured.session_hard_micros,
51
+ state,
52
+ behaviour,
53
+ action
54
+ };
55
+ }
56
+ async function setBudget(db, projectId, limits, actor, also = []) {
57
+ const period = limits.period ?? "day";
58
+ await db.batch([
59
+ db.prepare(
60
+ `INSERT INTO budgets (scope, subject, period, soft_micros, hard_micros, session_hard_micros, updated_by)
61
+ VALUES ('project', ?1, ?2, ?3, ?4, CASE WHEN ?6 THEN ?7 END, ?5)
62
+ ON CONFLICT (scope, subject) DO UPDATE
63
+ SET period = ?2, soft_micros = ?3, hard_micros = ?4,
64
+ session_hard_micros = CASE WHEN ?6 THEN ?7 ELSE session_hard_micros END,
65
+ updated_by = ?5, updated_at = datetime('now')`
66
+ ).bind(
67
+ projectId,
68
+ period,
69
+ limits.softMicros ?? null,
70
+ limits.hardMicros ?? null,
71
+ actor,
72
+ limits.sessionHardMicros === void 0 ? 0 : 1,
73
+ limits.sessionHardMicros ?? null
74
+ ),
75
+ db.prepare(
76
+ `INSERT INTO audit_events (id, actor, action, subject, detail, tenant_id)
77
+ SELECT ?1, ?2, 'budget.set', ?3, ?4, (SELECT tenant_id FROM projects WHERE id = ?3)`
78
+ ).bind(
79
+ crypto.randomUUID(),
80
+ actor,
81
+ projectId,
82
+ JSON.stringify({
83
+ period,
84
+ soft_micros: limits.softMicros ?? null,
85
+ hard_micros: limits.hardMicros ?? null,
86
+ ...limits.sessionHardMicros === void 0 ? {} : { session_hard_micros: limits.sessionHardMicros }
87
+ })
88
+ ),
89
+ ...also
90
+ ]);
91
+ }
92
+
93
+ export {
94
+ usdToMicros,
95
+ formatUsd,
96
+ readBudget,
97
+ setBudget
98
+ };
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+ // Built by packages/cli/build.ts.
3
+
4
+ // src/reservation-journal.ts
5
+ var ABANDONED_AFTER_SECONDS = 2 * 60 * 60;
6
+ var JOB_ABANDONED_AFTER_SECONDS = 48 * 60 * 60;
7
+ async function sweepAbandonedHolds(db, now, olderThanSeconds = ABANDONED_AFTER_SECONDS, jobsOlderThanSeconds = JOB_ABANDONED_AFTER_SECONDS) {
8
+ const before = (seconds) => new Date(now.getTime() - seconds * 1e3).toISOString().replace("T", " ").slice(0, 19);
9
+ const { results } = await db.prepare(
10
+ `SELECT id FROM reservations
11
+ WHERE (job_id IS NULL AND at < ?1) OR (job_id IS NOT NULL AND at < ?2)
12
+ ORDER BY at`
13
+ ).bind(before(olderThanSeconds), before(jobsOlderThanSeconds)).all();
14
+ let swept = 0;
15
+ for (const { id } of results) {
16
+ const claim = await db.prepare(`UPDATE reservations SET swept_at = COALESCE(swept_at, datetime('now')) WHERE id = ?`).bind(id).run();
17
+ if ((claim.meta.changes ?? 0) === 0) continue;
18
+ const closing = await db.batch([
19
+ db.prepare(
20
+ `INSERT OR IGNORE INTO ledger
21
+ (id, at, tenant_id, project_id, traffic_class, lane, account, model, upstream_id, key_prefix,
22
+ cost_micros, list_micros, priced, outcome, streamed, requested_model, session_digest)
23
+ SELECT r.id, r.at, p.tenant_id, r.project_id, r.traffic_class, r.lane, r.account, r.model,
24
+ r.upstream_id, r.key_prefix, r.reserved_micros, r.reserved_micros, 0, 'no_usage',
25
+ r.streamed, r.requested_model,
26
+ -- The run's digest is the subject's tail (sessionSubject), so
27
+ -- an abandoned turn still counts toward its run (#651, #617).
28
+ substr(r.session_subject, -64)
29
+ FROM reservations r JOIN projects p ON p.id = r.project_id
30
+ WHERE r.id = ?`
31
+ ).bind(id),
32
+ // At the hold's own time, so reconcile counts it in the window it drifted.
33
+ db.prepare(
34
+ `INSERT OR IGNORE INTO reservation_failures (id, at, project_id, reserved, stage, detail)
35
+ SELECT 'abandoned:' || r.id, r.at, r.project_id, r.reserved_micros, 'abandoned',
36
+ 'never settled; ledger row ' || r.id || ' charged the hold'
37
+ FROM reservations r WHERE r.id = ?`
38
+ ).bind(id),
39
+ // A job whose hold is abandoned is a job nothing is running (#714): it
40
+ // fails, and its hook is owed that news. Before the delete, because the
41
+ // job is found through the row; a hold with no job matches nothing.
42
+ db.prepare(
43
+ `UPDATE jobs
44
+ SET status = 'failed', error = 'abandoned: its hold outlived the job threshold unsettled',
45
+ completed_at = datetime('now'), updated_at = datetime('now'),
46
+ delivery = CASE WHEN webhook_endpoint_id IS NULL THEN delivery ELSE 'pending' END,
47
+ next_delivery_at = CASE WHEN webhook_endpoint_id IS NULL THEN next_delivery_at ELSE datetime('now') END
48
+ WHERE id = (SELECT job_id FROM reservations WHERE id = ?1)
49
+ AND status IN ('queued', 'running')
50
+ AND EXISTS (SELECT 1 FROM ledger WHERE id = ?1)`
51
+ ).bind(id),
52
+ db.prepare(`DELETE FROM reservations WHERE id = ?1 AND EXISTS (SELECT 1 FROM ledger WHERE id = ?1)`).bind(id)
53
+ ]);
54
+ if ((closing[closing.length - 1]?.meta.changes ?? 0) > 0) swept++;
55
+ }
56
+ return swept;
57
+ }
58
+
59
+ // src/session-budget.ts
60
+ var SESSION_IDLE_DAYS = 30;
61
+ async function pruneIdleSessions(db) {
62
+ const result = await db.prepare(
63
+ `DELETE FROM spend
64
+ WHERE scope = 'session' AND updated_at < datetime('now', ?1)
65
+ AND NOT EXISTS (SELECT 1 FROM reservations r WHERE r.session_subject = spend.subject)`
66
+ ).bind(`-${SESSION_IDLE_DAYS} days`).run();
67
+ return result.meta.changes ?? 0;
68
+ }
69
+
70
+ // src/ratelimit.ts
71
+ var WINDOW_SECONDS = 60;
72
+ async function sweepRateBuckets(db, now) {
73
+ const bucket = Math.floor(Math.floor(now.getTime() / 1e3) / WINDOW_SECONDS);
74
+ const result = await db.prepare(`DELETE FROM rate_buckets WHERE bucket < ?`).bind(bucket - 1).run();
75
+ return result.meta.changes ?? 0;
76
+ }
77
+
78
+ export {
79
+ sweepAbandonedHolds,
80
+ SESSION_IDLE_DAYS,
81
+ pruneIdleSessions,
82
+ sweepRateBuckets
83
+ };
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ // Built by packages/cli/build.ts.
3
+ import {
4
+ MODALITIES
5
+ } from "./chunk-LALZUKEC.mjs";
6
+
7
+ // src/conformance-plan.ts
8
+ function shardOf(items, index, total) {
9
+ if (!Number.isInteger(total) || total < 1) throw new Error(`--shard total must be a positive integer, got ${total}`);
10
+ if (!Number.isInteger(index) || index < 1 || index > total) {
11
+ throw new Error(`--shard index must be between 1 and ${total}, got ${index}`);
12
+ }
13
+ return items.filter((_, i) => i % total === index - 1);
14
+ }
15
+ function planShard(models, index, total) {
16
+ const stable = [...models].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
17
+ return shardOf(stable, index, total).sort(stalestFirst);
18
+ }
19
+ function stalestFirst(a, b) {
20
+ if (a.measured_at === b.measured_at) return a.id < b.id ? -1 : 1;
21
+ if (a.measured_at === null) return -1;
22
+ if (b.measured_at === null) return 1;
23
+ return a.measured_at < b.measured_at ? -1 : 1;
24
+ }
25
+ function selectModels(models, selection) {
26
+ const ids = selection.ids ?? [];
27
+ const modalities = selection.modalities ?? [];
28
+ const unknown = modalities.filter((m) => !MODALITIES.includes(m));
29
+ if (unknown.length > 0) {
30
+ throw new Error(`--modality does not know ${unknown.join(", ")}. The closed set is ${MODALITIES.join(", ")}.`);
31
+ }
32
+ const present = new Set(models.map((m) => m.id));
33
+ const missing = ids.filter((id) => !present.has(id));
34
+ if (missing.length > 0) {
35
+ throw new Error(
36
+ `--models names ${missing.join(", ")}, which no enabled model on an enabled upstream matches. A run that quietly skipped them would report as a run that passed.`
37
+ );
38
+ }
39
+ const wanted = new Set(ids);
40
+ const chosen = models.filter(
41
+ (m) => (ids.length === 0 || wanted.has(m.id)) && (modalities.length === 0 || modalities.includes(m.modality))
42
+ );
43
+ if (chosen.length === 0 && (ids.length > 0 || modalities.length > 0)) {
44
+ throw new Error(
45
+ `no enabled model matches ${[ids.length > 0 ? `--models ${ids.join(",")}` : "", modalities.length > 0 ? `--modality ${modalities.join(",")}` : ""].filter((part) => part !== "").join(" and ")}.`
46
+ );
47
+ }
48
+ return chosen;
49
+ }
50
+ function listFlag(raw, flag) {
51
+ if (raw === void 0) return void 0;
52
+ if (raw === true || raw.trim() === "") {
53
+ throw new Error(`--${flag} needs a comma-separated value. Given nothing, it would run as though it were absent.`);
54
+ }
55
+ const parts = raw.split(",").map((part) => part.trim());
56
+ if (parts.some((part) => part === "")) {
57
+ throw new Error(`--${flag} has an empty entry in '${raw}'. An unset shell variable reads exactly like this.`);
58
+ }
59
+ return parts;
60
+ }
61
+
62
+ export {
63
+ planShard,
64
+ selectModels,
65
+ listFlag
66
+ };
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env node
2
+ // Built by packages/cli/build.ts.
3
+
4
+ // src/ledger-components.ts
5
+ async function spendByComponent(db, month, tenantId) {
6
+ const scope = tenantId === void 0 ? "" : "AND l.tenant_id = ?2";
7
+ const window = `(?1 = 'all' OR strftime('%Y-%m', l.at) = ?1)`;
8
+ const statement = db.prepare(
9
+ `SELECT CASE WHEN c.variant = '' THEN c.component ELSE c.component || ':' || c.variant END AS bucket,
10
+ count(DISTINCT l.id) AS requests,
11
+ COALESCE(sum(c.micros), 0) AS micros,
12
+ COALESCE(sum(c.micros), 0) AS listMicros,
13
+ 0 AS tokens,
14
+ COALESCE(sum(c.count), 0) AS units
15
+ FROM ledger_components c
16
+ JOIN ledger l ON l.id = c.ledger_id
17
+ WHERE ${window} ${scope}
18
+ GROUP BY bucket
19
+ ORDER BY micros DESC`
20
+ );
21
+ const { results } = await (tenantId === void 0 ? statement.bind(month) : statement.bind(month, tenantId)).all();
22
+ return results;
23
+ }
24
+
25
+ // src/ledger.ts
26
+ function outcomeOf(body, priced, hadUsage) {
27
+ const choice = body?.choices?.[0];
28
+ if (choice?.message?.refusal != null) return "refusal";
29
+ if (String(choice?.finish_reason ?? "").toLowerCase().includes("refus")) return "refusal";
30
+ const content = typeof choice?.message?.content === "string" ? choice.message.content : "";
31
+ if (content.trim() === "" && String(choice?.finish_reason ?? "") !== "tool_calls") return "empty";
32
+ if (!hadUsage) return "no_usage";
33
+ return priced ? "served" : "unpriced";
34
+ }
35
+ var ALL_MONTHS = "all";
36
+ var MONTH_PATTERN = "\\d{4}-(0[1-9]|1[0-2])";
37
+ var CALENDAR_MONTH = new RegExp(`^${MONTH_PATTERN}$`);
38
+ var GROUPABLE = /* @__PURE__ */ new Set([
39
+ "project_id",
40
+ "model",
41
+ "account",
42
+ "lane",
43
+ "traffic_class",
44
+ "upstream_id",
45
+ "outcome",
46
+ // The hit rate beside the discount it earned (#98): requests per status,
47
+ // and `micros` against `listMicros` is what the discount came to.
48
+ "cache_status",
49
+ // How wide the cache that answered was allowed to be (#276, 0068). Every
50
+ // miss buckets under NULL, so this is the hit breakdown rather than a
51
+ // second spelling of `cache_status`: it is the dimension that tells a
52
+ // model-scope saving from a lane-scope one, and those are different
53
+ // products — one is a repeat, the other is an answer somebody else paid
54
+ // for. Blending them would report the second as the first.
55
+ "cache_scope",
56
+ // What has this key spent (#147 gap 3, asked for by the dashboard).
57
+ //
58
+ // The first question anyone asks before revoking a key, and the second one
59
+ // after a leak — where the answer decides whether it is an inconvenience or
60
+ // an incident. The dashboard could issue a key and revoke a key and could
61
+ // not answer it.
62
+ //
63
+ // The bucket is the **prefix**, which is the half designed to be seen: it is
64
+ // what a key listing returns, what `audit_events.actor` records, and what a
65
+ // leaked string is classified by on sight. The key itself is stored hashed
66
+ // and is not here to leak.
67
+ //
68
+ // A revoked key keeps its rows and keeps answering, which is the point. The
69
+ // ledger references ids that must outlive the thing they name, so "what did
70
+ // the key I just revoked spend" is a question with an answer rather than a
71
+ // gap where the row used to be.
72
+ "key_prefix",
73
+ // Not a column of `ledger` at all (#228): the per-unit lines a request's
74
+ // total is made of, from `ledger_components`. Answered by its own query
75
+ // because it joins a child table, and listed here so the refusal below
76
+ // names every dimension there is.
77
+ "component"
78
+ ]);
79
+ async function spendBy(db, dimension, month, tenantId) {
80
+ if (!GROUPABLE.has(dimension)) {
81
+ throw new Error(`Cannot group by '${dimension}'. Try one of: ${[...GROUPABLE].join(", ")}`);
82
+ }
83
+ if (dimension === "component") return spendByComponent(db, month, tenantId);
84
+ const scope = tenantId === void 0 ? "" : "AND tenant_id = ?2";
85
+ const window = `(?1 = '${ALL_MONTHS}' OR strftime('%Y-%m', at) = ?1)`;
86
+ const statement = db.prepare(
87
+ `SELECT ${dimension} AS bucket,
88
+ count(*) AS requests,
89
+ COALESCE(sum(cost_micros), 0) AS micros,
90
+ COALESCE(sum(list_micros), 0) AS listMicros,
91
+ COALESCE(sum(prompt_tokens + completion_tokens), 0) AS tokens,
92
+ NULL AS units
93
+ FROM ledger
94
+ WHERE ${window} ${scope}
95
+ GROUP BY ${dimension}
96
+ ORDER BY micros DESC`
97
+ );
98
+ const { results } = await (tenantId === void 0 ? statement.bind(month) : statement.bind(month, tenantId)).all();
99
+ return results;
100
+ }
101
+ async function effectiveRates(db, month, tenantId) {
102
+ const scope = tenantId === void 0 ? "" : "AND l.tenant_id = ?2";
103
+ const statement = db.prepare(
104
+ `SELECT l.model AS model,
105
+ count(*) AS requests,
106
+ CASE WHEN sum(l.prompt_tokens + l.completion_tokens) > 0
107
+ THEN CAST(sum(l.list_micros) * 1000000.0
108
+ / sum(l.prompt_tokens + l.completion_tokens) AS INTEGER)
109
+ ELSE NULL END AS observedInputMicrosPerMtok,
110
+ (SELECT p.input_micros_per_mtok FROM model_prices p
111
+ WHERE p.model = l.model AND p.prompt_tokens_from = 0 AND p.peak_utc = ''
112
+ ORDER BY p.effective_from DESC LIMIT 1)
113
+ AS tableInputMicrosPerMtok
114
+ FROM ledger l
115
+ WHERE strftime('%Y-%m', l.at) = ?1 AND l.priced = 1 ${scope}
116
+ GROUP BY l.model`
117
+ );
118
+ const { results } = await (tenantId === void 0 ? statement.bind(month) : statement.bind(month, tenantId)).all();
119
+ return results;
120
+ }
121
+
122
+ export {
123
+ outcomeOf,
124
+ spendBy,
125
+ effectiveRates
126
+ };
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ // Built by packages/cli/build.ts.
3
+ import "./chunk-4447IUTF.mjs";
4
+ import "./chunk-O7TNRKSA.mjs";
5
+ import {
6
+ connect,
7
+ environmentOf,
8
+ parseArgs,
9
+ requireString,
10
+ run
11
+ } from "./chunk-7NQHBXVZ.mjs";
12
+ import "./chunk-JZPUTXM7.mjs";
13
+
14
+ // src/compress-setting.ts
15
+ async function setProjectCompression(db, projectId, enabled, actor) {
16
+ const to = enabled ? 1 : 0;
17
+ const [, update] = await db.batch([
18
+ db.prepare(
19
+ `INSERT INTO audit_events (id, actor, action, subject, detail, tenant_id)
20
+ SELECT ?1, ?2, 'project.compression', ?3,
21
+ json_object('from', json(CASE p.compress WHEN 1 THEN 'true' ELSE 'false' END),
22
+ 'to', json(CASE ?4 WHEN 1 THEN 'true' ELSE 'false' END)),
23
+ p.tenant_id
24
+ FROM projects p WHERE p.id = ?3`
25
+ ).bind(crypto.randomUUID(), actor, projectId, to),
26
+ db.prepare(`UPDATE projects SET compress = ?1 WHERE id = ?2`).bind(to, projectId)
27
+ ]);
28
+ return (update?.meta.changes ?? 0) === 0 ? null : { enabled };
29
+ }
30
+
31
+ // scripts/compress.ts
32
+ run(async () => {
33
+ const [command, ...rest] = process.argv.slice(2);
34
+ const args = parseArgs(rest);
35
+ const db = connect(environmentOf(args));
36
+ if (command === "set") {
37
+ const project = requireString(args, "project");
38
+ const actor = requireString(args, "actor");
39
+ if (args.on === true === (args.off === true)) throw new Error("Pass exactly one of --on or --off.");
40
+ const written = await setProjectCompression(db, project, args.on === true, actor);
41
+ if (written === null) throw new Error(`No project with id '${project}'.`);
42
+ console.log(`${project}: compression ${written.enabled ? "on" : "off"}.`);
43
+ return;
44
+ }
45
+ const { results } = await db.prepare(`SELECT tenant_id, id, compress FROM projects WHERE compress = 1 ORDER BY tenant_id, id`).all();
46
+ if (results.length === 0) console.log("No project has compression switched on.");
47
+ for (const row of results) console.log(`${row.tenant_id.padEnd(24)} ${row.id}`);
48
+ console.log("\nIn effect only where the environment's COMPRESS_MODE is 'on' (wrangler.jsonc); `npm run doctor` reads it as deployed.");
49
+ });