@bytesbrains/cruise 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 +59 -2
- package/dist/budget-FIRNSN67.mjs +132 -0
- package/dist/chunk-2FB2T3UI.mjs +122 -0
- package/dist/chunk-2NVDAK5J.mjs +76 -0
- package/dist/chunk-42ERHAJD.mjs +79 -0
- package/dist/chunk-4447IUTF.mjs +7 -0
- package/dist/chunk-5TVD3ZAI.mjs +69 -0
- package/dist/chunk-7NQHBXVZ.mjs +104 -0
- package/dist/chunk-BNP63KB2.mjs +42 -0
- package/dist/chunk-FGLMW5IY.mjs +575 -0
- package/dist/chunk-GUZNI3NS.mjs +267 -0
- package/dist/chunk-HQDQTXRX.mjs +57 -0
- package/dist/chunk-ICJVJSTC.mjs +42 -0
- package/dist/chunk-JZPUTXM7.mjs +236 -0
- package/dist/chunk-LALZUKEC.mjs +137 -0
- package/dist/chunk-N3YKWKOA.mjs +9 -0
- package/dist/chunk-O7TNRKSA.mjs +2 -0
- package/dist/chunk-RT4R2W6H.mjs +21 -0
- package/dist/chunk-SYTYOTO4.mjs +18 -0
- package/dist/chunk-T3ICD43U.mjs +15 -0
- package/dist/chunk-THEXUHQC.mjs +98 -0
- package/dist/chunk-UNXM6FSM.mjs +83 -0
- package/dist/chunk-WS6NFRIU.mjs +66 -0
- package/dist/chunk-XOH3XKM3.mjs +126 -0
- package/dist/compress-3DWG35BY.mjs +49 -0
- package/dist/conformance-NPFDYCQL.mjs +922 -0
- package/dist/cruise.mjs +693 -0
- package/dist/fallback-P5KK55M4.mjs +139 -0
- package/dist/keys-FWPQPMTW.mjs +127 -0
- package/dist/ledger-XBGWXY6C.mjs +97 -0
- package/dist/member-ZBV4YG2M.mjs +74 -0
- package/dist/report-VRYJ3W5L.mjs +414 -0
- package/dist/service-key-N2RZ7FMZ.mjs +59 -0
- package/dist/smoke-ZNYX6RO5.mjs +105 -0
- package/dist/sweep-SZX7OIDK.mjs +33 -0
- package/dist/tenant-4YDGI23Q.mjs +122 -0
- package/dist/tier-4L3SPLFP.mjs +116 -0
- package/package.json +38 -4
- package/templates/docker-compose.yml +66 -0
- 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,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
|
+
});
|