@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,575 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Built by packages/cli/build.ts.
|
|
3
|
+
import {
|
|
4
|
+
outcomeOf
|
|
5
|
+
} from "./chunk-XOH3XKM3.mjs";
|
|
6
|
+
import {
|
|
7
|
+
tokenCount,
|
|
8
|
+
usageFromJson
|
|
9
|
+
} from "./chunk-ICJVJSTC.mjs";
|
|
10
|
+
import {
|
|
11
|
+
checkLimits,
|
|
12
|
+
requiredCapabilities
|
|
13
|
+
} from "./chunk-LALZUKEC.mjs";
|
|
14
|
+
|
|
15
|
+
// src/reservation-search.ts
|
|
16
|
+
function searchInputBound(model, body, promptTokens) {
|
|
17
|
+
const search = webSearch(body);
|
|
18
|
+
if (!search.present) return promptTokens;
|
|
19
|
+
const window = model.maxContext;
|
|
20
|
+
if (!searchBoundedByHost(model) || search.maxUses === null || typeof window !== "number" || window <= 0) return null;
|
|
21
|
+
return Math.min(promptTokens, window) + search.maxUses * window;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/reservation.ts
|
|
25
|
+
var TOKENS_PER_MTOK = 1e6;
|
|
26
|
+
function promptTokenBound(raw) {
|
|
27
|
+
return new TextEncoder().encode(raw).byteLength;
|
|
28
|
+
}
|
|
29
|
+
function projectCapFor(model, projectDefault) {
|
|
30
|
+
if (projectDefault === null) return null;
|
|
31
|
+
return model.maxOutput === null ? projectDefault : Math.min(projectDefault, model.maxOutput);
|
|
32
|
+
}
|
|
33
|
+
function requestedOutput(body) {
|
|
34
|
+
const requested = body.max_tokens ?? body.max_completion_tokens;
|
|
35
|
+
return typeof requested === "number" && Number.isInteger(requested) && requested >= 0 ? requested : null;
|
|
36
|
+
}
|
|
37
|
+
function dearestRates(rows) {
|
|
38
|
+
let input = 0;
|
|
39
|
+
let output = 0;
|
|
40
|
+
for (const row of rows) {
|
|
41
|
+
input = Math.max(input, row.input_micros_per_mtok);
|
|
42
|
+
output = Math.max(output, row.output_micros_per_mtok);
|
|
43
|
+
}
|
|
44
|
+
return { input, output };
|
|
45
|
+
}
|
|
46
|
+
function boundFor(candidates, priceRows, body, raw, projectDefault = null) {
|
|
47
|
+
const bodyTokens = promptTokenBound(raw);
|
|
48
|
+
const stated = requestedOutput(body);
|
|
49
|
+
let micros = 0;
|
|
50
|
+
let dearest = "";
|
|
51
|
+
let promptTokens = bodyTokens;
|
|
52
|
+
let outputTokens = 0;
|
|
53
|
+
for (const model of candidates) {
|
|
54
|
+
const rows = priceRows.get(model.id);
|
|
55
|
+
if (rows === void 0 || rows.length === 0) return { ok: false, model: model.id, reason: "unpriced" };
|
|
56
|
+
const rates = dearestRates(rows);
|
|
57
|
+
const input = searchInputBound(model, body, bodyTokens);
|
|
58
|
+
if (input === null) return { ok: false, model: model.id, reason: "search_input_unbounded" };
|
|
59
|
+
const output = stated ?? projectCapFor(model, projectDefault) ?? model.defaultOutput ?? model.maxOutput;
|
|
60
|
+
if (output === null || output === void 0) {
|
|
61
|
+
return { ok: false, model: model.id, reason: "no_output_limit" };
|
|
62
|
+
}
|
|
63
|
+
const worst = Math.ceil((input * rates.input + output * rates.output) / TOKENS_PER_MTOK);
|
|
64
|
+
if (worst > micros) {
|
|
65
|
+
micros = worst;
|
|
66
|
+
dearest = model.id;
|
|
67
|
+
promptTokens = input;
|
|
68
|
+
outputTokens = output;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { ok: true, micros, dearest, promptTokens, outputTokens };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/lanes.ts
|
|
75
|
+
var LANE_PREFIX = "bb/";
|
|
76
|
+
var CONTRIBUTOR_LANE_PREFIX = "oss/";
|
|
77
|
+
var SLUG = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
78
|
+
|
|
79
|
+
// src/routing-image.ts
|
|
80
|
+
var TOKENS_PER_MTOK2 = 1e6;
|
|
81
|
+
var AUTO = "auto";
|
|
82
|
+
var MAX_IMAGES_PER_REQUEST = 10;
|
|
83
|
+
function imageRequestOf(body) {
|
|
84
|
+
const size = body.size === void 0 || body.size === null ? AUTO : body.size;
|
|
85
|
+
const quality = body.quality === void 0 || body.quality === null ? AUTO : body.quality;
|
|
86
|
+
if (typeof size !== "string" || typeof quality !== "string") return null;
|
|
87
|
+
const asked = body.n;
|
|
88
|
+
if (asked === void 0 || asked === null) return { size, quality, n: 1 };
|
|
89
|
+
if (typeof asked !== "number" || !Number.isSafeInteger(asked) || asked < 1) return null;
|
|
90
|
+
return { size, quality, n: asked };
|
|
91
|
+
}
|
|
92
|
+
async function variantsFor(db, modelIds) {
|
|
93
|
+
const byModel = /* @__PURE__ */ new Map();
|
|
94
|
+
if (modelIds.length === 0) return byModel;
|
|
95
|
+
const placeholders = modelIds.map(() => "?").join(", ");
|
|
96
|
+
const { results } = await db.prepare(
|
|
97
|
+
`SELECT model, size, quality, output_tokens, measured_at
|
|
98
|
+
FROM model_image_variants
|
|
99
|
+
WHERE model IN (${placeholders})
|
|
100
|
+
ORDER BY model, output_tokens`
|
|
101
|
+
).bind(...modelIds).all();
|
|
102
|
+
for (const row of results) {
|
|
103
|
+
const list = byModel.get(row.model) ?? [];
|
|
104
|
+
list.push({
|
|
105
|
+
size: row.size,
|
|
106
|
+
quality: row.quality,
|
|
107
|
+
outputTokens: row.output_tokens,
|
|
108
|
+
measuredAt: row.measured_at
|
|
109
|
+
});
|
|
110
|
+
byModel.set(row.model, list);
|
|
111
|
+
}
|
|
112
|
+
return byModel;
|
|
113
|
+
}
|
|
114
|
+
function resolveVariant(model, variants, asked) {
|
|
115
|
+
const measured = variants.filter((v) => v.outputTokens !== null);
|
|
116
|
+
if (measured.length === 0) {
|
|
117
|
+
return {
|
|
118
|
+
ok: false,
|
|
119
|
+
message: `Model '${model.id}' has no measured image variants, so an image request to it cannot be bounded. Variants are measured by the conformance suite (#10, #227).`
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (asked.size === AUTO || asked.quality === AUTO) {
|
|
123
|
+
const dearest = measured.reduce((worst, v) => (v.outputTokens ?? 0) > (worst.outputTokens ?? 0) ? v : worst);
|
|
124
|
+
return { ok: true, outputTokens: dearest.outputTokens, variant: `${dearest.size}/${dearest.quality}` };
|
|
125
|
+
}
|
|
126
|
+
const exact = measured.find((v) => v.size === asked.size && v.quality === asked.quality);
|
|
127
|
+
if (exact === void 0) {
|
|
128
|
+
const offered = measured.map((v) => `${v.size}/${v.quality}`).join(", ");
|
|
129
|
+
return {
|
|
130
|
+
ok: false,
|
|
131
|
+
message: `Model '${model.id}' has no measured cost for size '${asked.size}' at quality '${asked.quality}', so a request for it cannot be bounded. Measured variants: ${offered}.`
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return { ok: true, outputTokens: exact.outputTokens, variant: `${exact.size}/${exact.quality}` };
|
|
135
|
+
}
|
|
136
|
+
function resolveComponentImageVariant(model, rows, asked) {
|
|
137
|
+
const imageRows = rows.filter((row) => row.component === "image");
|
|
138
|
+
if (imageRows.length === 0) {
|
|
139
|
+
return {
|
|
140
|
+
ok: false,
|
|
141
|
+
message: `Model '${model.id}' has no image component price in force, so an image request to it cannot be bounded (#240).`
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (asked.size === AUTO || asked.quality === AUTO) return { ok: true };
|
|
145
|
+
if (imageRows.some((row) => row.variant === "" || row.variant === `${asked.size}/${asked.quality}`)) {
|
|
146
|
+
return { ok: true };
|
|
147
|
+
}
|
|
148
|
+
const offered = imageRows.map((row) => row.variant === "" ? "(flat)" : row.variant).join(", ");
|
|
149
|
+
return {
|
|
150
|
+
ok: false,
|
|
151
|
+
message: `Model '${model.id}' has no image price for size '${asked.size}' at quality '${asked.quality}'. Priced variants: ${offered}.`
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function imageBoundFor(candidates, priceRows, variants, asked, raw) {
|
|
155
|
+
const promptTokens = promptTokenBound(raw);
|
|
156
|
+
let micros = 0;
|
|
157
|
+
let dearest = "";
|
|
158
|
+
let outputTokens = 0;
|
|
159
|
+
for (const model of candidates) {
|
|
160
|
+
const rows = priceRows.get(model.id);
|
|
161
|
+
if (rows === void 0 || rows.length === 0) return { ok: false, model: model.id, reason: "unpriced" };
|
|
162
|
+
const variant = resolveVariant(model, variants.get(model.id) ?? [], asked);
|
|
163
|
+
if (!variant.ok) return { ok: false, model: model.id, reason: "unmeasured_variant" };
|
|
164
|
+
const rates = dearestRates(rows);
|
|
165
|
+
const output = variant.outputTokens * asked.n;
|
|
166
|
+
const worst = Math.ceil((promptTokens * rates.input + output * rates.output) / TOKENS_PER_MTOK2);
|
|
167
|
+
if (worst > micros) {
|
|
168
|
+
micros = worst;
|
|
169
|
+
dearest = model.id;
|
|
170
|
+
outputTokens = output;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return { ok: true, micros, dearest, promptTokens, outputTokens };
|
|
174
|
+
}
|
|
175
|
+
function componentImageBoundFor(candidates, componentRows) {
|
|
176
|
+
for (const model of candidates) {
|
|
177
|
+
const rows = componentRows.get(model.id) ?? [];
|
|
178
|
+
if (!rows.some((row) => row.component === "image")) {
|
|
179
|
+
return { ok: false, model: model.id, reason: "unpriced" };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return { ok: true, micros: 0, dearest: candidates[0]?.id ?? "", promptTokens: 0, outputTokens: 0 };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/reading-image.ts
|
|
186
|
+
function usageFromImageJson(body) {
|
|
187
|
+
const usage = body?.usage;
|
|
188
|
+
if (!usage) return null;
|
|
189
|
+
const input = tokenCount(usage.input_tokens);
|
|
190
|
+
const output = tokenCount(usage.output_tokens);
|
|
191
|
+
if (input === null || output === null) {
|
|
192
|
+
console.warn(
|
|
193
|
+
`Refusing implausible image usage from upstream: input=${String(usage.input_tokens)} output=${String(usage.output_tokens)}. Recorded as no_usage and charged the reservation.`
|
|
194
|
+
);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
const rawImageInput = usage.input_tokens_details?.image_tokens;
|
|
198
|
+
const imageInput = rawImageInput === void 0 || rawImageInput === null ? 0 : tokenCount(rawImageInput);
|
|
199
|
+
if (imageInput === null || imageInput > 0) {
|
|
200
|
+
console.warn(
|
|
201
|
+
`Image request reported ${String(rawImageInput)} image input token(s), which this ledger has no rate for (#228). Recorded as no_usage and charged the reservation rather than billed at the text rate.`
|
|
202
|
+
);
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
return { prompt_tokens: input, completion_tokens: output, reasoning_tokens: 0, cached_input_tokens: 0 };
|
|
206
|
+
}
|
|
207
|
+
function imageOutcomeOf(body, priced, hadUsage) {
|
|
208
|
+
const data = body?.data;
|
|
209
|
+
const images = Array.isArray(data) ? data.filter((entry) => {
|
|
210
|
+
const image = entry;
|
|
211
|
+
return typeof image?.b64_json === "string" && image.b64_json !== "" || typeof image?.url === "string" && image.url !== "";
|
|
212
|
+
}) : [];
|
|
213
|
+
if (images.length === 0) return "empty";
|
|
214
|
+
if (!hadUsage) return "no_usage";
|
|
215
|
+
return priced ? "served" : "unpriced";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/speech-elevenlabs.ts
|
|
219
|
+
var ELEVENLABS = "elevenlabs";
|
|
220
|
+
var FORMATS = {
|
|
221
|
+
mp3: "mp3_44100_128",
|
|
222
|
+
opus: "opus_48000_128",
|
|
223
|
+
pcm: "pcm_24000",
|
|
224
|
+
wav: "wav_24000"
|
|
225
|
+
};
|
|
226
|
+
function elevenLabsFormat(format) {
|
|
227
|
+
return FORMATS[format] ?? null;
|
|
228
|
+
}
|
|
229
|
+
function elevenLabsVoiceOk(voice) {
|
|
230
|
+
return typeof voice === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(voice);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/routing-speech.ts
|
|
234
|
+
var OPENAI_FORMATS = ["mp3", "opus", "aac", "flac", "wav", "pcm"];
|
|
235
|
+
var DEFAULT_FORMAT = "mp3";
|
|
236
|
+
function speechRequestOf(body) {
|
|
237
|
+
if (typeof body.input !== "string" || body.input.length === 0) return null;
|
|
238
|
+
const raw = body.response_format;
|
|
239
|
+
if (raw !== void 0 && raw !== null && !OPENAI_FORMATS.includes(raw)) return null;
|
|
240
|
+
return {
|
|
241
|
+
input: body.input,
|
|
242
|
+
characters: characterCount(body),
|
|
243
|
+
voice: body.voice,
|
|
244
|
+
format: raw === void 0 || raw === null ? DEFAULT_FORMAT : raw
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function checkSpeechLimits(model, body) {
|
|
248
|
+
if (body.stream_format === "sse") {
|
|
249
|
+
return {
|
|
250
|
+
ok: false,
|
|
251
|
+
code: "unsupported_capability",
|
|
252
|
+
message: `'stream_format: sse' is not served on /v1/audio/speech. Audio is passed through as bytes and nothing is parsed from it; an event stream is a shape this route does not carry (#229).`
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const asked = speechRequestOf(body);
|
|
256
|
+
if (asked === null) {
|
|
257
|
+
return {
|
|
258
|
+
ok: false,
|
|
259
|
+
message: `'input' must be a non-empty string and 'response_format', when present, one of ${OPENAI_FORMATS.join(", ")}. The length of 'input' is the bill, so a value that cannot be counted is a request that cannot be bounded.`
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
if (model.maxInputChars !== null && asked.characters > model.maxInputChars) {
|
|
263
|
+
return {
|
|
264
|
+
ok: false,
|
|
265
|
+
message: `Model '${model.id}' accepts at most ${model.maxInputChars} characters of input, and ${asked.characters} were sent. Refused here rather than reserved for and then refused upstream, since the count is the bill.`
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (model.upstreamId === ELEVENLABS) {
|
|
269
|
+
if (!elevenLabsVoiceOk(asked.voice)) {
|
|
270
|
+
return {
|
|
271
|
+
ok: false,
|
|
272
|
+
message: `'voice' must be an ElevenLabs voice id \u2014 letters, digits, '-' and '_' \u2014 for model '${model.id}'. It is sent as a path segment, so nothing else can be.`
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
if (elevenLabsFormat(asked.format) === null) {
|
|
276
|
+
return {
|
|
277
|
+
ok: false,
|
|
278
|
+
code: "unsupported_capability",
|
|
279
|
+
message: `Model '${model.id}' cannot produce '${asked.format}'. ElevenLabs serves mp3, opus, pcm and wav; aac and flac have no output format there and are refused before anything is sent (#229).`
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
return { ok: true };
|
|
283
|
+
}
|
|
284
|
+
if (asked.voice === void 0 || asked.voice === null) {
|
|
285
|
+
return { ok: false, message: `'voice' is required.` };
|
|
286
|
+
}
|
|
287
|
+
return { ok: true };
|
|
288
|
+
}
|
|
289
|
+
function speechBoundFor(candidates, characterRows) {
|
|
290
|
+
for (const model of candidates) {
|
|
291
|
+
const rows = characterRows.get(model.id) ?? [];
|
|
292
|
+
if (!rows.some((row) => row.component === "character")) {
|
|
293
|
+
return { ok: false, model: model.id, reason: "unpriced" };
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return { ok: true, micros: 0, dearest: candidates[0]?.id ?? "", promptTokens: 0, outputTokens: 0 };
|
|
297
|
+
}
|
|
298
|
+
function usageFromSpeech(_parsed) {
|
|
299
|
+
return { prompt_tokens: 0, completion_tokens: 0, reasoning_tokens: 0, cached_input_tokens: 0 };
|
|
300
|
+
}
|
|
301
|
+
function speechOutcomeOf(parsed, priced, _hadUsage) {
|
|
302
|
+
if (typeof parsed !== "number" || parsed <= 0) return "empty";
|
|
303
|
+
return priced ? "served" : "unpriced";
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/routing-embeddings.ts
|
|
307
|
+
var TOKENS_PER_MTOK3 = 1e6;
|
|
308
|
+
function hasInput(body) {
|
|
309
|
+
const input = body.input;
|
|
310
|
+
if (typeof input === "string") return input.length > 0;
|
|
311
|
+
return Array.isArray(input) && input.length > 0;
|
|
312
|
+
}
|
|
313
|
+
function checkEmbeddingLimits(_model, body) {
|
|
314
|
+
if (body.stream === true) {
|
|
315
|
+
return {
|
|
316
|
+
ok: false,
|
|
317
|
+
code: "unsupported_capability",
|
|
318
|
+
message: `Streaming is not served on /v1/embeddings. No host here streams an embedding, and a route that half works over-bills (#230).`
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
if (!hasInput(body)) {
|
|
322
|
+
return {
|
|
323
|
+
ok: false,
|
|
324
|
+
message: `'input' must be a non-empty string, or a non-empty array of strings or token ids. A request with nothing to embed cannot produce a vector, and is refused before it holds a reservation.`
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
return { ok: true };
|
|
328
|
+
}
|
|
329
|
+
function embeddingBoundFor(candidates, priceRows, raw) {
|
|
330
|
+
const promptTokens = promptTokenBound(raw);
|
|
331
|
+
let micros = 0;
|
|
332
|
+
let dearest = "";
|
|
333
|
+
for (const model of candidates) {
|
|
334
|
+
const rows = priceRows.get(model.id);
|
|
335
|
+
if (rows === void 0 || rows.length === 0) return { ok: false, model: model.id, reason: "unpriced" };
|
|
336
|
+
const worst = Math.ceil(promptTokens * dearestRates(rows).input / TOKENS_PER_MTOK3);
|
|
337
|
+
if (worst > micros || dearest === "") {
|
|
338
|
+
micros = worst;
|
|
339
|
+
dearest = model.id;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return { ok: true, micros, dearest, promptTokens, outputTokens: 0 };
|
|
343
|
+
}
|
|
344
|
+
function usageFromEmbedding(body) {
|
|
345
|
+
const usage = body?.usage;
|
|
346
|
+
if (!usage) return null;
|
|
347
|
+
const prompt = tokenCount(usage.prompt_tokens);
|
|
348
|
+
if (prompt === null) {
|
|
349
|
+
console.warn(
|
|
350
|
+
`Refusing implausible embedding usage from upstream: prompt=${String(usage.prompt_tokens)}. Recorded as no_usage and charged the reservation.`
|
|
351
|
+
);
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
const total = tokenCount(usage.total_tokens) ?? 0;
|
|
355
|
+
return { prompt_tokens: Math.max(prompt, total), completion_tokens: 0, reasoning_tokens: 0, cached_input_tokens: 0 };
|
|
356
|
+
}
|
|
357
|
+
function embeddingOutcomeOf(body, priced, hadUsage) {
|
|
358
|
+
const data = body?.data;
|
|
359
|
+
const vectors = Array.isArray(data) ? data.filter((entry) => {
|
|
360
|
+
const vector = entry?.embedding;
|
|
361
|
+
return Array.isArray(vector) && vector.length > 0 || typeof vector === "string" && vector !== "";
|
|
362
|
+
}) : [];
|
|
363
|
+
if (vectors.length === 0) return "empty";
|
|
364
|
+
if (!hadUsage) return "no_usage";
|
|
365
|
+
return priced ? "served" : "unpriced";
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/route-spec.ts
|
|
369
|
+
var ROUTES = {};
|
|
370
|
+
var CHAT_ROUTE = {
|
|
371
|
+
modality: "chat",
|
|
372
|
+
path: "/v1/chat/completions",
|
|
373
|
+
capabilities: requiredCapabilities,
|
|
374
|
+
limits: (_db, model, body) => Promise.resolve(checkLimits(model, body)),
|
|
375
|
+
bound: (_db, candidates, priceRows, body, raw, projectDefault) => Promise.resolve(boundFor([...candidates], priceRows, body, raw, projectDefault)),
|
|
376
|
+
reading: { usage: usageFromJson, outcome: outcomeOf }
|
|
377
|
+
};
|
|
378
|
+
var IMAGE_ROUTE = {
|
|
379
|
+
modality: "image",
|
|
380
|
+
path: "/v1/images/generations",
|
|
381
|
+
capabilities: () => [],
|
|
382
|
+
async limits(db, model, body) {
|
|
383
|
+
if (body.stream === true) {
|
|
384
|
+
return {
|
|
385
|
+
ok: false,
|
|
386
|
+
code: "unsupported_capability",
|
|
387
|
+
message: `Streaming is not served on /v1/images/generations. The host does serve a partial-image stream there and nothing has measured it, so it is a route that does not exist yet rather than one that half works (#227).`
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
const asked = imageRequestOf(body);
|
|
391
|
+
if (asked === null) {
|
|
392
|
+
return {
|
|
393
|
+
ok: false,
|
|
394
|
+
message: `'size' and 'quality' must be strings and 'n' a positive integer. 'n' multiplies what the request is allowed to cost, so a value that cannot be read is a bill that cannot be bounded.`
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
if (asked.n > MAX_IMAGES_PER_REQUEST) {
|
|
398
|
+
return {
|
|
399
|
+
ok: false,
|
|
400
|
+
message: `'n' is ${asked.n}; the host generates at most ${MAX_IMAGES_PER_REQUEST} images per request. Refused here rather than reserved for and then refused upstream, since 'n' multiplies the bound.`
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
if (model.pricedBy === "component") {
|
|
404
|
+
const rows = await componentRowsInForce(db, model.id, /* @__PURE__ */ new Date());
|
|
405
|
+
const verdict2 = resolveComponentImageVariant(model, rows, asked);
|
|
406
|
+
return verdict2.ok ? { ok: true } : { ok: false, message: verdict2.message };
|
|
407
|
+
}
|
|
408
|
+
const variants = await variantsFor(db, [model.id]);
|
|
409
|
+
const verdict = resolveVariant(model, variants.get(model.id) ?? [], asked);
|
|
410
|
+
return verdict.ok ? { ok: true } : { ok: false, message: verdict.message };
|
|
411
|
+
},
|
|
412
|
+
async bound(db, candidates, priceRows, body, raw) {
|
|
413
|
+
const asked = imageRequestOf(body);
|
|
414
|
+
if (asked === null) return { ok: false, model: candidates[0]?.id ?? "", reason: "no_output_limit" };
|
|
415
|
+
const component = candidates.filter((c) => c.pricedBy === "component");
|
|
416
|
+
const token = candidates.filter((c) => c.pricedBy !== "component");
|
|
417
|
+
if (component.length > 0) {
|
|
418
|
+
const rows = await componentRowsInForceFor(
|
|
419
|
+
db,
|
|
420
|
+
component.map((c) => c.id),
|
|
421
|
+
/* @__PURE__ */ new Date()
|
|
422
|
+
);
|
|
423
|
+
const part = componentImageBoundFor(component, rows);
|
|
424
|
+
if (!part.ok) return part;
|
|
425
|
+
}
|
|
426
|
+
if (token.length === 0) {
|
|
427
|
+
return { ok: true, micros: 0, dearest: candidates[0]?.id ?? "", promptTokens: 0, outputTokens: 0 };
|
|
428
|
+
}
|
|
429
|
+
const variants = await variantsFor(
|
|
430
|
+
db,
|
|
431
|
+
token.map((c) => c.id)
|
|
432
|
+
);
|
|
433
|
+
return imageBoundFor(token, priceRows, variants, asked, raw);
|
|
434
|
+
},
|
|
435
|
+
reading: { usage: usageFromImageJson, outcome: imageOutcomeOf }
|
|
436
|
+
};
|
|
437
|
+
ROUTES[CHAT_ROUTE.modality] = CHAT_ROUTE;
|
|
438
|
+
ROUTES[IMAGE_ROUTE.modality] = IMAGE_ROUTE;
|
|
439
|
+
var SPEECH_ROUTE = {
|
|
440
|
+
modality: "speech",
|
|
441
|
+
path: "/v1/audio/speech",
|
|
442
|
+
capabilities: () => [],
|
|
443
|
+
limits: (_db, model, body) => Promise.resolve(checkSpeechLimits(model, body)),
|
|
444
|
+
async bound(db, candidates) {
|
|
445
|
+
const rows = await componentRowsInForceFor(db, candidates.map((c) => c.id), /* @__PURE__ */ new Date());
|
|
446
|
+
return speechBoundFor(candidates, rows);
|
|
447
|
+
},
|
|
448
|
+
reading: { body: "bytes", usage: usageFromSpeech, outcome: speechOutcomeOf }
|
|
449
|
+
};
|
|
450
|
+
ROUTES[SPEECH_ROUTE.modality] = SPEECH_ROUTE;
|
|
451
|
+
var EMBEDDING_ROUTE = {
|
|
452
|
+
modality: "embedding",
|
|
453
|
+
path: "/v1/embeddings",
|
|
454
|
+
capabilities: () => [],
|
|
455
|
+
limits: (_db, model, body) => Promise.resolve(checkEmbeddingLimits(model, body)),
|
|
456
|
+
bound: (_db, candidates, priceRows, _body, raw) => Promise.resolve(embeddingBoundFor(candidates, priceRows, raw)),
|
|
457
|
+
reading: { usage: usageFromEmbedding, outcome: embeddingOutcomeOf }
|
|
458
|
+
};
|
|
459
|
+
ROUTES[EMBEDDING_ROUTE.modality] = EMBEDDING_ROUTE;
|
|
460
|
+
|
|
461
|
+
// src/chat-openai-responses-stream.ts
|
|
462
|
+
var encoder = new TextEncoder();
|
|
463
|
+
|
|
464
|
+
// src/chat-perplexity-agent.ts
|
|
465
|
+
var PERPLEXITY_UPSTREAM = "perplexity";
|
|
466
|
+
function wantsPerplexityAgent(candidate) {
|
|
467
|
+
return candidate.upstreamId === PERPLEXITY_UPSTREAM;
|
|
468
|
+
}
|
|
469
|
+
function searchBoundedByHost(candidate) {
|
|
470
|
+
return !wantsPerplexityAgent(candidate);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// src/pricing-components.ts
|
|
474
|
+
var DEAREST = "\0dearest";
|
|
475
|
+
function contextSize(body) {
|
|
476
|
+
const options = body.web_search_options;
|
|
477
|
+
return typeof options?.search_context_size === "string" ? options.search_context_size : null;
|
|
478
|
+
}
|
|
479
|
+
function webSearch(body) {
|
|
480
|
+
let present = false;
|
|
481
|
+
let maxUses = 0;
|
|
482
|
+
const types = /* @__PURE__ */ new Set();
|
|
483
|
+
if (Array.isArray(body.tools)) {
|
|
484
|
+
for (const tool of body.tools) {
|
|
485
|
+
if (typeof tool?.type !== "string" || !tool.type.startsWith("web_search")) continue;
|
|
486
|
+
present = true;
|
|
487
|
+
const max = tool.max_uses;
|
|
488
|
+
const uses = typeof max === "number" && Number.isSafeInteger(max) && max >= 0 ? max : null;
|
|
489
|
+
maxUses = maxUses === null || uses === null ? null : maxUses + uses;
|
|
490
|
+
types.add(typeof tool.search_type === "string" ? tool.search_type : "");
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (present) return { present, maxUses, searchType: types.size === 1 ? [...types][0] : DEAREST };
|
|
494
|
+
if (body.web_search_options !== void 0 && body.web_search_options !== null) {
|
|
495
|
+
return { present: true, maxUses: null, searchType: "" };
|
|
496
|
+
}
|
|
497
|
+
return { present: false, maxUses: 0, searchType: "" };
|
|
498
|
+
}
|
|
499
|
+
function characterCount(body) {
|
|
500
|
+
return typeof body.input === "string" ? [...body.input].length : 0;
|
|
501
|
+
}
|
|
502
|
+
var READERS = {
|
|
503
|
+
request: {
|
|
504
|
+
variant: (body) => contextSize(body) ?? "",
|
|
505
|
+
// Perplexity reports `search_context_size` in its usage, so the size that
|
|
506
|
+
// was billed is the host's answer and not the body's ask.
|
|
507
|
+
hostChoosesVariant: true,
|
|
508
|
+
bound: () => 1,
|
|
509
|
+
settle: (body, usage) => ({ count: 1, variant: usage.search_context_size ?? contextSize(body) ?? "" })
|
|
510
|
+
},
|
|
511
|
+
search: {
|
|
512
|
+
variant: (body) => webSearch(body).searchType,
|
|
513
|
+
bound: (body, model) => {
|
|
514
|
+
const search = webSearch(body);
|
|
515
|
+
return search.present && !searchBoundedByHost(model) ? null : search.maxUses;
|
|
516
|
+
},
|
|
517
|
+
settle: (body, usage) => {
|
|
518
|
+
if (usage.web_search_requests !== void 0) return { count: usage.web_search_requests };
|
|
519
|
+
return webSearch(body).present ? null : { count: 0 };
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
character: {
|
|
523
|
+
variant: () => "",
|
|
524
|
+
bound: (body) => characterCount(body),
|
|
525
|
+
settle: (body) => ({ count: characterCount(body) })
|
|
526
|
+
},
|
|
527
|
+
image: {
|
|
528
|
+
variant: (body) => {
|
|
529
|
+
const asked = imageRequestOf(body);
|
|
530
|
+
return asked === null ? "" : `${asked.size}/${asked.quality}`;
|
|
531
|
+
},
|
|
532
|
+
bound: (body) => imageRequestOf(body)?.n ?? null,
|
|
533
|
+
settle: (body) => {
|
|
534
|
+
const asked = imageRequestOf(body);
|
|
535
|
+
return asked === null ? null : { count: asked.n };
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
var READ_COMPONENTS = new Set(Object.keys(READERS));
|
|
540
|
+
async function componentRowsInForceFor(db, models, at) {
|
|
541
|
+
const rows = /* @__PURE__ */ new Map();
|
|
542
|
+
if (models.length === 0) return rows;
|
|
543
|
+
const day = at.toISOString().slice(0, 10);
|
|
544
|
+
const placeholders = models.map(() => "?").join(", ");
|
|
545
|
+
const { results } = await db.prepare(
|
|
546
|
+
`SELECT c.model, c.component, c.variant, c.micros, c.per
|
|
547
|
+
FROM model_components c
|
|
548
|
+
JOIN (SELECT model, component, max(effective_from) AS effective_from
|
|
549
|
+
FROM model_components
|
|
550
|
+
WHERE effective_from <= ?1 AND model IN (${placeholders})
|
|
551
|
+
GROUP BY model, component) latest
|
|
552
|
+
ON latest.model = c.model AND latest.component = c.component
|
|
553
|
+
AND latest.effective_from = c.effective_from
|
|
554
|
+
ORDER BY c.model, c.component, c.variant`
|
|
555
|
+
).bind(day, ...models).all();
|
|
556
|
+
for (const { model, ...row } of results) {
|
|
557
|
+
const list = rows.get(model);
|
|
558
|
+
if (list === void 0) rows.set(model, [row]);
|
|
559
|
+
else list.push(row);
|
|
560
|
+
}
|
|
561
|
+
return rows;
|
|
562
|
+
}
|
|
563
|
+
async function componentRowsInForce(db, model, at) {
|
|
564
|
+
return (await componentRowsInForceFor(db, [model], at)).get(model) ?? [];
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// src/fallback.ts
|
|
568
|
+
var MAX_CHAIN = 3;
|
|
569
|
+
|
|
570
|
+
export {
|
|
571
|
+
MAX_CHAIN,
|
|
572
|
+
LANE_PREFIX,
|
|
573
|
+
CONTRIBUTOR_LANE_PREFIX,
|
|
574
|
+
SLUG
|
|
575
|
+
};
|