@latimer-woods-tech/llm 0.6.1 → 0.8.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.
- package/CHANGELOG.md +57 -0
- package/README.md +2 -1
- package/dist/index.d.mts +29 -2
- package/dist/index.mjs +48 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,62 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.1 — 2026-09-02
|
|
4
|
+
|
|
5
|
+
### Fixed — Qwen 27B visible-output contract
|
|
6
|
+
|
|
7
|
+
- Workbench requests now send OpenAI-compatible `reasoning_effort: "none"`.
|
|
8
|
+
Ollama otherwise enables Qwen thinking by default, so a short request could
|
|
9
|
+
return HTTP 200 while exhausting `max_tokens` in hidden reasoning and leaving
|
|
10
|
+
`message.content` empty. The existing empty-content guard remains the
|
|
11
|
+
fail-closed backstop.
|
|
12
|
+
|
|
13
|
+
## 0.8.0 — 2026-08-23
|
|
14
|
+
|
|
15
|
+
### Added (#4204 — the counter half of "LLM chain DEGRADED is unreachable without reading logs")
|
|
16
|
+
|
|
17
|
+
- `LLMResult.degraded: boolean` — true when a fallback provider served instead
|
|
18
|
+
of the tier's intended primary. Computed from `legIndex > 0` at the point the
|
|
19
|
+
route is walked (`[route.primary, route.fallback]`), so it is a direct read
|
|
20
|
+
of "servedBy != intended" rather than a proxy. `attempts > 1` already
|
|
21
|
+
existed but conflates a same-provider retry with an actual fallback to a
|
|
22
|
+
different provider — `degraded` is the distinct signal.
|
|
23
|
+
- Threaded into `LLMRecordRow.degraded` so every metering consumer (starting
|
|
24
|
+
with `@latimer-woods-tech/llm-meter`) gets it automatically via the existing
|
|
25
|
+
`onRecord` callback — no new dependency inside this package, no Sentry/
|
|
26
|
+
PostHog call added here.
|
|
27
|
+
- `completionStream()`'s direct-Anthropic path sets `degraded` from whether
|
|
28
|
+
the resolved `streamLeg` is `route.fallback` (the grok-no-key special case);
|
|
29
|
+
every other stream branch already delegates to `complete()` and inherits its
|
|
30
|
+
`degraded` value.
|
|
31
|
+
- No routing/fallback decision logic changed — purely additive observability.
|
|
32
|
+
|
|
33
|
+
## 0.7.0 — 2026-08-23
|
|
34
|
+
|
|
35
|
+
### Fixed (metering seam)
|
|
36
|
+
|
|
37
|
+
- `LLMOptions` carries `project`/`actor`/`runId`/`workload` at the TOP level and
|
|
38
|
+
again inside `ledger`. Both shapes typecheck, but only `ledger` was read by the
|
|
39
|
+
`onRecord` metering callback, so choosing the wrong one silently produced logs
|
|
40
|
+
and no ledger row. Measured across the estate on 2026-08-23: **8 of 8** call
|
|
41
|
+
sites supplying metering context used the top-level shape and **0** used
|
|
42
|
+
`ledger`. A 0% adoption rate on one of two identical-looking APIs is a design
|
|
43
|
+
defect, not eight independent mistakes.
|
|
44
|
+
- New exported `resolveLedgerContext(opts)` reconciles the two: an explicit
|
|
45
|
+
`ledger` always wins, otherwise a context is derived from top-level
|
|
46
|
+
`project` + `actor`. **Both** are required — a partial context would write a
|
|
47
|
+
row that cannot be attributed, which is worse than no row.
|
|
48
|
+
- No behaviour change for callers already passing `ledger`.
|
|
49
|
+
|
|
50
|
+
### Added (cost observability)
|
|
51
|
+
|
|
52
|
+
- `llm.complete` / `llm.completionStream` log lines now carry **`tierExplicit`**.
|
|
53
|
+
`false` means no caller named a tier, so the call took the package default —
|
|
54
|
+
`balanced`, i.e. claude-sonnet-5 at $3/$15 per 1M, the second most expensive
|
|
55
|
+
of five tiers. Selecting the expensive route by silence is invisible at the
|
|
56
|
+
call site: `apps/video-studio/scripts/generate-script.mjs` made SIX untiered
|
|
57
|
+
`complete()` calls while `CLAUDE.md` described that step as Haiku ($1/$5).
|
|
58
|
+
A defaulted tier is now greppable in logs. No behaviour change.
|
|
59
|
+
|
|
3
60
|
## 0.6.1 — 2026-08-18
|
|
4
61
|
|
|
5
62
|
### Fixed — ships the routing and sampling fixes from #3946, which never reached npm
|
package/README.md
CHANGED
|
@@ -25,7 +25,8 @@ falls through to Grok, never directly to Anthropic.
|
|
|
25
25
|
|
|
26
26
|
Set `LLM_LOCAL_WORKBENCH=true` to make `qwen3.6:27b` the `workbench` primary. The 27B model
|
|
27
27
|
retains the OpenAI-compatible `custom-local-gpu/v1/chat/completions` route and normalized tool
|
|
28
|
-
protocol,
|
|
28
|
+
protocol, sends `reasoning_effort: "none"` so bounded jobs cannot return a blank answer after
|
|
29
|
+
spending their budget on hidden reasoning, and retains DeepSeek as its independent fallback. Both routes require `GPU_LLM_API_TOKEN`;
|
|
29
30
|
the Cloudflare Access client id and secret are forwarded when supplied.
|
|
30
31
|
|
|
31
32
|
## Usage
|
package/dist/index.d.mts
CHANGED
|
@@ -255,6 +255,8 @@ interface LLMResult {
|
|
|
255
255
|
* (non-empty) when `stopReason === 'tool_use'`.
|
|
256
256
|
*/
|
|
257
257
|
toolCalls?: LLMToolCall[];
|
|
258
|
+
/** True when a fallback provider served instead of the tier's intended primary — see #4204. */
|
|
259
|
+
degraded: boolean;
|
|
258
260
|
}
|
|
259
261
|
/**
|
|
260
262
|
* Environment bindings required by {@link complete}.
|
|
@@ -351,6 +353,8 @@ interface LLMRecordRow extends LLMRecordContext {
|
|
|
351
353
|
model: string;
|
|
352
354
|
provider: LLMProvider;
|
|
353
355
|
tier: LLMTier;
|
|
356
|
+
/** True when a fallback provider served instead of the tier's intended primary — see #4204. */
|
|
357
|
+
degraded: boolean;
|
|
354
358
|
inputTokens: number;
|
|
355
359
|
outputTokens: number;
|
|
356
360
|
cacheReadTokens: number;
|
|
@@ -359,6 +363,29 @@ interface LLMRecordRow extends LLMRecordContext {
|
|
|
359
363
|
costUsd: number;
|
|
360
364
|
yyyyMm: string;
|
|
361
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Resolve the metering context for a call.
|
|
368
|
+
*
|
|
369
|
+
* `LLMOptions` carries the same four field names in TWO places: top-level
|
|
370
|
+
* `project`/`actor`/`runId`/`workload` (documented for logs and cost-policy
|
|
371
|
+
* call sites) and the nested {@link LLMOptions.ledger}, which is the ONLY one
|
|
372
|
+
* the `onRecord` metering callback reads. Both shapes typecheck, so picking
|
|
373
|
+
* the wrong one is silent — you get logging and no ledger row.
|
|
374
|
+
*
|
|
375
|
+
* Every caller picked the wrong one. Measured across the estate 2026-08-23:
|
|
376
|
+
* **8 of 8** call sites that supply metering context set it at the top level
|
|
377
|
+
* (`admin-studio` test-analyst + ai, `daily-brief` wisdom + insights,
|
|
378
|
+
* `inbound-oracle`, `supervisor` reflect, `lead-gen`, `linkedin-publisher`)
|
|
379
|
+
* and **0** use `ledger`. A 0% adoption rate on one of two identical-looking
|
|
380
|
+
* APIs is a design defect, not eight independent mistakes — so the fix belongs
|
|
381
|
+
* here rather than in eight call sites that already read as correct.
|
|
382
|
+
*
|
|
383
|
+
* Explicit `ledger` always wins; the top-level fields are a fallback. A call
|
|
384
|
+
* needs BOTH `project` and `actor` to be meterable, because those two are the
|
|
385
|
+
* required columns on the ledger row — a partial context would write a row
|
|
386
|
+
* that cannot be attributed, which is worse than no row.
|
|
387
|
+
*/
|
|
388
|
+
declare function resolveLedgerContext(opts: LLMOptions): LLMRecordContext | undefined;
|
|
362
389
|
/**
|
|
363
390
|
* Optional dependencies for {@link complete}.
|
|
364
391
|
*/
|
|
@@ -383,7 +410,7 @@ declare const MODELS: {
|
|
|
383
410
|
readonly smart: "gemini-2.5-flash";
|
|
384
411
|
};
|
|
385
412
|
readonly groq: {
|
|
386
|
-
readonly verifier: "
|
|
413
|
+
readonly verifier: "openai/gpt-oss-120b";
|
|
387
414
|
};
|
|
388
415
|
readonly grok: {
|
|
389
416
|
readonly fast: "grok-4.3";
|
|
@@ -504,4 +531,4 @@ declare function completionStream(messages: LLMMessage[], env: LLMEnv, opts?: LL
|
|
|
504
531
|
*/
|
|
505
532
|
declare function assertGrounding(response: string, sources: string[]): boolean;
|
|
506
533
|
|
|
507
|
-
export { type AiBinding, BASE_BACKOFF_MS, type CostKvStore, DEFAULT_EMBEDDING_MODEL, type EmbedResult, type EmbeddingModel, type LLMContentBlock, type LLMDeps, type LLMEnv, type LLMMessage, type LLMOptions, type LLMProvider, type LLMRecordContext, type LLMRecordRow, type LLMResult, type LLMTier, type LLMTool, type LLMToolCall, LOCAL_EMBEDDING_MODEL, type LocalEmbedEnv, MODELS, MODEL_PRICE_PER_1M, PROVIDER_COOLDOWN_MS, assertGrounding, clearGcpTokenCache, clearProviderCooldown, complete, completionStream, embed, embedLocal, isProviderCoolingDown, markProviderCoolingDown, mintGcpAccessToken, serviceAccountProjectId };
|
|
534
|
+
export { type AiBinding, BASE_BACKOFF_MS, type CostKvStore, DEFAULT_EMBEDDING_MODEL, type EmbedResult, type EmbeddingModel, type LLMContentBlock, type LLMDeps, type LLMEnv, type LLMMessage, type LLMOptions, type LLMProvider, type LLMRecordContext, type LLMRecordRow, type LLMResult, type LLMTier, type LLMTool, type LLMToolCall, LOCAL_EMBEDDING_MODEL, type LocalEmbedEnv, MODELS, MODEL_PRICE_PER_1M, PROVIDER_COOLDOWN_MS, assertGrounding, clearGcpTokenCache, clearProviderCooldown, complete, completionStream, embed, embedLocal, isProviderCoolingDown, markProviderCoolingDown, mintGcpAccessToken, resolveLedgerContext, serviceAccountProjectId };
|
package/dist/index.mjs
CHANGED
|
@@ -199,6 +199,14 @@ function systemText(opts, messages) {
|
|
|
199
199
|
const c = messages.find((m) => m.role === "system")?.content;
|
|
200
200
|
return c === void 0 ? void 0 : contentToText(c);
|
|
201
201
|
}
|
|
202
|
+
function resolveLedgerContext(opts) {
|
|
203
|
+
if (opts.ledger) return opts.ledger;
|
|
204
|
+
if (!opts.project || !opts.actor) return void 0;
|
|
205
|
+
const ctx = { project: opts.project, actor: opts.actor };
|
|
206
|
+
if (opts.runId !== void 0) ctx.runId = opts.runId;
|
|
207
|
+
if (opts.workload !== void 0) ctx.workload = opts.workload;
|
|
208
|
+
return ctx;
|
|
209
|
+
}
|
|
202
210
|
var MODELS = {
|
|
203
211
|
anthropic: {
|
|
204
212
|
// `claude-haiku-4-20250514` was never a model Anthropic served — it 404s
|
|
@@ -232,12 +240,13 @@ var MODELS = {
|
|
|
232
240
|
smart: "gemini-2.5-flash"
|
|
233
241
|
},
|
|
234
242
|
groq: {
|
|
235
|
-
// `llama-
|
|
236
|
-
// 404'd (the tier has no
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
|
|
243
|
+
// Groq deprecated `llama-3.3-70b-versatile` on 2026-08-16 — every
|
|
244
|
+
// `verifier` call 404'd `model_not_found` from then on (the tier has no
|
|
245
|
+
// fallback, so it was hard-broken; second time this tier has died to a
|
|
246
|
+
// model retirement). `openai/gpt-oss-120b` is Groq's recommended
|
|
247
|
+
// migration target and is served to our org (verified against /v1/models
|
|
248
|
+
// and a live completion, 2026-08-23).
|
|
249
|
+
verifier: "openai/gpt-oss-120b"
|
|
241
250
|
},
|
|
242
251
|
grok: {
|
|
243
252
|
fast: "grok-4.3"
|
|
@@ -313,8 +322,11 @@ var MODEL_PRICE_PER_1M = {
|
|
|
313
322
|
"gemini-2.5-flash": { input: 0.3, output: 2.5, cacheRead: 0.075, cacheWrite: 0.3 },
|
|
314
323
|
// Gemini 2.5 Pro — retained for historical ledger rows (was the routed model).
|
|
315
324
|
"gemini-2.5-pro": { input: 1.25, output: 10, cacheRead: 0.31, cacheWrite: 4.5 },
|
|
316
|
-
// Groq
|
|
317
|
-
//
|
|
325
|
+
// Groq GPT-OSS 120B (`verifier` tier). Groq bills cached input at $0.075/1M
|
|
326
|
+
// with no separate cache-write charge (implicit caching), so cacheWrite is 0.
|
|
327
|
+
"openai/gpt-oss-120b": { input: 0.15, output: 0.6, cacheRead: 0.075, cacheWrite: 0 },
|
|
328
|
+
// Groq Llama 3.3 70B Versatile — retained for historical ledger rows
|
|
329
|
+
// (routed `verifier` until Groq retired the model on 2026-08-16).
|
|
318
330
|
"llama-3.3-70b-versatile": { input: 0.59, output: 0.79, cacheRead: 0, cacheWrite: 0 },
|
|
319
331
|
// Grok 4.3
|
|
320
332
|
"grok-4.3": { input: 1.25, output: 2.5, cacheRead: 0, cacheWrite: 0 },
|
|
@@ -656,6 +668,10 @@ function buildLocalRequest(model, messages, opts, env) {
|
|
|
656
668
|
headers,
|
|
657
669
|
body: JSON.stringify({
|
|
658
670
|
model,
|
|
671
|
+
// Ollama enables Qwen thinking by default. Without this OpenAI-
|
|
672
|
+
// compatible control, short bounded calls can spend every token in the
|
|
673
|
+
// hidden `reasoning` field and return HTTP 200 with empty visible text.
|
|
674
|
+
reasoning_effort: "none",
|
|
659
675
|
max_tokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
660
676
|
temperature: opts.temperature ?? DEFAULT_TEMPERATURE,
|
|
661
677
|
messages: toOpenAiMessages(messages, sys),
|
|
@@ -1015,6 +1031,12 @@ async function complete(messages, env, opts = {}, deps = {}) {
|
|
|
1015
1031
|
throw { provider: leg.provider, status: 200, retryable: false, message: "empty content" };
|
|
1016
1032
|
}
|
|
1017
1033
|
logger?.info?.("llm.complete", {
|
|
1034
|
+
// `false` means NO caller named a tier, so this call took the
|
|
1035
|
+
// package default (`balanced` — claude-sonnet-5, the second most
|
|
1036
|
+
// expensive route). Emitted so a defaulted tier is greppable rather
|
|
1037
|
+
// than invisible: an expensive route chosen by silence is the thing
|
|
1038
|
+
// that made the video pipeline bill Sonnet while its docs said Haiku.
|
|
1039
|
+
tierExplicit: opts.tier !== void 0,
|
|
1018
1040
|
provider: leg.provider,
|
|
1019
1041
|
model: leg.model,
|
|
1020
1042
|
tier,
|
|
@@ -1025,6 +1047,7 @@ async function complete(messages, env, opts = {}, deps = {}) {
|
|
|
1025
1047
|
actor: opts.actor,
|
|
1026
1048
|
workload: opts.workload
|
|
1027
1049
|
});
|
|
1050
|
+
const degraded = legIndex > 0;
|
|
1028
1051
|
const llmResult = {
|
|
1029
1052
|
content: result.parsed.content,
|
|
1030
1053
|
provider: leg.provider,
|
|
@@ -1040,7 +1063,8 @@ async function complete(messages, env, opts = {}, deps = {}) {
|
|
|
1040
1063
|
attempts: result.attempts,
|
|
1041
1064
|
gatewayRequestId: result.gatewayRequestId,
|
|
1042
1065
|
stopReason: result.parsed.stopReason,
|
|
1043
|
-
toolCalls: result.parsed.toolCalls
|
|
1066
|
+
toolCalls: result.parsed.toolCalls,
|
|
1067
|
+
degraded
|
|
1044
1068
|
};
|
|
1045
1069
|
const costUsd = estimateCostUsd(llmResult.tokens, llmResult.model);
|
|
1046
1070
|
if (opts.maxCostUsd !== void 0 && costUsd > opts.maxCostUsd) {
|
|
@@ -1059,12 +1083,14 @@ async function complete(messages, env, opts = {}, deps = {}) {
|
|
|
1059
1083
|
if (kv && (opts.dailyCapUsd !== void 0 || opts.monthlyCapUsd !== void 0)) {
|
|
1060
1084
|
await recordOrgCostUsage(kv, todayKey, monthKey, costUsd, opts);
|
|
1061
1085
|
}
|
|
1062
|
-
|
|
1086
|
+
const ledgerCtx = resolveLedgerContext(opts);
|
|
1087
|
+
if (deps.onRecord && ledgerCtx) {
|
|
1063
1088
|
const row = {
|
|
1064
|
-
...
|
|
1089
|
+
...ledgerCtx,
|
|
1065
1090
|
model: llmResult.model,
|
|
1066
1091
|
provider: llmResult.provider,
|
|
1067
1092
|
tier: llmResult.tier,
|
|
1093
|
+
degraded: llmResult.degraded,
|
|
1068
1094
|
inputTokens: llmResult.tokens.input,
|
|
1069
1095
|
outputTokens: llmResult.tokens.output,
|
|
1070
1096
|
cacheReadTokens: llmResult.tokens.cacheRead ?? 0,
|
|
@@ -1244,6 +1270,12 @@ async function* completionStream(messages, env, opts = {}) {
|
|
|
1244
1270
|
}
|
|
1245
1271
|
clearProviderCooldown(streamLeg.provider);
|
|
1246
1272
|
logger?.info?.("llm.completionStream", {
|
|
1273
|
+
// `false` means NO caller named a tier, so this call took the
|
|
1274
|
+
// package default (`balanced` — claude-sonnet-5, the second most
|
|
1275
|
+
// expensive route). Emitted so a defaulted tier is greppable rather
|
|
1276
|
+
// than invisible: an expensive route chosen by silence is the thing
|
|
1277
|
+
// that made the video pipeline bill Sonnet while its docs said Haiku.
|
|
1278
|
+
tierExplicit: opts.tier !== void 0,
|
|
1247
1279
|
provider: streamLeg.provider,
|
|
1248
1280
|
model: streamLeg.model,
|
|
1249
1281
|
tier,
|
|
@@ -1268,7 +1300,10 @@ async function* completionStream(messages, env, opts = {}) {
|
|
|
1268
1300
|
attempts: 1,
|
|
1269
1301
|
gatewayRequestId,
|
|
1270
1302
|
stopReason: streamStopReason,
|
|
1271
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : void 0
|
|
1303
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
1304
|
+
// streamLeg is only ever route.fallback in the grok-no-key special case above —
|
|
1305
|
+
// same "servedBy != intended" signal as the non-streaming path, see #4204.
|
|
1306
|
+
degraded: streamLeg !== route.primary
|
|
1272
1307
|
};
|
|
1273
1308
|
}
|
|
1274
1309
|
function assertGrounding(response, sources) {
|
|
@@ -1308,6 +1343,7 @@ export {
|
|
|
1308
1343
|
isProviderCoolingDown,
|
|
1309
1344
|
markProviderCoolingDown,
|
|
1310
1345
|
mintGcpAccessToken,
|
|
1346
|
+
resolveLedgerContext,
|
|
1311
1347
|
serviceAccountProjectId
|
|
1312
1348
|
};
|
|
1313
1349
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/gcp-token.ts","../src/embed.ts"],"sourcesContent":["import {\n InternalError,\n RateLimitError,\n ValidationError,\n toErrorResponse,\n type FactoryResponse,\n} from '@latimer-woods-tech/errors';\nimport type { Logger } from '@latimer-woods-tech/logger';\nimport { fetchAdcAccessToken, mintGcpAccessToken, serviceAccountProjectId } from './gcp-token.js';\n\n/**\n * A tool the model may call. `parameters` is a JSON Schema object describing\n * the tool's input. Provider-agnostic; normalized per provider at request time.\n */\nexport interface LLMTool {\n name: string;\n description?: string;\n /** JSON Schema for the tool's input arguments. */\n parameters: Record<string, unknown>;\n}\n\n/**\n * A tool invocation requested by the model, normalized across providers.\n */\nexport interface LLMToolCall {\n /** Provider-assigned call id; echo it back in the matching tool_result. */\n id: string;\n name: string;\n /** Parsed argument object the model passed to the tool. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Structured content block for tool-calling conversations. The field shapes\n * mirror the Anthropic Messages wire format so they pass through unchanged.\n */\nexport type LLMContentBlock =\n | { type: 'text'; text: string }\n | { type: 'tool_use'; id: string; name: string; input: Record<string, unknown> }\n | { type: 'tool_result'; tool_use_id: string; content: string; is_error?: boolean };\n\n/**\n * Single chat message exchanged with an LLM provider.\n *\n * `content` is a plain string in the common case. For tool-calling\n * conversations it may be an array of {@link LLMContentBlock}s (e.g. an\n * assistant turn carrying `tool_use` blocks, or a user turn carrying\n * `tool_result` blocks). Providers that don't support tool-calling receive\n * the text projection of the content (see `contentToText`).\n */\nexport interface LLMMessage {\n role: 'user' | 'assistant' | 'system';\n content: string | LLMContentBlock[];\n}\n\n/**\n * Flattens message content to plain text for providers/paths that only handle\n * strings. `tool_use` blocks contribute nothing; `tool_result` blocks\n * contribute their textual content.\n */\nfunction contentToText(content: string | LLMContentBlock[]): string {\n if (typeof content === 'string') return content;\n return content\n .map((b) => (b.type === 'text' ? b.text : b.type === 'tool_result' ? b.content : ''))\n .join('');\n}\n\n/**\n * Resolves the system prompt: explicit `opts.system` wins, else the first\n * `system` message, flattened to text. Returns `undefined` when neither is set.\n */\nfunction systemText(opts: LLMOptions, messages: LLMMessage[]): string | undefined {\n if (opts.system !== undefined) return opts.system;\n const c = messages.find((m) => m.role === 'system')?.content;\n return c === undefined ? undefined : contentToText(c);\n}\n\n/**\n * Quality tier selected by the caller. Routing is workload-split:\n * - `fast` → Grok 4.3 with Anthropic Haiku fallback (routine drafts/small jobs)\n * - `balanced` → Anthropic Sonnet (default)\n * - `smart` → Anthropic Opus OR Gemini 2.5 Flash if input is long-context (>150k tokens estimated)\n * - `verifier` → Groq Llama (cheap second opinion; only used from verifier code path)\n * - `workbench` → DeepSeek Chat with Groq fallback (boring, reviewable, non-sensitive batch work)\n */\nexport type LLMTier = 'fast' | 'balanced' | 'smart' | 'verifier' | 'workbench';\n\n/**\n * Options that influence LLM completion behaviour.\n */\nexport interface LLMOptions {\n /** Quality tier; see {@link LLMTier}. Defaults to `balanced`. */\n tier?: LLMTier;\n /** Explicit model override. Takes precedence over tier. */\n model?: string;\n maxTokens?: number;\n temperature?: number;\n system?: string;\n /** Token budget above which we force long-context routing (Gemini). */\n longContextThreshold?: number;\n /** Per-call cancellation signal. Aborts the in-flight provider request. */\n signal?: AbortSignal;\n /** Optional run identifier stamped on ledger rows + logs. */\n runId?: string;\n /** Optional project identifier stamped on ledger rows + logs. */\n project?: string;\n /** Optional actor identifier (supervisor / worker / human). */\n actor?: string;\n /** Optional workload label used in logs and cost-policy call sites. */\n workload?: string;\n /** Grok reasoning effort. Defaults to `none` for cost-controlled fast/draft calls. */\n reasoningEffort?: 'none' | 'low' | 'medium' | 'high';\n /** Anthropic prompt-cache control. Defaults to `true` for `system` prompts ≥ 1024 tokens. */\n promptCache?: boolean;\n /**\n * Maximum estimated cost in USD for this completion.\n * This cap is enforced after the provider returns because it uses actual\n * response token counts to compute the final cost.\n * If the post-call estimated cost exceeds this cap, `complete` returns a\n * {@link RateLimitError} with code `LLM_COST_CAP_EXCEEDED` and\n * `completionStream` throws the same error.\n * Pricing is based on {@link MODEL_PRICE_PER_1M}; unknown models default to\n * Opus rates (conservative upper bound).\n */\n maxCostUsd?: number;\n /**\n * Org-level daily cost cap in USD. Requires `env.LLM_COST_KV` to be set.\n * When today's cumulative spend read from KV is >= this value, `complete`\n * returns a {@link RateLimitError} with code `LLM_DAILY_CAP_EXCEEDED`\n * without making any provider call. After a successful call the daily\n * accumulator is updated in KV (TTL: 48 h).\n */\n dailyCapUsd?: number;\n /**\n * Org-level monthly cost cap in USD. Requires `env.LLM_COST_KV` to be set.\n * Same enforcement pattern as {@link dailyCapUsd} but keyed by YYYY-MM.\n * KV TTL: 40 days.\n */\n monthlyCapUsd?: number;\n /**\n * Metering context. When supplied and `deps.onRecord` is set, a {@link LLMRecordRow}\n * is emitted after every successful completion. Errors are swallowed.\n */\n ledger?: LLMRecordContext;\n /**\n * Tools the model may call. When present, routing **fails closed** to\n * tool-capable providers — failover never falls back to a provider that\n * can't honour the tool schema. See {@link LLMResult.toolCalls}.\n */\n tools?: LLMTool[];\n /**\n * Tool-selection policy. `'auto'` (default when `tools` is set) lets the\n * model decide; `'none'` forbids tool use; `{ name }` forces a specific tool.\n */\n toolChoice?: 'auto' | 'none' | { name: string };\n}\n\n/**\n * Provider that produced an LLM response.\n */\nexport type LLMProvider = 'anthropic' | 'gemini' | 'groq' | 'grok' | 'deepseek' | 'local';\n\n/**\n * Result returned by a successful completion.\n */\nexport interface LLMResult {\n content: string;\n provider: LLMProvider;\n model: string;\n tier: LLMTier;\n tokens: { input: number; output: number; cacheRead?: number; cacheWrite?: number };\n latency: number;\n /** Number of attempts before success (1 = primary succeeded). */\n attempts: number;\n /** Monotonic request id from AI Gateway, if present in headers. */\n gatewayRequestId?: string;\n /**\n * Why generation stopped, normalized across providers. `'tool_use'` means\n * the model is requesting one or more tool calls (see {@link toolCalls}).\n */\n stopReason?: 'end' | 'tool_use' | 'max_tokens' | 'other';\n /**\n * Tool calls the model requested, normalized across providers. Present\n * (non-empty) when `stopReason === 'tool_use'`.\n */\n toolCalls?: LLMToolCall[];\n}\n\n/**\n * Environment bindings required by {@link complete}.\n *\n * `AI_GATEWAY_BASE_URL` is REQUIRED in 0.3.0. All provider calls flow through the\n * Cloudflare AI Gateway for unified logging, rate limiting, and cost telemetry.\n * In test/dev the caller may pass a custom fetch impl that short-circuits this.\n */\nexport interface LLMEnv {\n AI_GATEWAY_BASE_URL: string;\n ANTHROPIC_API_KEY: string;\n GROQ_API_KEY: string;\n /** Optional — only required for `{ tier: 'workbench' }` or `deepseek-*` model overrides. */\n DEEPSEEK_API_KEY?: string;\n /** Optional — only required when caller passes `{ model: 'grok-*' }` override. */\n GROK_API_KEY?: string;\n /**\n * Cost optimization: when true, `fast`-tier calls route to the self-hosted GPU\n * (qwen3:8b via the `custom-local-fast-gpu` AI Gateway provider) FIRST, with the normal\n * fast route (Grok→Haiku) as automatic fallback on any error. Off by default →\n * fully dormant. Requires {@link LLMEnv.GPU_LLM_API_TOKEN}.\n */\n LLM_LOCAL_FIRST?: boolean;\n /**\n * Agentic-work opt-in: route the `workbench` tier to local Qwen 27B first,\n * with DeepSeek as the independent cloud fallback.\n */\n LLM_LOCAL_WORKBENCH?: boolean;\n /** Bearer for the local GPU proxy (`GPU_LLM_API_TOKEN` in Secret Manager). Required to enable local. */\n GPU_LLM_API_TOKEN?: string;\n /** Cloudflare Access service-token id for the local GPU app; forwarded by the gateway to the origin. */\n GPU_LLM_ACCESS_CLIENT_ID?: string;\n /** Cloudflare Access service-token secret for the local GPU app. */\n GPU_LLM_ACCESS_CLIENT_SECRET?: string;\n /**\n * GCP service-account JSON key (base64-encoded or raw) with `roles/aiplatform.user`.\n * The credential for the `gemini` leg in environments WITHOUT a metadata server\n * (Cloudflare Workers): the package exchanges it for a fresh access token per\n * isolate and caches it (see `./gcp-token.ts`).\n *\n * Optional. When neither this nor {@link LLMEnv.VERTEX_ACCESS_TOKEN} is set, the\n * leg falls back to Application Default Credentials — the GCP metadata server's\n * ambient service-account token (keyless; the default on Cloud Run / GCE). Off\n * GCP with no static credential, the metadata probe fails and the chain falls\n * through to its next leg.\n */\n GCP_SA_KEY?: string;\n /**\n * Pre-minted Google Cloud access token with `aiplatform.endpoints.predict`.\n * Explicit override, used only when {@link LLMEnv.GCP_SA_KEY} is absent and\n * before ADC is attempted. Valid for ~1 hour from minting, so it cannot be a\n * durable Worker secret.\n */\n VERTEX_ACCESS_TOKEN?: string;\n /** Vertex project. Defaults to the `GCP_SA_KEY`'s own `project_id`. */\n VERTEX_PROJECT?: string;\n /** Vertex location. Defaults to `us-central1`. */\n VERTEX_LOCATION?: string;\n /**\n * Optional KV store for org-level daily/monthly cost tracking and enforcement.\n * When provided alongside {@link LLMOptions.dailyCapUsd} or {@link LLMOptions.monthlyCapUsd},\n * `complete` will block calls that would exceed the declared cap.\n * Any KV-like store satisfying `get`/`put` works (e.g. Cloudflare KV, in-memory stub).\n */\n LLM_COST_KV?: CostKvStore;\n}\n\n/**\n * Minimal KV store interface for org-level LLM cost tracking.\n * Cloudflare KV satisfies this. An in-memory stub is sufficient for tests.\n */\nexport interface CostKvStore {\n get(key: string): Promise<string | null>;\n put(key: string, value: string, options?: { expirationTtl?: number }): Promise<void>;\n}\n\n/**\n * Caller-supplied context stamped on every metering row.\n * Mirrors the `LLMRecordContext` in `@latimer-woods-tech/llm-meter`; kept inline\n * to avoid a circular dependency (llm-meter imports llm).\n */\nexport interface LLMRecordContext {\n project: string;\n actor: string;\n runId?: string;\n workload?: string;\n tenantId?: string;\n}\n\n/**\n * Row shape passed to the optional {@link LLMDeps.onRecord} callback.\n * Callers can wire this directly to `recordCall` from `@latimer-woods-tech/llm-meter`.\n */\nexport interface LLMRecordRow extends LLMRecordContext {\n model: string;\n provider: LLMProvider;\n tier: LLMTier;\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n latencyMs: number;\n costUsd: number;\n yyyyMm: string;\n}\n\n/**\n * Optional dependencies for {@link complete}.\n */\nexport interface LLMDeps {\n fetch?: typeof fetch;\n logger?: Logger;\n now?: () => number;\n /**\n * Optional metering callback. Called after every successful completion.\n * Errors are swallowed so metering never blocks the caller.\n * Wire to `recordCall` from `@latimer-woods-tech/llm-meter`.\n */\n onRecord?: (row: LLMRecordRow) => Promise<void>;\n}\n\n// Model catalogue — keep in sync with docs/architecture/FACTORY_V1.md § LLM substrate.\nconst MODELS = {\n anthropic: {\n // `claude-haiku-4-20250514` was never a model Anthropic served — it 404s\n // (verified live against /v1/messages). Date suffixes are never appended to\n // an alias; the alias is `claude-haiku-4-5`, which resolves server-side to\n // `claude-haiku-4-5-20251001`. This is the `fast` tier's FALLBACK, so the\n // 404 stayed invisible for as long as the Grok primary held.\n fast: 'claude-haiku-4-5',\n // Current-generation Sonnet/Opus (2026-08 refresh). The previous pins —\n // `claude-sonnet-4-6` / `claude-opus-4-7` — were a generation behind, and\n // the opus-4-7 leg was HARD-BROKEN: Claude 4.7+ rejects `temperature`\n // (400 \"`temperature` is deprecated for this model\", verified live\n // 2026-08-14) and buildAnthropicRequest always sent one, so every `smart`\n // call 400'd on its Anthropic primary and silently served Gemini Flash.\n // Sonnet 5 / Opus 5 cost the same list price or less than the models they\n // replace ($3/$15, $5/$25 per 1M). The sampling/thinking request-shape\n // differences these models introduce are handled in buildAnthropicRequest.\n balanced: 'claude-sonnet-5',\n smart: 'claude-opus-5',\n },\n gemini: {\n // `gemini-2.5-flash`, not `-pro`: the leg's job here is a fast, reliable,\n // JSON-returning fallback. Gemini 2.5 *Pro* mandates a thinking budget of\n // ≥128 tokens that is drawn from `maxOutputTokens` and CANNOT be disabled\n // (thinkingBudget=0 is rejected). On the render-runner's large judge /\n // generation prompts — and any low-`maxTokens` call (headline uses 40) —\n // the thinking phase exhausts the whole budget and Vertex returns 200 with\n // an empty candidate (finishReason MAX_TOKENS, no text), which the router\n // treats as a failed leg. Flash supports `thinkingBudget: 0` (set in\n // buildGeminiRequest), so text is always emitted. Both are Vertex-served.\n smart: 'gemini-2.5-flash',\n },\n groq: {\n // `llama-4-maverick` was NOT a model Groq serves — every `verifier` call\n // 404'd (the tier has no fallback, so it was hard-broken), and it was also\n // the `workbench` fallback. Groq's llama-4 offering is `scout`, which is\n // blocked at our org level; `llama-3.3-70b-versatile` is served and\n // unblocked (verified against /v1/models and a live completion).\n verifier: 'llama-3.3-70b-versatile',\n },\n grok: {\n fast: 'grok-4.3',\n },\n deepseek: {\n workbench: 'deepseek-chat',\n },\n local: {\n // Self-hosted qwen3:8b on the dedicated fast GPU, reached through the\n // `custom-local-fast-gpu` AI Gateway provider's native Ollama chat route.\n fast: 'qwen3:8b',\n workbench: 'qwen3.6:27b',\n },\n} as const;\n\nconst DEFAULT_MAX_TOKENS = 1024;\nconst DEFAULT_TEMPERATURE = 0.7;\n/** Vertex region used when the caller does not pin one. */\nconst DEFAULT_VERTEX_LOCATION = 'us-central1';\nconst DEFAULT_LONG_CONTEXT_THRESHOLD = 150_000; // tokens\n\n// ─── Per-provider exponential backoff constants ────────────────────────────\n/** Base delay in ms for the first retry. */\nconst BACKOFF_BASE_MS = 500;\n/** Maximum backoff cap in ms. */\nconst BACKOFF_CAP_MS = 8_000;\n/** Max random jitter added to each backoff delay, in ms. */\nconst BACKOFF_JITTER_MAX_MS = 250;\n/** Maximum number of attempts per provider (1 initial + 2 retries). */\nconst PER_PROVIDER_MAX_ATTEMPTS = 3;\n\n// ─── Per-provider cooldown state (module-level) ────────────────────────────\n/**\n * Tracks when a provider's cooldown period expires.\n * Keyed by {@link LLMProvider}; value is the `Date.now()` epoch ms at which\n * the cooldown expires. Absent key means \"not cooling down\".\n */\nconst providerCooldownUntil: Map<LLMProvider, number> = new Map();\n\n/** Cooldown duration in ms after a provider exhausts all retries. */\nconst PROVIDER_COOLDOWN_MS = 30_000;\n\n/**\n * Returns `true` if the provider is currently in its cooldown window.\n * Uses the injected `now` function (or `Date.now`) for testability.\n */\nfunction isProviderCoolingDown(provider: LLMProvider, now: () => number = Date.now): boolean {\n const until = providerCooldownUntil.get(provider);\n if (until === undefined) return false;\n return now() < until;\n}\n\n/** Returns `YYYY-MM-DD` from a Unix timestamp (ms). Used for daily KV cost keys. */\nfunction isoDate(nowMs: number): string {\n return new Date(nowMs).toISOString().slice(0, 10);\n}\n\n/**\n * Record actual call spend in org-level daily/monthly KV buckets.\n * This is intentionally best-effort: Cloudflare KV does not provide an atomic\n * compare-and-swap, so concurrent requests can race and undercount spend.\n */\nasync function recordOrgCostUsage(\n kv: CostKvStore,\n todayKey: string,\n monthKey: string,\n costUsd: number,\n opts: LLMOptions,\n): Promise<void> {\n if (opts.dailyCapUsd !== undefined) {\n const raw = await kv.get(todayKey).catch(() => null);\n const spent = parseFloat(raw ?? '0');\n await kv.put(todayKey, String(spent + costUsd), { expirationTtl: 172_800 /* 48 h */ }).catch(() => undefined);\n }\n if (opts.monthlyCapUsd !== undefined) {\n const raw = await kv.get(monthKey).catch(() => null);\n const spent = parseFloat(raw ?? '0');\n await kv.put(monthKey, String(spent + costUsd), { expirationTtl: 3_456_000 /* 40 d */ }).catch(() => undefined);\n }\n}\n\n/**\n * USD cost per 1 million tokens for each model.\n * Source: Anthropic / Google / xAI pricing pages as of 2026-05.\n * Keep these model names in sync with the default routing constants in\n * {@link MODELS}; unknown models fall back to Opus rates (conservative upper bound).\n *\n * CANONICAL pricing source for the platform. `@latimer-woods-tech/llm-meter`\n * derives its cents-denominated rates from this table and a drift-guard test\n * there fails CI if they diverge — make all rate changes here.\n */\nexport const MODEL_PRICE_PER_1M: Record<string, { input: number; output: number; cacheRead: number; cacheWrite: number }> = {\n // Anthropic Haiku 4.5 — `claude-haiku-4-5` is the routed alias; the dated id\n // is what the API echoes back in `response.model`, so both must price.\n // (Rates corrected 2026-08: the previous $0.80/$4.00 rows UNDERSTATED the\n // actual $1/$5 list price.)\n 'claude-haiku-4-5': { input: 1.00, output: 5.00, cacheRead: 0.10, cacheWrite: 1.25 },\n 'claude-haiku-4-5-20251001': { input: 1.00, output: 5.00, cacheRead: 0.10, cacheWrite: 1.25 },\n // Anthropic Sonnet 4\n 'claude-sonnet-4-20250514': { input: 3.00, output: 15.00, cacheRead: 0.30, cacheWrite: 3.75 },\n 'claude-sonnet-4-6': { input: 3.00, output: 15.00, cacheRead: 0.30, cacheWrite: 3.75 },\n // Anthropic Sonnet 5 — the routed `balanced` primary. List $3/$15; intro\n // pricing ($2/$10) runs through 2026-08-31 — priced at list here, the\n // conservative upper bound (table convention).\n 'claude-sonnet-5': { input: 3.00, output: 15.00, cacheRead: 0.30, cacheWrite: 3.75 },\n // Anthropic Opus 4 (dated id) — genuinely the $15/$75 era.\n 'claude-opus-4-20250514': { input: 15.00, output: 75.00, cacheRead: 1.50, cacheWrite: 18.75 },\n // Opus 4.7 was NEVER $15/$75 — it launched at $5/$25. The old row copied the\n // Opus 4 rate and overstated every smart-tier ledger entry 3x.\n 'claude-opus-4-7': { input: 5.00, output: 25.00, cacheRead: 0.50, cacheWrite: 6.25 },\n // Anthropic Opus 5 — the routed `smart` primary. Same $5/$25 as Opus 4.8/4.7.\n 'claude-opus-5': { input: 5.00, output: 25.00, cacheRead: 0.50, cacheWrite: 6.25 },\n // Gemini 2.5 Flash — the routed `smart`/long-context model (JSON fallback leg).\n 'gemini-2.5-flash': { input: 0.30, output: 2.50, cacheRead: 0.075, cacheWrite: 0.30 },\n // Gemini 2.5 Pro — retained for historical ledger rows (was the routed model).\n 'gemini-2.5-pro': { input: 1.25, output: 10.00, cacheRead: 0.31, cacheWrite: 4.50 },\n // Groq Llama 3.3 70B Versatile (`verifier` tier). Groq has no prompt caching,\n // so cache rates are 0.00 — same convention as grok-4.3 below.\n 'llama-3.3-70b-versatile': { input: 0.59, output: 0.79, cacheRead: 0.00, cacheWrite: 0.00 },\n // Grok 4.3\n 'grok-4.3': { input: 1.25, output: 2.50, cacheRead: 0.00, cacheWrite: 0.00 },\n // DeepSeek API pricing as of 2026-05: cache-write conservatively uses cache-miss input pricing.\n 'deepseek-chat': { input: 0.27, output: 1.10, cacheRead: 0.07, cacheWrite: 0.27 },\n 'deepseek-reasoner': { input: 0.55, output: 2.19, cacheRead: 0.14, cacheWrite: 0.55 },\n // Deprecated aliases retained for historical ledger rows.\n 'grok-4-fast': { input: 1.25, output: 2.50, cacheRead: 0.00, cacheWrite: 0.00 },\n 'grok-3-mini-latest': { input: 1.25, output: 2.50, cacheRead: 0.00, cacheWrite: 0.00 },\n // Self-hosted qwen3 on the GPU box — zero marginal cost (electricity aside).\n 'qwen3:8b': { input: 0.0, output: 0.0, cacheRead: 0.0, cacheWrite: 0.0 },\n 'qwen3.6:27b': { input: 0.0, output: 0.0, cacheRead: 0.0, cacheWrite: 0.0 },\n};\n\n/** Fallback pricing used for unrecognised models (Opus rates — conservative upper bound). */\nconst PRICE_FALLBACK = MODEL_PRICE_PER_1M['claude-opus-4-7']!;\n\n/**\n * Estimates the USD cost of a single LLM completion from token counts.\n * Returns 0 for zero-token results. Uses {@link MODEL_PRICE_PER_1M} with\n * {@link PRICE_FALLBACK} for unknown models.\n */\nfunction estimateCostUsd(\n tokens: { input: number; output: number; cacheRead?: number; cacheWrite?: number },\n model: string,\n): number {\n const price = MODEL_PRICE_PER_1M[model] ?? PRICE_FALLBACK;\n return (\n (tokens.input * price.input +\n tokens.output * price.output +\n (tokens.cacheRead ?? 0) * price.cacheRead +\n (tokens.cacheWrite ?? 0) * price.cacheWrite) /\n 1_000_000\n );\n}\n\n/** Returns `YYYY-MM` from a Unix timestamp (ms). Used for monthly KV cost keys. */\nfunction isoMonth(nowMs: number): string {\n return new Date(nowMs).toISOString().slice(0, 7);\n}\n\n/**\n * Marks a provider as cooling down for {@link PROVIDER_COOLDOWN_MS} milliseconds.\n */\nfunction markProviderCoolingDown(provider: LLMProvider, now: () => number = Date.now): void {\n providerCooldownUntil.set(provider, now() + PROVIDER_COOLDOWN_MS);\n}\n\n/**\n * Clears the cooldown state for a provider after a successful call.\n */\nfunction clearProviderCooldown(provider: LLMProvider): void {\n providerCooldownUntil.delete(provider);\n}\n\n// ─── Legacy backoff constant (kept for the existing callWithBackoff signature) ─\nconst BASE_BACKOFF_MS = 250;\n\ninterface ProviderError {\n provider: LLMProvider;\n status: number;\n retryable: boolean;\n message: string;\n}\n\n/**\n * Returns `true` for status codes that should trigger a retry.\n * Only 429 and 5xx (transient server errors) qualify; other 4xx are terminal.\n */\nfunction isRetryableForBackoff(status: number): boolean {\n return status === 429 || (status >= 500 && status < 600);\n}\n\nfunction estimateTokens(messages: LLMMessage[], system?: string): number {\n // Cheap estimator: ~4 chars/token. Good enough for threshold routing.\n let chars = system?.length ?? 0;\n for (const m of messages) chars += contentToText(m.content).length;\n return Math.ceil(chars / 4);\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n const t = setTimeout(resolve, ms);\n if (signal) {\n const onAbort = () => {\n clearTimeout(t);\n reject(new DOMException('Aborted', 'AbortError'));\n };\n if (signal.aborted) onAbort();\n else signal.addEventListener('abort', onAbort, { once: true });\n }\n });\n}\n\n/**\n * Computes the exponential backoff delay for a given attempt with jitter.\n *\n * Formula: `Math.min(base * 2^attempt + jitter, cap)`\n * where `jitter` is a random value in `[0, BACKOFF_JITTER_MAX_MS)`.\n *\n * @param attempt - Zero-based attempt index (0 = first retry after initial failure).\n */\nfunction computeBackoffMs(attempt: number): number {\n const jitter = Math.floor(Math.random() * BACKOFF_JITTER_MAX_MS);\n return Math.min(BACKOFF_BASE_MS * Math.pow(2, attempt) + jitter, BACKOFF_CAP_MS);\n}\n\n// ─── Provider request builders ─────────────────────────────────────────────\n\n/**\n * Claude 4.7-and-later models reject the classic sampling params: sending\n * `temperature` (or `top_p`/`top_k`) returns 400 \"`temperature` is deprecated\n * for this model\" (verified live against /v1/messages, 2026-08-14). Models\n * matching this pattern must not receive a `temperature` field at all — this\n * is exactly what hard-broke the `smart` tier while it pinned opus-4-7.\n */\nconst ANTHROPIC_SAMPLING_REMOVED = /^claude-(opus-4-[78]|opus-5|sonnet-5|fable-5|mythos-5)/;\n\n/**\n * Models that run ADAPTIVE THINKING by default when the `thinking` field is\n * omitted. Thinking tokens draw from `max_tokens`, and this package's budgets\n * are small (DEFAULT_MAX_TOKENS 1024; the headline call passes 40) — left on,\n * the model can spend the whole budget thinking and truncate the visible\n * text. Same failure mode as Gemini 2.5 Pro's mandatory thinking budget (see\n * the MODELS.gemini comment), same fix: turn it off explicitly.\n * `{type:'disabled'}` is accepted on both models at the default effort\n * (verified live 2026-08-14). Do NOT add `claude-fable-5`/`claude-mythos-5`\n * here — those models reject an explicit `disabled` (thinking is always on).\n */\nconst ANTHROPIC_THINKING_DEFAULT_ON = /^claude-(opus-5|sonnet-5)$/;\n\nfunction buildAnthropicRequest(\n model: string,\n messages: LLMMessage[],\n opts: LLMOptions,\n env: LLMEnv,\n streaming = false,\n): { url: string; headers: Record<string, string>; body: string } {\n const sys = systemText(opts, messages);\n const filtered = messages.filter((m) => m.role !== 'system');\n const body: Record<string, unknown> = {\n model,\n max_tokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,\n messages: filtered.map((m) => ({ role: m.role, content: m.content })),\n };\n if (!ANTHROPIC_SAMPLING_REMOVED.test(model)) {\n body.temperature = opts.temperature ?? DEFAULT_TEMPERATURE;\n }\n if (ANTHROPIC_THINKING_DEFAULT_ON.test(model)) {\n body.thinking = { type: 'disabled' };\n }\n if (streaming) {\n body.stream = true;\n }\n if (sys) {\n const cache = opts.promptCache ?? sys.length >= 4096;\n body.system = cache\n ? [{ type: 'text', text: sys, cache_control: { type: 'ephemeral' } }]\n : sys;\n }\n if (opts.tools && opts.tools.length > 0) {\n body.tools = opts.tools.map((t) => ({\n name: t.name,\n description: t.description ?? '',\n input_schema: t.parameters,\n }));\n const tc = opts.toolChoice ?? 'auto';\n body.tool_choice =\n tc === 'auto'\n ? { type: 'auto' }\n : tc === 'none'\n ? { type: 'none' }\n : { type: 'tool', name: tc.name };\n }\n return {\n url: `${env.AI_GATEWAY_BASE_URL}/anthropic/v1/messages`,\n headers: {\n 'content-type': 'application/json',\n 'x-api-key': env.ANTHROPIC_API_KEY,\n 'anthropic-version': '2023-06-01',\n 'anthropic-beta': 'prompt-caching-2024-07-31',\n },\n body: JSON.stringify(body),\n };\n}\n\n/** A resolved Vertex credential: a bearer token plus (for ADC) the ambient project id. */\ninterface VertexAuth {\n token: string;\n /** Project id discovered from the metadata server (ADC path only). */\n project?: string;\n}\n\n/**\n * Resolve a Vertex credential, in priority order:\n * 1. `GCP_SA_KEY` — mint a fresh token from the service-account key (the\n * credential Cloudflare Workers supply, where there is no metadata server).\n * 2. `VERTEX_ACCESS_TOKEN` — a pre-minted token (legacy / CI override).\n * 3. Application Default Credentials — the GCP metadata server's ambient\n * service-account token. This is the KEYLESS default on Cloud Run / GCE:\n * neither env var need be set, mirroring the post-WIF-migration pattern\n * the render-runner already uses for every other GCP API call.\n *\n * @throws ValidationError when no static credential is set AND the metadata\n * server is unreachable (i.e. off-GCP) — the leg fails before the request so\n * the router falls through to the next provider, never calling Vertex\n * unauthenticated.\n */\nasync function resolveVertexAuth(env: LLMEnv, fetchImpl: typeof fetch): Promise<VertexAuth> {\n if (env.GCP_SA_KEY) {\n try {\n return { token: await mintGcpAccessToken(env.GCP_SA_KEY, fetchImpl) };\n } catch (error) {\n // A pre-minted token is worth trying if minting broke. With nothing to fall\n // back to, surface the minting failure rather than swallowing it.\n if (!env.VERTEX_ACCESS_TOKEN) throw error;\n }\n }\n if (env.VERTEX_ACCESS_TOKEN) return { token: env.VERTEX_ACCESS_TOKEN };\n // Keyless default: Application Default Credentials from the GCP metadata\n // server. Throws (→ chain falls through) when off-GCP.\n return fetchAdcAccessToken(fetchImpl);\n}\n\nfunction buildGeminiRequest(\n model: string,\n messages: LLMMessage[],\n opts: LLMOptions,\n env: LLMEnv,\n accessToken: string,\n adcProject?: string,\n): { url: string; headers: Record<string, string>; body: string } {\n const sys = systemText(opts, messages);\n const contents = messages\n .filter((m) => m.role !== 'system')\n .map((m) => ({\n role: m.role === 'assistant' ? 'model' : 'user',\n parts: [{ text: contentToText(m.content) }],\n }));\n const body: Record<string, unknown> = {\n contents,\n generationConfig: {\n maxOutputTokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,\n temperature: opts.temperature ?? DEFAULT_TEMPERATURE,\n // Disable \"thinking\": Gemini 2.5 draws thinking tokens from\n // maxOutputTokens, so on a large prompt (or a small token budget) the\n // model can spend the entire budget thinking and return an empty\n // candidate (finishReason MAX_TOKENS). This leg wants deterministic text\n // out, so thinking is turned off (supported by gemini-2.5-flash).\n thinkingConfig: { thinkingBudget: 0 },\n },\n };\n if (sys) {\n body.systemInstruction = { parts: [{ text: sys }] };\n }\n // Project resolution: an explicit VERTEX_PROJECT wins; else the service\n // account's own project (GCP_SA_KEY path); else the ambient project id the\n // metadata server reported (ADC path). So a Cloud Run job needs no Vertex\n // config at all — the runner's own project is used.\n const project =\n env.VERTEX_PROJECT ||\n (env.GCP_SA_KEY ? serviceAccountProjectId(env.GCP_SA_KEY) : (adcProject ?? ''));\n const location = env.VERTEX_LOCATION || DEFAULT_VERTEX_LOCATION;\n const path = `v1/projects/${project}/locations/${location}/publishers/google/models/${model}:generateContent`;\n return {\n url: `${env.AI_GATEWAY_BASE_URL}/google-vertex-ai/${path}`,\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${accessToken}`,\n },\n body: JSON.stringify(body),\n };\n}\n\n// ─── OpenAI-style (Grok / DeepSeek / Groq) tool-calling helpers ──────────────\n\n/**\n * Converts provider-agnostic messages to OpenAI chat-completions format,\n * translating the Anthropic-shaped tool blocks: `tool_use` → an assistant\n * message with `tool_calls`; `tool_result` → a standalone `tool` message keyed\n * by `tool_call_id`. Plain-string content passes through unchanged.\n */\nfunction toOpenAiMessages(messages: LLMMessage[], sys: string | undefined): Array<Record<string, unknown>> {\n const out: Array<Record<string, unknown>> = [];\n if (sys) out.push({ role: 'system', content: sys });\n for (const m of messages) {\n if (m.role === 'system') continue;\n if (typeof m.content === 'string') {\n out.push({ role: m.role, content: m.content });\n continue;\n }\n let text = '';\n const toolCalls: Array<Record<string, unknown>> = [];\n const results: Array<{ tool_use_id: string; content: string }> = [];\n for (const b of m.content) {\n if (b.type === 'text') text += b.text;\n else if (b.type === 'tool_use')\n toolCalls.push({ id: b.id, type: 'function', function: { name: b.name, arguments: JSON.stringify(b.input) } });\n else if (b.type === 'tool_result') results.push({ tool_use_id: b.tool_use_id, content: b.content });\n }\n if (results.length > 0) {\n for (const r of results) out.push({ role: 'tool', tool_call_id: r.tool_use_id, content: r.content });\n if (text) out.push({ role: 'user', content: text });\n } else if (toolCalls.length > 0) {\n out.push({ role: 'assistant', content: text || null, tool_calls: toolCalls });\n } else {\n out.push({ role: m.role, content: text });\n }\n }\n return out;\n}\n\n/**\n * Converts normalized messages to Ollama's native chat shape. Native tool calls\n * carry object arguments (rather than OpenAI's JSON string), and tool results\n * identify the function by name rather than by call id.\n */\nfunction toOllamaMessages(messages: LLMMessage[], sys: string | undefined): Array<Record<string, unknown>> {\n const toolNames = new Map<string, string>();\n for (const message of messages) {\n if (typeof message.content === 'string') continue;\n for (const block of message.content) {\n if (block.type === 'tool_use') toolNames.set(block.id, block.name);\n }\n }\n\n const out: Array<Record<string, unknown>> = [];\n if (sys) out.push({ role: 'system', content: sys });\n for (const message of messages) {\n if (message.role === 'system') continue;\n if (typeof message.content === 'string') {\n out.push({ role: message.role, content: message.content });\n continue;\n }\n\n let text = '';\n const toolCalls: Array<Record<string, unknown>> = [];\n const results: Array<{ toolUseId: string; content: string }> = [];\n for (const block of message.content) {\n if (block.type === 'text') text += block.text;\n else if (block.type === 'tool_use') {\n toolCalls.push({\n type: 'function',\n function: { index: toolCalls.length, name: block.name, arguments: block.input },\n });\n } else if (block.type === 'tool_result') {\n results.push({ toolUseId: block.tool_use_id, content: block.content });\n }\n }\n if (results.length > 0) {\n for (const result of results) {\n out.push({\n role: 'tool',\n tool_name: toolNames.get(result.toolUseId) ?? result.toolUseId,\n content: result.content,\n });\n }\n if (text) out.push({ role: 'user', content: text });\n } else if (toolCalls.length > 0) {\n out.push({ role: 'assistant', content: text, tool_calls: toolCalls });\n } else {\n out.push({ role: message.role, content: text });\n }\n }\n return out;\n}\n\n/** Builds the OpenAI `tools` array from {@link LLMOptions.tools}, or undefined. */\nfunction openAiTools(opts: LLMOptions): Array<Record<string, unknown>> | undefined {\n if (!opts.tools || opts.tools.length === 0) return undefined;\n return opts.tools.map((t) => ({\n type: 'function',\n function: { name: t.name, description: t.description ?? '', parameters: t.parameters },\n }));\n}\n\n/** Maps {@link LLMOptions.toolChoice} to the OpenAI `tool_choice` value. */\nfunction openAiToolChoice(tc: LLMOptions['toolChoice']): unknown {\n if (tc === undefined) return undefined;\n if (tc === 'auto' || tc === 'none') return tc;\n return { type: 'function', function: { name: tc.name } };\n}\n\nfunction buildGroqRequest(\n model: string,\n messages: LLMMessage[],\n opts: LLMOptions,\n env: LLMEnv,\n): { url: string; headers: Record<string, string>; body: string } {\n const sys = systemText(opts, messages);\n const merged: LLMMessage[] = [];\n if (sys) merged.push({ role: 'system', content: sys });\n for (const m of messages) if (m.role !== 'system') merged.push({ role: m.role, content: contentToText(m.content) });\n return {\n url: `${env.AI_GATEWAY_BASE_URL}/groq/openai/v1/chat/completions`,\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${env.GROQ_API_KEY}`,\n },\n body: JSON.stringify({\n model,\n max_tokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,\n temperature: opts.temperature ?? DEFAULT_TEMPERATURE,\n messages: merged,\n }),\n };\n}\n\nfunction buildGrokRequest(\n model: string,\n messages: LLMMessage[],\n opts: LLMOptions,\n env: LLMEnv,\n): { url: string; headers: Record<string, string>; body: string } {\n if (!env.GROK_API_KEY) {\n throw new ValidationError('GROK_API_KEY required for grok-* model override');\n }\n const sys = systemText(opts, messages);\n const body: Record<string, unknown> = {\n model,\n max_tokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,\n temperature: opts.temperature ?? DEFAULT_TEMPERATURE,\n messages: toOpenAiMessages(messages, sys),\n };\n const tools = openAiTools(opts);\n if (tools) {\n body.tools = tools;\n const tc = openAiToolChoice(opts.toolChoice ?? 'auto');\n if (tc !== undefined) body.tool_choice = tc;\n }\n if (model === MODELS.grok.fast) {\n body.reasoning_effort = opts.reasoningEffort ?? 'none';\n }\n return {\n url: `${env.AI_GATEWAY_BASE_URL}/grok/v1/chat/completions`,\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${env.GROK_API_KEY}`,\n },\n body: JSON.stringify(body),\n };\n}\n\nfunction buildDeepSeekRequest(\n model: string,\n messages: LLMMessage[],\n opts: LLMOptions,\n env: LLMEnv,\n): { url: string; headers: Record<string, string>; body: string } {\n if (!env.DEEPSEEK_API_KEY) {\n throw new ValidationError('DEEPSEEK_API_KEY required for workbench tier or deepseek-* model override');\n }\n const sys = systemText(opts, messages);\n const body: Record<string, unknown> = {\n model,\n max_tokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,\n temperature: opts.temperature ?? DEFAULT_TEMPERATURE,\n messages: toOpenAiMessages(messages, sys),\n };\n const tools = openAiTools(opts);\n if (tools) {\n body.tools = tools;\n const tc = openAiToolChoice(opts.toolChoice ?? 'auto');\n if (tc !== undefined) body.tool_choice = tc;\n }\n return {\n url: `${env.AI_GATEWAY_BASE_URL}/deepseek/chat/completions`,\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${env.DEEPSEEK_API_KEY}`,\n },\n body: JSON.stringify(body),\n };\n}\n\n/**\n * Builds a local Qwen request behind Cloudflare AI Gateway. Qwen 8B uses the\n * dedicated fast origin's native Ollama contract so `think:false` is enforced;\n * Qwen 27B retains the OpenAI-compatible agentic/tool-call route.\n */\nfunction buildLocalRequest(\n model: string,\n messages: LLMMessage[],\n opts: LLMOptions,\n env: LLMEnv,\n): { url: string; headers: Record<string, string>; body: string } {\n if (!env.GPU_LLM_API_TOKEN) {\n throw new ValidationError('GPU_LLM_API_TOKEN required for the local provider');\n }\n const sys = systemText(opts, messages);\n const fastMode = model === MODELS.local.fast;\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n authorization: `Bearer ${env.GPU_LLM_API_TOKEN}`,\n };\n if (env.GPU_LLM_ACCESS_CLIENT_ID) headers['CF-Access-Client-Id'] = env.GPU_LLM_ACCESS_CLIENT_ID;\n if (env.GPU_LLM_ACCESS_CLIENT_SECRET) headers['CF-Access-Client-Secret'] = env.GPU_LLM_ACCESS_CLIENT_SECRET;\n const tools = openAiTools(opts);\n if (fastMode) {\n if (typeof opts.toolChoice === 'object') {\n throw new ValidationError('named tool choice is not supported by the native Qwen 8B route');\n }\n return {\n url: `${env.AI_GATEWAY_BASE_URL}/custom-local-fast-gpu/api/chat`,\n headers,\n body: JSON.stringify({\n model,\n stream: false,\n think: false,\n messages: toOllamaMessages(messages, sys),\n options: {\n num_predict: opts.maxTokens ?? DEFAULT_MAX_TOKENS,\n temperature: opts.temperature ?? DEFAULT_TEMPERATURE,\n },\n ...(tools && opts.toolChoice !== 'none' ? { tools } : {}),\n }),\n };\n }\n return {\n url: `${env.AI_GATEWAY_BASE_URL}/custom-local-gpu/v1/chat/completions`,\n headers,\n body: JSON.stringify({\n model,\n max_tokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,\n temperature: opts.temperature ?? DEFAULT_TEMPERATURE,\n messages: toOpenAiMessages(messages, sys),\n ...(tools\n ? { tools, tool_choice: openAiToolChoice(opts.toolChoice ?? 'auto') }\n : {}),\n }),\n };\n}\n\n// ─── Response parsers ──────────────────────────────────────────────────────\n\ninterface AnthropicResponse {\n content?: Array<{\n type: string;\n text?: string;\n id?: string;\n name?: string;\n input?: Record<string, unknown>;\n }>;\n stop_reason?: string;\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n };\n model?: string;\n}\n\n/** Maps a provider stop reason to the normalized {@link LLMResult.stopReason}. */\nfunction normalizeAnthropicStop(reason: string | undefined): LLMResult['stopReason'] {\n switch (reason) {\n case 'end_turn':\n case 'stop_sequence':\n return 'end';\n case 'tool_use':\n return 'tool_use';\n case 'max_tokens':\n return 'max_tokens';\n default:\n return reason ? 'other' : undefined;\n }\n}\n\ninterface GeminiResponse {\n candidates?: Array<{ content?: { parts?: Array<{ text?: string }> } }>;\n usageMetadata?: {\n promptTokenCount?: number;\n candidatesTokenCount?: number;\n };\n}\n\ninterface GroqResponse {\n choices?: Array<{ message?: { content?: string } }>;\n usage?: { prompt_tokens?: number; completion_tokens?: number };\n model?: string;\n}\n\nfunction parseAnthropic(\n json: unknown,\n): {\n content: string;\n input: number;\n output: number;\n cacheRead: number;\n cacheWrite: number;\n model?: string;\n toolCalls?: LLMToolCall[];\n stopReason?: LLMResult['stopReason'];\n} {\n const r = json as AnthropicResponse;\n const toolCalls: LLMToolCall[] = (r.content ?? [])\n .filter((c) => c.type === 'tool_use' && typeof c.id === 'string' && typeof c.name === 'string')\n .map((c) => ({ id: c.id!, name: c.name!, arguments: c.input ?? {} }));\n return {\n content: r.content?.find((c) => c.type === 'text')?.text ?? '',\n input: r.usage?.input_tokens ?? 0,\n output: r.usage?.output_tokens ?? 0,\n cacheRead: r.usage?.cache_read_input_tokens ?? 0,\n cacheWrite: r.usage?.cache_creation_input_tokens ?? 0,\n model: r.model,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n stopReason: normalizeAnthropicStop(r.stop_reason),\n };\n}\n\nfunction parseGemini(json: unknown): { content: string; input: number; output: number } {\n const r = json as GeminiResponse;\n const text =\n r.candidates?.[0]?.content?.parts?.map((p) => p.text ?? '').join('') ?? '';\n return {\n content: text,\n input: r.usageMetadata?.promptTokenCount ?? 0,\n output: r.usageMetadata?.candidatesTokenCount ?? 0,\n };\n}\n\nfunction parseGroq(json: unknown): { content: string; input: number; output: number; model?: string } {\n const r = json as GroqResponse;\n return {\n content: r.choices?.[0]?.message?.content ?? '',\n input: r.usage?.prompt_tokens ?? 0,\n output: r.usage?.completion_tokens ?? 0,\n model: r.model,\n };\n}\n\ninterface OpenAiResponse {\n choices?: Array<{\n message?: {\n content?: string | null;\n tool_calls?: Array<{ id?: string; function?: { name?: string; arguments?: string } }>;\n };\n finish_reason?: string;\n }>;\n usage?: { prompt_tokens?: number; completion_tokens?: number };\n model?: string;\n}\n\ninterface OllamaChatResponse {\n model?: string;\n message?: {\n content?: string;\n thinking?: string;\n tool_calls?: Array<{\n function?: { name?: string; arguments?: Record<string, unknown> | string };\n }>;\n };\n done?: boolean;\n done_reason?: string;\n prompt_eval_count?: number;\n eval_count?: number;\n}\n\n/** Maps an OpenAI `finish_reason` to the normalized {@link LLMResult.stopReason}. */\nfunction normalizeOpenAiStop(reason: string | undefined): LLMResult['stopReason'] {\n switch (reason) {\n case 'stop':\n return 'end';\n case 'tool_calls':\n case 'function_call':\n return 'tool_use';\n case 'length':\n return 'max_tokens';\n default:\n return reason ? 'other' : undefined;\n }\n}\n\n/** Best-effort parse of an OpenAI tool-call arguments string; `{}` on failure. */\nfunction parseToolArgs(raw: string | undefined): Record<string, unknown> {\n if (!raw) return {};\n try {\n const v = JSON.parse(raw) as unknown;\n return typeof v === 'object' && v !== null ? (v as Record<string, unknown>) : {};\n } catch {\n return {};\n }\n}\n\n/**\n * Parses an OpenAI chat-completions response (Grok, DeepSeek), extracting\n * normalized tool calls and a stop reason in addition to text + tokens.\n */\nfunction parseOpenAi(json: unknown): {\n content: string;\n input: number;\n output: number;\n model?: string;\n toolCalls?: LLMToolCall[];\n stopReason?: LLMResult['stopReason'];\n} {\n const r = json as OpenAiResponse;\n const choice = r.choices?.[0];\n const toolCalls: LLMToolCall[] = (choice?.message?.tool_calls ?? [])\n .filter((c) => typeof c.function?.name === 'string')\n .map((c, i) => ({\n id: c.id ?? `call_${i}`,\n name: c.function!.name!,\n arguments: parseToolArgs(c.function?.arguments),\n }));\n return {\n content: choice?.message?.content ?? '',\n input: r.usage?.prompt_tokens ?? 0,\n output: r.usage?.completion_tokens ?? 0,\n model: r.model,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n stopReason: normalizeOpenAiStop(choice?.finish_reason),\n };\n}\n\n/** Parses Ollama's native non-streaming `/api/chat` response for Qwen 8B. */\nfunction parseOllamaChat(json: unknown): {\n content: string;\n input: number;\n output: number;\n model?: string;\n toolCalls?: LLMToolCall[];\n stopReason?: LLMResult['stopReason'];\n} {\n const response = json as OllamaChatResponse;\n const toolCalls: LLMToolCall[] = (response.message?.tool_calls ?? [])\n .filter((call) => typeof call.function?.name === 'string')\n .map((call, index) => ({\n id: `call_${index}`,\n name: call.function!.name!,\n arguments:\n typeof call.function?.arguments === 'string'\n ? parseToolArgs(call.function.arguments)\n : (call.function?.arguments ?? {}),\n }));\n let stopReason: LLMResult['stopReason'];\n if (toolCalls.length > 0) stopReason = 'tool_use';\n else if (response.done_reason === 'length') stopReason = 'max_tokens';\n else if (response.done) stopReason = 'end';\n else if (response.done_reason) stopReason = 'other';\n return {\n // Deliberately exclude message.thinking: callers receive only the bounded\n // classification answer even if a non-conforming origin returns a trace.\n content: response.message?.content ?? '',\n input: response.prompt_eval_count ?? 0,\n output: response.eval_count ?? 0,\n model: response.model,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n stopReason,\n };\n}\n\n// ─── Core call with backoff ────────────────────────────────────────────────\n\n/**\n * Calls a provider with per-provider exponential backoff.\n *\n * Retries up to {@link PER_PROVIDER_MAX_ATTEMPTS} times on 429 or transient 5xx.\n * Other 4xx codes are treated as terminal and not retried.\n * AbortError is never retried — it bubbles immediately.\n *\n * @param provider - Provider name, used for error tagging.\n * @param request - Pre-built HTTP request descriptor.\n * @param fetchImpl - Fetch implementation (injectable for tests).\n * @param signal - Optional AbortSignal for cancellation.\n * @param logger - Optional logger for per-attempt warnings.\n * @param nowFn - Optional clock injection for testability.\n * @returns Parsed JSON body, optional AI Gateway request ID, and attempt count.\n */\nasync function callWithBackoff(\n provider: LLMProvider,\n request: { url: string; headers: Record<string, string>; body: string },\n fetchImpl: typeof fetch,\n signal: AbortSignal | undefined,\n logger: Logger | undefined,\n nowFn?: () => number,\n): Promise<{ json: unknown; gatewayRequestId?: string; attempts: number }> {\n /**\n * Helper: mark provider cooling down and then throw the error.\n * Called whenever we determine we've exhausted all retries for the provider.\n * AbortError is never counted as a provider exhaustion — it bypasses this.\n */\n function exhaustAndThrow(err: ProviderError): never {\n markProviderCoolingDown(provider, nowFn ?? Date.now);\n throw err;\n }\n\n let lastErr: ProviderError | undefined;\n for (let attempt = 1; attempt <= PER_PROVIDER_MAX_ATTEMPTS; attempt++) {\n try {\n const response = await fetchImpl(request.url, {\n method: 'POST',\n headers: request.headers,\n body: request.body,\n signal,\n });\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n const retryable = isRetryableForBackoff(response.status);\n const err: ProviderError = {\n provider,\n status: response.status,\n retryable,\n message: `${provider} ${String(response.status)}: ${text.slice(0, 300)}`,\n };\n logger?.warn?.('llm.provider.error', { provider, status: response.status, attempt });\n if (!err.retryable || attempt === PER_PROVIDER_MAX_ATTEMPTS) {\n if (err.retryable) exhaustAndThrow(err); // retryable but exhausted\n throw err; // terminal non-retryable error — no cooldown\n }\n lastErr = err;\n } else {\n const gatewayRequestId = response.headers.get('cf-aig-request-id') ?? undefined;\n clearProviderCooldown(provider);\n return { json: await response.json(), gatewayRequestId, attempts: attempt };\n }\n } catch (e) {\n if (e instanceof DOMException && e.name === 'AbortError') throw e;\n if (typeof e === 'object' && e !== null && 'retryable' in e) {\n const err = e as ProviderError;\n if (!err.retryable || attempt === PER_PROVIDER_MAX_ATTEMPTS) {\n if (err.retryable) exhaustAndThrow(err); // retryable but exhausted\n throw err; // terminal — no cooldown\n }\n lastErr = err;\n } else {\n const err: ProviderError = {\n provider,\n status: 0,\n retryable: true,\n message: e instanceof Error ? e.message : String(e),\n };\n if (attempt === PER_PROVIDER_MAX_ATTEMPTS) exhaustAndThrow(err);\n lastErr = err;\n }\n }\n // Exponential backoff with jitter: base=500ms, cap=8000ms, jitter up to 250ms\n const backoffMs = computeBackoffMs(attempt - 1);\n await sleep(backoffMs, signal);\n }\n // Fallthrough — should not be reached, but mark cooling down defensively.\n markProviderCoolingDown(provider, nowFn ?? Date.now);\n throw lastErr ?? ({ provider, status: 0, retryable: false, message: 'exhausted' } as ProviderError);\n}\n\nfunction isProviderError(err: unknown): err is ProviderError {\n return (\n typeof err === 'object' &&\n err !== null &&\n typeof (err as { status?: unknown }).status === 'number' &&\n typeof (err as { message?: unknown }).message === 'string' &&\n typeof (err as { provider?: unknown }).provider === 'string'\n );\n}\n\n// ─── Routing ───────────────────────────────────────────────────────────────\n\ninterface RoutePlan {\n primary: { provider: LLMProvider; model: string };\n fallback?: { provider: LLMProvider; model: string };\n}\n\n/**\n * Providers whose request builder + response parser support tool-calling.\n * When `opts.tools` is set, routing is restricted to this set and **fails\n * closed** rather than silently calling a provider that would ignore the\n * tools. Expanded in 1b as the other providers' tool formats are normalized.\n */\nconst TOOL_CAPABLE_PROVIDERS = new Set<LLMProvider>(['anthropic', 'grok', 'deepseek', 'local']);\n\nfunction plan(tier: LLMTier, opts: LLMOptions, tokenEstimate: number): RoutePlan {\n if (opts.model) {\n // Explicit override — best-effort provider detection.\n const m = opts.model;\n if (m.startsWith('claude')) return { primary: { provider: 'anthropic', model: m } };\n if (m.startsWith('gemini')) return { primary: { provider: 'gemini', model: m } };\n if (m.startsWith('grok')) return { primary: { provider: 'grok', model: m } };\n if (m.startsWith('deepseek')) return { primary: { provider: 'deepseek', model: m } };\n if (m.startsWith('qwen')) return { primary: { provider: 'local', model: m } };\n return { primary: { provider: 'groq', model: m } };\n }\n const longContext = tokenEstimate >= (opts.longContextThreshold ?? DEFAULT_LONG_CONTEXT_THRESHOLD);\n switch (tier) {\n case 'workbench':\n return {\n primary: { provider: 'deepseek', model: MODELS.deepseek.workbench },\n fallback: { provider: 'groq', model: MODELS.groq.verifier },\n };\n case 'verifier':\n return { primary: { provider: 'groq', model: MODELS.groq.verifier } };\n case 'smart':\n return longContext\n ? {\n primary: { provider: 'gemini', model: MODELS.gemini.smart },\n fallback: { provider: 'anthropic', model: MODELS.anthropic.smart },\n }\n : {\n primary: { provider: 'anthropic', model: MODELS.anthropic.smart },\n fallback: { provider: 'gemini', model: MODELS.gemini.smart },\n };\n case 'fast':\n return {\n primary: { provider: 'grok', model: MODELS.grok.fast },\n fallback: { provider: 'anthropic', model: MODELS.anthropic.fast },\n };\n case 'balanced':\n default:\n return longContext\n ? {\n primary: { provider: 'gemini', model: MODELS.gemini.smart },\n fallback: { provider: 'anthropic', model: MODELS.anthropic.balanced },\n }\n : {\n primary: { provider: 'anthropic', model: MODELS.anthropic.balanced },\n fallback: { provider: 'gemini', model: MODELS.gemini.smart },\n };\n }\n}\n\n/**\n * Build the `cf-aig-metadata` header value for the Cloudflare AI Gateway.\n *\n * Carries caller attribution (project / workload / actor / runId) so a single\n * shared gateway can be sliced per-app and per-feature in the AI Gateway\n * dashboard and logs. This replaces the per-app-gateway convention: rather than\n * one gateway per app (which has to be provisioned and silently 401s when it\n * isn't), one gateway tags every request with who made it.\n *\n * Returns `undefined` when no attribution fields are set (header omitted).\n * The CF AI Gateway accepts a JSON object of string/number/boolean values.\n */\nfunction buildAigMetadata(opts: LLMOptions): string | undefined {\n const meta: Record<string, string> = {};\n if (opts.project) meta.project = opts.project;\n if (opts.workload) meta.workload = opts.workload;\n if (opts.actor) meta.actor = opts.actor;\n if (opts.runId) meta.runId = opts.runId;\n return Object.keys(meta).length > 0 ? JSON.stringify(meta) : undefined;\n}\n\nasync function callOne(\n leg: { provider: LLMProvider; model: string },\n messages: LLMMessage[],\n opts: LLMOptions,\n env: LLMEnv,\n fetchImpl: typeof fetch,\n logger: Logger | undefined,\n nowFn?: () => number,\n): Promise<{ parsed: { content: string; input: number; output: number; cacheRead?: number; cacheWrite?: number; model?: string; toolCalls?: LLMToolCall[]; stopReason?: LLMResult['stopReason'] }; gatewayRequestId?: string; attempts: number }> {\n let req: { url: string; headers: Record<string, string>; body: string };\n switch (leg.provider) {\n case 'anthropic':\n req = buildAnthropicRequest(leg.model, messages, opts, env);\n break;\n case 'gemini': {\n // Credential resolution is a network call (mint from key, or the metadata\n // server for ADC), so it must happen here rather than inside the\n // (synchronous) request builders. Cached per isolate, so it is almost\n // always a no-op after the first gemini call.\n const vertexAuth = await resolveVertexAuth(env, fetchImpl);\n req = buildGeminiRequest(leg.model, messages, opts, env, vertexAuth.token, vertexAuth.project);\n break;\n }\n case 'groq':\n req = buildGroqRequest(leg.model, messages, opts, env);\n break;\n case 'grok':\n req = buildGrokRequest(leg.model, messages, opts, env);\n break;\n case 'deepseek':\n req = buildDeepSeekRequest(leg.model, messages, opts, env);\n break;\n case 'local':\n req = buildLocalRequest(leg.model, messages, opts, env);\n break;\n }\n // Attribution for the shared AI Gateway — one gateway, sliced per-app/feature.\n const aigMetadata = buildAigMetadata(opts);\n if (aigMetadata) req.headers['cf-aig-metadata'] = aigMetadata;\n const { json, gatewayRequestId, attempts } = await callWithBackoff(\n leg.provider,\n req,\n fetchImpl,\n opts.signal,\n logger,\n nowFn,\n );\n switch (leg.provider) {\n case 'anthropic':\n return { parsed: parseAnthropic(json), gatewayRequestId, attempts };\n case 'gemini':\n return { parsed: parseGemini(json), gatewayRequestId, attempts };\n case 'groq':\n return { parsed: parseGroq(json), gatewayRequestId, attempts };\n case 'grok':\n return { parsed: parseOpenAi(json), gatewayRequestId, attempts };\n case 'deepseek':\n return { parsed: parseOpenAi(json), gatewayRequestId, attempts };\n case 'local':\n return {\n parsed: leg.model === MODELS.local.fast ? parseOllamaChat(json) : parseOpenAi(json),\n gatewayRequestId,\n attempts,\n };\n }\n}\n\n/**\n * Run a completion through the routing plan for the requested tier.\n *\n * Routing summary (0.3.0):\n * - `fast` → Grok 4.3; Anthropic Haiku fallback when Grok is unavailable\n * - `balanced` → Anthropic Sonnet; Gemini 2.5 Flash if `longContextThreshold` exceeded\n * - `smart` → Anthropic Opus; Gemini 2.5 Flash if long-context\n * - `verifier` → Groq Llama 3.3 70B (no fallback — verifier is inherently cheap/best-effort)\n * - `workbench` → DeepSeek Chat; Groq fallback for boring/reviewable internal batch jobs\n *\n * All provider traffic flows through Cloudflare AI Gateway at `AI_GATEWAY_BASE_URL`.\n *\n * Per-provider reliability guarantees (0.4.0):\n * - Exponential backoff with jitter on 429 / 5xx (base 500ms, cap 8s, up to 2 retries).\n * - Provider cooldown: after exhausting retries the provider is marked cooling down\n * for 30 seconds; subsequent calls skip it and go straight to the fallback leg.\n *\n * @param messages - Ordered chat history.\n * @param env - API key + gateway bindings.\n * @param opts - Optional tier/model/parameters override.\n * @param deps - Optional fetch/logger/clock injection (for testing).\n * @returns A {@link FactoryResponse} carrying either an {@link LLMResult} or\n * an error (`LLM_ALL_PROVIDERS_FAILED`, `LLM_RATE_LIMITED`, or `INTERNAL_ERROR`).\n */\nexport async function complete(\n messages: LLMMessage[],\n env: LLMEnv,\n opts: LLMOptions = {},\n deps: LLMDeps = {},\n): Promise<FactoryResponse<LLMResult>> {\n if (messages.length === 0) {\n throw new ValidationError('messages must not be empty');\n }\n if (!env.AI_GATEWAY_BASE_URL) {\n throw new ValidationError('AI_GATEWAY_BASE_URL is required in 0.3.0');\n }\n const fetchImpl = deps.fetch ?? fetch;\n const now = deps.now ?? (() => Date.now());\n const logger = deps.logger;\n const startedAt = now();\n\n const tier: LLMTier = opts.tier ?? 'balanced';\n const system = systemText(opts, messages);\n const tokenEstimate = estimateTokens(messages, system);\n let route = plan(tier, opts, tokenEstimate);\n // Cost optimization: prefer the dedicated Qwen 8B GPU ($0) for cheap `fast`-tier\n // work, with the original fast route (Grok→Haiku) as automatic fallback on any\n // error. Dormant unless LLM_LOCAL_FIRST + GPU_LLM_API_TOKEN are set.\n if (\n env.LLM_LOCAL_FIRST &&\n tier === 'fast' &&\n !opts.model &&\n env.GPU_LLM_API_TOKEN\n ) {\n route = { primary: { provider: 'local', model: MODELS.local.fast }, fallback: route.primary };\n }\n // Qwen 27B owns local agentic work. DeepSeek remains the independent fallback;\n // explicit model overrides always win.\n if (\n env.LLM_LOCAL_WORKBENCH &&\n tier === 'workbench' &&\n !opts.model &&\n env.GPU_LLM_API_TOKEN\n ) {\n route = { primary: { provider: 'local', model: MODELS.local.workbench }, fallback: route.primary };\n }\n\n // ── Org-level daily / monthly cap pre-check ──────────────────────────────\n const kv = env.LLM_COST_KV;\n const todayKey = `llm:daily-cost:${isoDate(now())}`;\n const monthKey = `llm:monthly-cost:${isoMonth(now())}`;\n if (kv) {\n if (opts.dailyCapUsd !== undefined) {\n const raw = await kv.get(todayKey).catch(() => null);\n const spent = parseFloat(raw ?? '0');\n if (spent >= opts.dailyCapUsd) {\n return toErrorResponse(\n new RateLimitError('LLM_DAILY_CAP_EXCEEDED', {\n spentUsd: spent,\n dailyCapUsd: opts.dailyCapUsd,\n }),\n );\n }\n }\n if (opts.monthlyCapUsd !== undefined) {\n const raw = await kv.get(monthKey).catch(() => null);\n const spent = parseFloat(raw ?? '0');\n if (spent >= opts.monthlyCapUsd) {\n return toErrorResponse(\n new RateLimitError('LLM_MONTHLY_CAP_EXCEEDED', {\n spentUsd: spent,\n monthlyCapUsd: opts.monthlyCapUsd,\n }),\n );\n }\n }\n }\n\n const attemptLog: Array<{ provider: LLMProvider; status?: number; message: string }> = [];\n\n let routeLegs = [route.primary, route.fallback].filter(Boolean) as Array<{ provider: LLMProvider; model: string }>;\n // Tool-calling fails closed: never fall back to a provider that can't honour\n // the tool schema. Narrow the route to tool-capable providers when tools are set.\n if (opts.tools && opts.tools.length > 0) {\n routeLegs = routeLegs.filter((l) => TOOL_CAPABLE_PROVIDERS.has(l.provider));\n if (routeLegs.length === 0) {\n throw new ValidationError(\n `tool-calling requires a tool-capable provider (${[...TOOL_CAPABLE_PROVIDERS].join(', ')}); tier '${tier}' has none — use tier fast/balanced/smart or a claude-* model override`,\n );\n }\n }\n for (const [legIndex, leg] of routeLegs.entries()) {\n // Skip providers that are currently in their cooldown window.\n if (isProviderCoolingDown(leg.provider, now)) {\n logger?.warn?.('llm.provider.coolingDown', { provider: leg.provider });\n attemptLog.push({ provider: leg.provider, message: 'skipped: cooling down' });\n continue;\n }\n if (opts.signal?.aborted) {\n return toErrorResponse(\n new InternalError('llm call aborted', { provider: leg.provider, model: leg.model }),\n );\n }\n try {\n const result = await callOne(leg, messages, opts, env, fetchImpl, logger, now);\n // A tool_use turn legitimately has no text content — only treat a\n // genuinely empty response (no text AND no tool calls) as a failure.\n if (!result.parsed.content && !(result.parsed.toolCalls && result.parsed.toolCalls.length > 0)) {\n throw { provider: leg.provider, status: 200, retryable: false, message: 'empty content' } satisfies ProviderError;\n }\n logger?.info?.('llm.complete', {\n provider: leg.provider,\n model: leg.model,\n tier,\n tokenEstimate,\n attempts: result.attempts,\n runId: opts.runId,\n project: opts.project,\n actor: opts.actor,\n workload: opts.workload,\n });\n const llmResult: LLMResult = {\n content: result.parsed.content,\n provider: leg.provider,\n model: result.parsed.model ?? leg.model,\n tier,\n tokens: {\n input: result.parsed.input,\n output: result.parsed.output,\n cacheRead: result.parsed.cacheRead,\n cacheWrite: result.parsed.cacheWrite,\n },\n latency: now() - startedAt,\n attempts: result.attempts,\n gatewayRequestId: result.gatewayRequestId,\n stopReason: result.parsed.stopReason,\n toolCalls: result.parsed.toolCalls,\n };\n const costUsd = estimateCostUsd(llmResult.tokens, llmResult.model);\n if (opts.maxCostUsd !== undefined && costUsd > opts.maxCostUsd) {\n if (kv && (opts.dailyCapUsd !== undefined || opts.monthlyCapUsd !== undefined)) {\n await recordOrgCostUsage(kv, todayKey, monthKey, costUsd, opts);\n }\n return toErrorResponse(\n new RateLimitError('LLM_COST_CAP_EXCEEDED', {\n costUsd,\n maxCostUsd: opts.maxCostUsd,\n model: llmResult.model,\n tokens: llmResult.tokens,\n }),\n );\n }\n // ── Update org-level cost accumulators in KV ─────────────────────────\n // The KV writes are best-effort. Cloudflare KV does not support atomic\n // compare-and-swap, so concurrent increments may undercount spend.\n if (kv && (opts.dailyCapUsd !== undefined || opts.monthlyCapUsd !== undefined)) {\n await recordOrgCostUsage(kv, todayKey, monthKey, costUsd, opts);\n }\n // ── Metering callback ────────────────────────────────────────────────\n if (deps.onRecord && opts.ledger) {\n const row: LLMRecordRow = {\n ...opts.ledger,\n model: llmResult.model,\n provider: llmResult.provider,\n tier: llmResult.tier,\n inputTokens: llmResult.tokens.input,\n outputTokens: llmResult.tokens.output,\n cacheReadTokens: llmResult.tokens.cacheRead ?? 0,\n cacheWriteTokens: llmResult.tokens.cacheWrite ?? 0,\n latencyMs: llmResult.latency,\n costUsd,\n yyyyMm: isoMonth(now()),\n };\n deps.onRecord(row).catch((e: unknown) => {\n logger?.warn?.('llm.onRecord.error', { message: e instanceof Error ? e.message : String(e) });\n });\n }\n return { data: llmResult, error: null };\n } catch (e) {\n if (e instanceof DOMException && e.name === 'AbortError') {\n return toErrorResponse(\n new InternalError('llm call aborted', { provider: leg.provider, model: leg.model }),\n );\n }\n if (isProviderError(e)) {\n attemptLog.push({ provider: e.provider, status: e.status, message: e.message });\n if (e.status === 429 && legIndex === routeLegs.length - 1) {\n return toErrorResponse(\n new RateLimitError(`llm rate limited on ${e.provider}`, { attempts: attemptLog }),\n );\n }\n logger?.warn?.('llm.leg.failed', { provider: leg.provider, status: e.status });\n continue;\n }\n attemptLog.push({ provider: leg.provider, message: e instanceof Error ? e.message : String(e) });\n }\n }\n\n return toErrorResponse(\n new InternalError('LLM_ALL_PROVIDERS_FAILED', { attempts: attemptLog, tier, tokenEstimate }),\n );\n}\n\n// ─── Streaming ────────────────────────────────────────────────────────────\n\n/**\n * Anthropic server-sent event shapes used by the streaming parser.\n * Only the fields we consume are typed; the rest are ignored.\n */\ninterface AnthropicStreamEvent {\n type: string;\n index?: number;\n delta?: { type?: string; text?: string; partial_json?: string; stop_reason?: string };\n content_block?: { type?: string; id?: string; name?: string };\n message?: {\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n };\n model?: string;\n };\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n };\n}\n\n/**\n * Streams a completion from the primary Anthropic provider, yielding text chunks\n * as they arrive. Falls back to the non-streaming {@link complete} function when\n * the provider does not support streaming (i.e. a non-Anthropic primary is selected).\n *\n * The generator's **return value** (accessible via `gen.return()` or by consuming\n * the full iteration) is an {@link LLMResult} with the same shape as {@link complete}.\n *\n * Usage pattern:\n * ```ts\n * const gen = completionStream(messages, env, opts);\n * for await (const chunk of gen) {\n * // stream chunk to client\n * }\n * const result = (await gen.return(undefined)).value; // LLMResult\n * ```\n *\n * @param messages - Ordered chat history.\n * @param env - API key + gateway bindings.\n * @param opts - Optional tier/model/parameters override. Accepts `deps` as nested field.\n * @returns An async generator that yields `string` chunks and returns an {@link LLMResult}.\n */\nexport async function* completionStream(\n messages: LLMMessage[],\n env: LLMEnv,\n opts: LLMOptions & { deps?: LLMDeps } = {},\n): AsyncGenerator<string, LLMResult, unknown> {\n if (messages.length === 0) {\n throw new ValidationError('messages must not be empty');\n }\n if (!env.AI_GATEWAY_BASE_URL) {\n throw new ValidationError('AI_GATEWAY_BASE_URL is required in 0.3.0');\n }\n\n const deps: LLMDeps = opts.deps ?? {};\n const fetchImpl = deps.fetch ?? fetch;\n const now = deps.now ?? (() => Date.now());\n const logger = deps.logger;\n const startedAt = now();\n\n const tier: LLMTier = opts.tier ?? 'balanced';\n const system = systemText(opts, messages);\n const tokenEstimate = estimateTokens(messages, system);\n const route = plan(tier, opts, tokenEstimate);\n const streamLeg =\n route.primary.provider === 'grok' && !env.GROK_API_KEY && route.fallback?.provider === 'anthropic'\n ? route.fallback\n : route.primary;\n\n // Only Anthropic supports streaming in the current implementation.\n // For all other primaries, fall back to non-streaming complete().\n if (streamLeg.provider !== 'anthropic') {\n const result = await complete(messages, env, opts, deps);\n if (result.error !== null || result.data === null) {\n throw new InternalError('LLM_ALL_PROVIDERS_FAILED', { error: result.error });\n }\n yield result.data.content;\n return result.data;\n }\n\n // Check cooldown before attempting the streaming call.\n if (isProviderCoolingDown(streamLeg.provider, now)) {\n logger?.warn?.('llm.provider.coolingDown', { provider: streamLeg.provider });\n // Fall back to non-streaming complete() which will handle the fallback leg.\n const result = await complete(messages, env, opts, deps);\n if (result.error !== null || result.data === null) {\n throw new InternalError('LLM_ALL_PROVIDERS_FAILED', { error: result.error });\n }\n yield result.data.content;\n return result.data;\n }\n\n const req = buildAnthropicRequest(streamLeg.model, messages, opts, env, true);\n // Attribution for the shared AI Gateway (matches the non-streaming path).\n const streamAigMetadata = buildAigMetadata(opts);\n if (streamAigMetadata) req.headers['cf-aig-metadata'] = streamAigMetadata;\n\n let response: Response;\n try {\n response = await fetchImpl(req.url, {\n method: 'POST',\n headers: req.headers,\n body: req.body,\n // Fall back to a 60 s default when the caller provides no signal — prevents\n // a hung provider connection from consuming the Worker's wall-clock budget.\n signal: opts.signal ?? AbortSignal.timeout(60_000),\n });\n } catch (e) {\n if (e instanceof DOMException && e.name === 'AbortError') {\n throw new InternalError('llm call aborted', {\n provider: streamLeg.provider,\n model: streamLeg.model,\n });\n }\n throw new InternalError('llm stream fetch failed', {\n message: e instanceof Error ? e.message : String(e),\n });\n }\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n const retryable = isRetryableForBackoff(response.status);\n if (retryable && response.status === 429) {\n markProviderCoolingDown(streamLeg.provider, now);\n }\n // Fall back to non-streaming complete() which will try the fallback leg.\n const result = await complete(messages, env, opts, deps);\n if (result.error !== null || result.data === null) {\n throw new InternalError('LLM_ALL_PROVIDERS_FAILED', {\n streamError: `${streamLeg.provider} ${String(response.status)}: ${text.slice(0, 300)}`,\n error: result.error,\n });\n }\n yield result.data.content;\n return result.data;\n }\n\n if (!response.body) {\n throw new InternalError('llm stream response body is null', {\n provider: streamLeg.provider,\n });\n }\n\n // Stream SSE events from Anthropic.\n const decoder = new TextDecoder();\n let accumulatedText = '';\n let inputTokens = 0;\n let outputTokens = 0;\n let cacheRead = 0;\n let cacheWrite = 0;\n let modelName: string | undefined;\n // Tool-use blocks arrive as content_block_start (id/name) then a sequence of\n // input_json_delta fragments that concatenate into the arguments JSON string.\n const toolBlocks = new Map<number, { id: string; name: string; json: string }>();\n let streamStopReason: LLMResult['stopReason'];\n const gatewayRequestId: string | undefined = response.headers.get('cf-aig-request-id') ?? undefined;\n\n const reader = response.body.getReader();\n let buffer = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n // SSE lines are delimited by '\\n'. Events are separated by '\\n\\n'.\n const lines = buffer.split('\\n');\n // Keep the last (potentially incomplete) line in the buffer.\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue;\n const data = line.slice(6).trim();\n if (data === '[DONE]') break;\n let event: AnthropicStreamEvent;\n try {\n event = JSON.parse(data) as AnthropicStreamEvent;\n } catch {\n continue; // Skip malformed SSE lines.\n }\n\n switch (event.type) {\n case 'message_start':\n inputTokens = event.message?.usage?.input_tokens ?? 0;\n cacheRead = event.message?.usage?.cache_read_input_tokens ?? 0;\n cacheWrite = event.message?.usage?.cache_creation_input_tokens ?? 0;\n modelName = event.message?.model;\n break;\n case 'content_block_start':\n if (\n event.content_block?.type === 'tool_use' &&\n typeof event.index === 'number' &&\n typeof event.content_block.id === 'string' &&\n typeof event.content_block.name === 'string'\n ) {\n toolBlocks.set(event.index, { id: event.content_block.id, name: event.content_block.name, json: '' });\n }\n break;\n case 'content_block_delta':\n if (event.delta?.type === 'text_delta' && typeof event.delta.text === 'string') {\n accumulatedText += event.delta.text;\n yield event.delta.text;\n } else if (\n event.delta?.type === 'input_json_delta' &&\n typeof event.delta.partial_json === 'string' &&\n typeof event.index === 'number'\n ) {\n const block = toolBlocks.get(event.index);\n if (block) block.json += event.delta.partial_json;\n }\n break;\n case 'message_delta':\n outputTokens = event.usage?.output_tokens ?? outputTokens;\n if (event.delta?.stop_reason) streamStopReason = normalizeAnthropicStop(event.delta.stop_reason);\n break;\n default:\n break;\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n clearProviderCooldown(streamLeg.provider);\n logger?.info?.('llm.completionStream', {\n provider: streamLeg.provider,\n model: streamLeg.model,\n tier,\n tokenEstimate,\n runId: opts.runId,\n project: opts.project,\n actor: opts.actor,\n workload: opts.workload,\n });\n\n const toolCalls: LLMToolCall[] = [...toolBlocks.values()].map((b) => ({\n id: b.id,\n name: b.name,\n arguments: parseToolArgs(b.json),\n }));\n\n return {\n content: accumulatedText,\n provider: streamLeg.provider,\n model: modelName ?? streamLeg.model,\n tier,\n tokens: { input: inputTokens, output: outputTokens, cacheRead, cacheWrite },\n latency: now() - startedAt,\n attempts: 1,\n gatewayRequestId,\n stopReason: streamStopReason,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n };\n}\n\n// ─── Grounding assertion ───────────────────────────────────────────────────\n\n/**\n * Returns `true` if `response` contains at least one verbatim phrase of at\n * least 5 consecutive whitespace-delimited tokens that also appears in one of\n * the `sources` strings.\n *\n * Returns `true` unconditionally when `sources` is empty (no grounding\n * documents means grounding cannot be violated).\n *\n * This is a lightweight guard for RAG pipelines — it detects obvious\n * hallucinations where the model generates content not present in any\n * retrieved source. It is NOT a semantic similarity check.\n *\n * @param response - The LLM-generated text to inspect.\n * @param sources - Retrieved source documents to check against.\n * @returns `true` if the response is grounded, `false` if hallucination detected.\n *\n * @example\n * ```ts\n * const grounded = assertGrounding(llmAnswer, retrievedDocs);\n * if (!grounded) {\n * // flag or re-rank the response\n * }\n * ```\n */\nexport function assertGrounding(response: string, sources: string[]): boolean {\n if (sources.length === 0) return true;\n\n const WINDOW = 5;\n const responseTokens = response.split(/\\s+/).filter((t) => t.length > 0);\n\n if (responseTokens.length < WINDOW) return false;\n\n // Build a set of all 5-token ngrams from each source for O(n) lookup.\n const sourceNgrams = new Set<string>();\n for (const source of sources) {\n const tokens = source.split(/\\s+/).filter((t) => t.length > 0);\n for (let i = 0; i <= tokens.length - WINDOW; i++) {\n const ngram = tokens.slice(i, i + WINDOW).join(' ');\n sourceNgrams.add(ngram);\n }\n }\n\n if (sourceNgrams.size === 0) return false;\n\n // Slide a window of WINDOW tokens over the response and check for a match.\n for (let i = 0; i <= responseTokens.length - WINDOW; i++) {\n const ngram = responseTokens.slice(i, i + WINDOW).join(' ');\n if (sourceNgrams.has(ngram)) return true;\n }\n\n return false;\n}\n\n// ─── Exported helpers (kept for existing consumers) ───────────────────────\n\nexport { MODELS, isProviderCoolingDown, markProviderCoolingDown, clearProviderCooldown, PROVIDER_COOLDOWN_MS };\nexport { BASE_BACKOFF_MS };\n\n// ─── Embeddings ────────────────────────────────────────────────────────────\n\nexport { embed, embedLocal, DEFAULT_EMBEDDING_MODEL, LOCAL_EMBEDDING_MODEL } from './embed.js';\nexport type { AiBinding, EmbedResult, EmbeddingModel, LocalEmbedEnv } from './embed.js';\n\nexport { mintGcpAccessToken, clearGcpTokenCache, serviceAccountProjectId } from './gcp-token.js';\n","/**\n * Mint short-lived Google Cloud OAuth2 access tokens from a service-account key,\n * inside a Cloudflare Worker.\n *\n * The `gemini` leg authenticates to Vertex AI with a GCP access token, which\n * expires after one hour. A token therefore cannot be a durable Worker secret —\n * any stored value is stale almost immediately. Callers supply the long-lived\n * service-account key instead, and this module exchanges it for a fresh token\n * via the JWT-bearer flow, caching the result per isolate.\n *\n * Web Crypto only — no Node.js built-ins, no `Buffer`. Lifted from\n * `apps/admin-studio/src/lib/gcp-secrets.ts`, where this exact flow has been\n * running in production; hoisted here so every consumer of the `gemini` leg\n * gets a working credential rather than just the one app that hand-rolled it.\n */\nimport { InternalError, ValidationError } from '@latimer-woods-tech/errors';\n\n/** The subset of a GCP service-account JSON key needed for the JWT-bearer flow. */\ninterface ServiceAccountKey {\n project_id: string;\n private_key: string;\n client_email: string;\n token_uri: string;\n}\n\ninterface OAuth2Response {\n access_token: string;\n expires_in: number;\n}\n\n/** Refresh this long before expiry so an in-flight request never races the clock. */\nconst EXPIRY_SKEW_MS = 300_000;\n\n/**\n * Per-isolate token cache, keyed by service-account email.\n *\n * Caching a *string* across requests is safe. Caching an I/O object (a socket, a\n * database client, a `Response`) is not — Workers rejects reuse of I/O created\n * on behalf of a different request. This holds only the token text and its\n * expiry, so it is reused freely.\n */\nconst tokenCache = new Map<string, { token: string; expiresAt: number }>();\n\n/** Decode a base64url-encoded service-account key, tolerating raw JSON too. */\nfunction parseServiceAccountKey(gcpSaKey: string): ServiceAccountKey {\n const text = gcpSaKey.trimStart().startsWith('{') ? gcpSaKey : atob(gcpSaKey);\n const key = JSON.parse(text) as ServiceAccountKey;\n if (!key.client_email || !key.private_key || !key.token_uri) {\n throw new ValidationError('GCP_SA_KEY is missing client_email, private_key, or token_uri');\n }\n return key;\n}\n\n/** Encode bytes as base64url (RFC 4648 §5): `-`/`_` for `+`/`/`, no padding. */\nfunction base64UrlEncode(bytes: Uint8Array): string {\n let binary = '';\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!);\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '');\n}\n\n/** Import a PEM-encoded PKCS#8 private key as a Web Crypto signing key. */\nasync function importPrivateKey(pem: string): Promise<CryptoKey> {\n const body = pem\n .split('\\n')\n .filter((line) => !line.startsWith('-----'))\n .join('');\n const binary = atob(body);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return crypto.subtle.importKey(\n 'pkcs8',\n bytes.buffer,\n { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },\n false,\n ['sign'],\n );\n}\n\n/** Build a service-account JWT assertion signed with RS256, per GCP's spec. */\nasync function createAssertion(key: ServiceAccountKey, nowSeconds: number): Promise<string> {\n const encoder = new TextEncoder();\n const header = base64UrlEncode(encoder.encode(JSON.stringify({ alg: 'RS256', typ: 'JWT' })));\n const payload = base64UrlEncode(\n encoder.encode(\n JSON.stringify({\n iss: key.client_email,\n scope: 'https://www.googleapis.com/auth/cloud-platform',\n aud: key.token_uri,\n exp: nowSeconds + 3600,\n iat: nowSeconds,\n }),\n ),\n );\n const signingInput = `${header}.${payload}`;\n const cryptoKey = await importPrivateKey(key.private_key);\n const signature = await crypto.subtle.sign(\n 'RSASSA-PKCS1-v1_5',\n cryptoKey,\n encoder.encode(signingInput).buffer,\n );\n return `${signingInput}.${base64UrlEncode(new Uint8Array(signature))}`;\n}\n\n/**\n * Exchange a service-account key for a cloud-platform access token, reusing a\n * cached token until it is within {@link EXPIRY_SKEW_MS} of expiring.\n *\n * @param gcpSaKey - Service-account JSON key, base64-encoded or raw.\n * @param fetchImpl - Fetch implementation (injectable for tests).\n * @returns A bearer token valid for Vertex AI.\n * @throws ValidationError when the key is malformed; InternalError when the exchange fails.\n */\nexport async function mintGcpAccessToken(gcpSaKey: string, fetchImpl: typeof fetch): Promise<string> {\n const key = parseServiceAccountKey(gcpSaKey);\n\n const now = Date.now();\n const cached = tokenCache.get(key.client_email);\n if (cached && cached.expiresAt - EXPIRY_SKEW_MS > now) return cached.token;\n\n const assertion = await createAssertion(key, Math.floor(now / 1000));\n const response = await fetchImpl(key.token_uri, {\n method: 'POST',\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',\n assertion,\n }).toString(),\n }).catch((cause: unknown) => {\n throw new InternalError(`GCP token exchange failed: ${String(cause)}`);\n });\n\n if (!response.ok) {\n throw new InternalError(`GCP token exchange returned ${response.status}`);\n }\n\n const data = (await response.json()) as OAuth2Response;\n if (!data.access_token) {\n throw new InternalError('GCP token exchange returned no access_token');\n }\n\n // Trust the server's TTL rather than assuming the 3600s default.\n const ttlMs = (data.expires_in > 0 ? data.expires_in : 3600) * 1000;\n tokenCache.set(key.client_email, { token: data.access_token, expiresAt: now + ttlMs });\n return data.access_token;\n}\n\n// ─── Application Default Credentials (GCP metadata server) ───────────────────\n/**\n * GCE / Cloud Run metadata server base. Reachable only from GCP compute; the\n * hostname does not resolve elsewhere, so an off-GCP probe fails fast.\n */\nconst METADATA_BASE = 'http://metadata.google.internal/computeMetadata/v1';\n/** Required header for every metadata-server request (anti-SSRF guard). */\nconst METADATA_HEADER = { 'Metadata-Flavor': 'Google' } as const;\n/**\n * Bound the metadata probe so a non-GCP environment (where the host may hang\n * rather than refuse) falls through to the next provider quickly.\n */\nconst METADATA_TIMEOUT_MS = 2_000;\n\n/** Per-isolate cache for the ADC (metadata-server) credential. */\nlet adcCache: { token: string; project?: string; expiresAt: number } | undefined;\n\ninterface AdcCredential {\n token: string;\n /** The instance's own GCP project id, as reported by the metadata server. */\n project?: string;\n}\n\n/**\n * Obtain an access token (and project id) from the GCP metadata server using\n * Application Default Credentials — the keyless credential every Cloud Run /\n * GCE workload already has via its attached service account. No key, no env\n * var: the instance SA's cloud-platform token is served by the metadata\n * server. This mirrors the render-runner's own `_meta_token` shell helper.\n *\n * Fails (throws {@link InternalError}) when the metadata server is unreachable\n * or returns no token — i.e. off-GCP — so the caller falls through to the next\n * provider leg. Never throws a bare `AbortError` (which the router treats as a\n * hard cancellation): an internal timeout is surfaced as an `InternalError`.\n *\n * @param fetchImpl - Fetch implementation (injectable for tests).\n * @returns A bearer token valid for Vertex AI plus the ambient project id.\n */\nexport async function fetchAdcAccessToken(fetchImpl: typeof fetch): Promise<AdcCredential> {\n const now = Date.now();\n if (adcCache && adcCache.expiresAt - EXPIRY_SKEW_MS > now) {\n return { token: adcCache.token, project: adcCache.project };\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), METADATA_TIMEOUT_MS);\n try {\n const tokenRes = await fetchImpl(\n `${METADATA_BASE}/instance/service-accounts/default/token`,\n { headers: METADATA_HEADER, signal: controller.signal },\n );\n if (!tokenRes.ok) {\n throw new InternalError(`GCP metadata token endpoint returned ${tokenRes.status}`);\n }\n const data = (await tokenRes.json()) as OAuth2Response;\n if (!data.access_token) {\n throw new InternalError('GCP metadata token endpoint returned no access_token');\n }\n\n // Project id is best-effort: a caller may pin VERTEX_PROJECT instead.\n let project: string | undefined;\n try {\n const projRes = await fetchImpl(`${METADATA_BASE}/project/project-id`, {\n headers: METADATA_HEADER,\n signal: controller.signal,\n });\n if (projRes.ok) project = (await projRes.text()).trim() || undefined;\n } catch {\n /* project id is optional — the token is still usable */\n }\n\n const ttlMs = (data.expires_in > 0 ? data.expires_in : 3600) * 1000;\n adcCache = { token: data.access_token, project, expiresAt: now + ttlMs };\n return { token: data.access_token, project };\n } catch (cause) {\n // Normalize an internal-timeout AbortError into an InternalError so the\n // router falls through to the next leg instead of hard-cancelling the call.\n if (cause instanceof InternalError) throw cause;\n throw new InternalError(`GCP metadata credential unavailable: ${String(cause)}`);\n } finally {\n clearTimeout(timer);\n }\n}\n\n/** Reset the per-isolate token cache. Tests only. */\nexport function clearGcpTokenCache(): void {\n tokenCache.clear();\n adcCache = undefined;\n}\n\n/** The project the service account belongs to — the Vertex project by default. */\nexport function serviceAccountProjectId(gcpSaKey: string): string {\n return parseServiceAccountKey(gcpSaKey).project_id;\n}\n","/**\n * Workers AI embedding API.\n *\n * Pins to bge-base-en-v1.5 (768-dim cosine) — the platform standard.\n * @remarks Local-rail errors use @latimer-woods-tech/errors (observability ratchet).\n * model_version is returned with every result so callers can record it\n * for provenance-aware re-embed when the model is swapped.\n *\n * Contract: inject the Workers AI binding (env.AI) — never import a vendor SDK.\n */\n\nimport { InternalError } from '@latimer-woods-tech/errors';\n\n/** The platform-standard embedding model (768-dim cosine) used when no override is given. */\nexport const DEFAULT_EMBEDDING_MODEL = '@cf/baai/bge-base-en-v1.5';\n\n/** Supported embedding model identifiers — widen deliberately, since dims are part of the contract. */\nexport type EmbeddingModel = '@cf/baai/bge-base-en-v1.5';\n\n/** One embedding call's output: the vectors plus the provenance needed to re-embed later. */\nexport interface EmbedResult {\n vectors: number[][];\n model: string;\n dims: number;\n}\n\n/** Minimal Workers AI binding shape needed for embeddings. */\nexport interface AiBinding {\n run(\n model: string,\n inputs: { text: string | string[] },\n ): Promise<{ data: number[][] }>;\n}\n\n/**\n * Embeds one or more text strings using the Workers AI binding.\n *\n * @param ai - Workers AI binding (`env.AI`) — injected, not imported.\n * @param input - Single string or array of strings to embed.\n * @param opts - Optional model override (must be a supported EmbeddingModel).\n * @returns Embedding vectors, model name, and dimension count.\n *\n * @throws When the AI binding call fails (let the caller decide whether to catch).\n */\nexport async function embed(\n ai: AiBinding,\n input: string | string[],\n opts?: { model?: EmbeddingModel },\n): Promise<EmbedResult> {\n const model = opts?.model ?? DEFAULT_EMBEDDING_MODEL;\n const texts = Array.isArray(input) ? input : [input];\n const result = await ai.run(model, { text: texts });\n const vectors = result.data;\n if (!vectors || vectors.length === 0) {\n throw new Error(`embed(): Workers AI returned no vectors for model ${model}`);\n }\n return {\n vectors,\n model,\n dims: vectors[0]!.length,\n };\n}\n\n/**\n * Self-hosted GPU embedding model (nomic-embed-text, 768-dim), reached through\n * the same CF AI Gateway custom provider as the local chat provider.\n *\n * ⚠️ VECTOR-SPACE WARNING: nomic-embed-text and bge-base-en-v1.5 are BOTH 768-dim\n * but occupy DIFFERENT embedding spaces. Their vectors are NOT comparable. A store\n * built with one model must be QUERIED — and, when migrating, RE-EMBEDDED — with\n * the SAME model. Never write vectors from both models into one store.\n */\nexport const LOCAL_EMBEDDING_MODEL = 'nomic-embed-text';\n\n/** Env subset needed to reach the local embedding rail (shares GPU_LLM_* with the chat provider). */\nexport interface LocalEmbedEnv {\n AI_GATEWAY_BASE_URL: string;\n GPU_LLM_API_TOKEN?: string;\n GPU_LLM_ACCESS_CLIENT_ID?: string;\n GPU_LLM_ACCESS_CLIENT_SECRET?: string;\n}\n\n/**\n * Embeds text on the self-hosted GPU rail (nomic-embed-text) via the AI Gateway\n * custom provider — the $0 alternative to Workers AI {@link embed}.\n *\n * Unlike the local CHAT provider, this has NO cross-provider fallback ON PURPOSE:\n * falling back to Workers AI (bge) would silently write an incompatible-space\n * vector into a nomic store and corrupt retrieval. If the rail is unavailable\n * this THROWS and the caller decides (retry local, or defer the write) — it must\n * NOT substitute a different model.\n *\n * @param env - Gateway base URL + GPU_LLM_* auth (same secrets as the chat provider).\n * @param input - Single string or array of strings to embed.\n * @returns Embedding vectors, model `nomic-embed-text`, and dimension count.\n * @throws On any transport/HTTP/empty-response error (no silent fallback).\n */\nexport async function embedLocal(\n env: LocalEmbedEnv,\n input: string | string[],\n): Promise<EmbedResult> {\n const texts = Array.isArray(input) ? input : [input];\n const res = await fetch(\n `${env.AI_GATEWAY_BASE_URL}/custom-local-gpu/v1/embeddings`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n // CF Access service-token headers only sent when present (bearer-only rails still work).\n ...(env.GPU_LLM_API_TOKEN ? { Authorization: `Bearer ${env.GPU_LLM_API_TOKEN}` } : {}),\n ...(env.GPU_LLM_ACCESS_CLIENT_ID ? { 'CF-Access-Client-Id': env.GPU_LLM_ACCESS_CLIENT_ID } : {}),\n ...(env.GPU_LLM_ACCESS_CLIENT_SECRET ? { 'CF-Access-Client-Secret': env.GPU_LLM_ACCESS_CLIENT_SECRET } : {}),\n },\n body: JSON.stringify({ model: LOCAL_EMBEDDING_MODEL, input: texts }),\n },\n );\n if (!res.ok) {\n throw new InternalError(\n `embedLocal(): rail ${res.status}: ${(await res.text()).slice(0, 160)}`,\n );\n }\n const json = (await res.json()) as { data?: Array<{ embedding: number[] }> };\n const vectors = (json.data ?? []).map((d) => d.embedding);\n if (vectors.length === 0) {\n throw new InternalError(\n `embedLocal(): rail returned no vectors for model ${LOCAL_EMBEDDING_MODEL}`,\n );\n }\n return {\n vectors,\n model: LOCAL_EMBEDDING_MODEL,\n dims: vectors[0]!.length,\n };\n}\n"],"mappings":";AAAA;AAAA,EACE,iBAAAA;AAAA,EACA;AAAA,EACA,mBAAAC;AAAA,EACA;AAAA,OAEK;;;ACSP,SAAS,eAAe,uBAAuB;AAgB/C,IAAM,iBAAiB;AAUvB,IAAM,aAAa,oBAAI,IAAkD;AAGzE,SAAS,uBAAuB,UAAqC;AACnE,QAAM,OAAO,SAAS,UAAU,EAAE,WAAW,GAAG,IAAI,WAAW,KAAK,QAAQ;AAC5E,QAAM,MAAM,KAAK,MAAM,IAAI;AAC3B,MAAI,CAAC,IAAI,gBAAgB,CAAC,IAAI,eAAe,CAAC,IAAI,WAAW;AAC3D,UAAM,IAAI,gBAAgB,+DAA+D;AAAA,EAC3F;AACA,SAAO;AACT;AAGA,SAAS,gBAAgB,OAA2B;AAClD,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,IAAK,WAAU,OAAO,aAAa,MAAM,CAAC,CAAE;AAC9E,SAAO,KAAK,MAAM,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,QAAQ,EAAE;AAChF;AAGA,eAAe,iBAAiB,KAAiC;AAC/D,QAAM,OAAO,IACV,MAAM,IAAI,EACV,OAAO,CAAC,SAAS,CAAC,KAAK,WAAW,OAAO,CAAC,EAC1C,KAAK,EAAE;AACV,QAAM,SAAS,KAAK,IAAI;AACxB,QAAM,QAAQ,IAAI,WAAW,OAAO,MAAM;AAC1C,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,IAAK,OAAM,CAAC,IAAI,OAAO,WAAW,CAAC;AACtE,SAAO,OAAO,OAAO;AAAA,IACnB;AAAA,IACA,MAAM;AAAA,IACN,EAAE,MAAM,qBAAqB,MAAM,UAAU;AAAA,IAC7C;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AACF;AAGA,eAAe,gBAAgB,KAAwB,YAAqC;AAC1F,QAAM,UAAU,IAAI,YAAY;AAChC,QAAM,SAAS,gBAAgB,QAAQ,OAAO,KAAK,UAAU,EAAE,KAAK,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC;AAC3F,QAAM,UAAU;AAAA,IACd,QAAQ;AAAA,MACN,KAAK,UAAU;AAAA,QACb,KAAK,IAAI;AAAA,QACT,OAAO;AAAA,QACP,KAAK,IAAI;AAAA,QACT,KAAK,aAAa;AAAA,QAClB,KAAK;AAAA,MACP,CAAC;AAAA,IACH;AAAA,EACF;AACA,QAAM,eAAe,GAAG,MAAM,IAAI,OAAO;AACzC,QAAM,YAAY,MAAM,iBAAiB,IAAI,WAAW;AACxD,QAAM,YAAY,MAAM,OAAO,OAAO;AAAA,IACpC;AAAA,IACA;AAAA,IACA,QAAQ,OAAO,YAAY,EAAE;AAAA,EAC/B;AACA,SAAO,GAAG,YAAY,IAAI,gBAAgB,IAAI,WAAW,SAAS,CAAC,CAAC;AACtE;AAWA,eAAsB,mBAAmB,UAAkB,WAA0C;AACnG,QAAM,MAAM,uBAAuB,QAAQ;AAE3C,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,SAAS,WAAW,IAAI,IAAI,YAAY;AAC9C,MAAI,UAAU,OAAO,YAAY,iBAAiB,IAAK,QAAO,OAAO;AAErE,QAAM,YAAY,MAAM,gBAAgB,KAAK,KAAK,MAAM,MAAM,GAAI,CAAC;AACnE,QAAM,WAAW,MAAM,UAAU,IAAI,WAAW;AAAA,IAC9C,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,oCAAoC;AAAA,IAC/D,MAAM,IAAI,gBAAgB;AAAA,MACxB,YAAY;AAAA,MACZ;AAAA,IACF,CAAC,EAAE,SAAS;AAAA,EACd,CAAC,EAAE,MAAM,CAAC,UAAmB;AAC3B,UAAM,IAAI,cAAc,8BAA8B,OAAO,KAAK,CAAC,EAAE;AAAA,EACvE,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,cAAc,+BAA+B,SAAS,MAAM,EAAE;AAAA,EAC1E;AAEA,QAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,MAAI,CAAC,KAAK,cAAc;AACtB,UAAM,IAAI,cAAc,6CAA6C;AAAA,EACvE;AAGA,QAAM,SAAS,KAAK,aAAa,IAAI,KAAK,aAAa,QAAQ;AAC/D,aAAW,IAAI,IAAI,cAAc,EAAE,OAAO,KAAK,cAAc,WAAW,MAAM,MAAM,CAAC;AACrF,SAAO,KAAK;AACd;AAOA,IAAM,gBAAgB;AAEtB,IAAM,kBAAkB,EAAE,mBAAmB,SAAS;AAKtD,IAAM,sBAAsB;AAG5B,IAAI;AAuBJ,eAAsB,oBAAoB,WAAiD;AACzF,QAAM,MAAM,KAAK,IAAI;AACrB,MAAI,YAAY,SAAS,YAAY,iBAAiB,KAAK;AACzD,WAAO,EAAE,OAAO,SAAS,OAAO,SAAS,SAAS,QAAQ;AAAA,EAC5D;AAEA,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,mBAAmB;AACtE,MAAI;AACF,UAAM,WAAW,MAAM;AAAA,MACrB,GAAG,aAAa;AAAA,MAChB,EAAE,SAAS,iBAAiB,QAAQ,WAAW,OAAO;AAAA,IACxD;AACA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,cAAc,wCAAwC,SAAS,MAAM,EAAE;AAAA,IACnF;AACA,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,QAAI,CAAC,KAAK,cAAc;AACtB,YAAM,IAAI,cAAc,sDAAsD;AAAA,IAChF;AAGA,QAAI;AACJ,QAAI;AACF,YAAM,UAAU,MAAM,UAAU,GAAG,aAAa,uBAAuB;AAAA,QACrE,SAAS;AAAA,QACT,QAAQ,WAAW;AAAA,MACrB,CAAC;AACD,UAAI,QAAQ,GAAI,YAAW,MAAM,QAAQ,KAAK,GAAG,KAAK,KAAK;AAAA,IAC7D,QAAQ;AAAA,IAER;AAEA,UAAM,SAAS,KAAK,aAAa,IAAI,KAAK,aAAa,QAAQ;AAC/D,eAAW,EAAE,OAAO,KAAK,cAAc,SAAS,WAAW,MAAM,MAAM;AACvE,WAAO,EAAE,OAAO,KAAK,cAAc,QAAQ;AAAA,EAC7C,SAAS,OAAO;AAGd,QAAI,iBAAiB,cAAe,OAAM;AAC1C,UAAM,IAAI,cAAc,wCAAwC,OAAO,KAAK,CAAC,EAAE;AAAA,EACjF,UAAE;AACA,iBAAa,KAAK;AAAA,EACpB;AACF;AAGO,SAAS,qBAA2B;AACzC,aAAW,MAAM;AACjB,aAAW;AACb;AAGO,SAAS,wBAAwB,UAA0B;AAChE,SAAO,uBAAuB,QAAQ,EAAE;AAC1C;;;ACpOA,SAAS,iBAAAC,sBAAqB;AAGvB,IAAM,0BAA0B;AA8BvC,eAAsB,MACpB,IACA,OACA,MACsB;AACtB,QAAM,QAAQ,MAAM,SAAS;AAC7B,QAAM,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AACnD,QAAM,SAAS,MAAM,GAAG,IAAI,OAAO,EAAE,MAAM,MAAM,CAAC;AAClD,QAAM,UAAU,OAAO;AACvB,MAAI,CAAC,WAAW,QAAQ,WAAW,GAAG;AACpC,UAAM,IAAI,MAAM,qDAAqD,KAAK,EAAE;AAAA,EAC9E;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,MAAM,QAAQ,CAAC,EAAG;AAAA,EACpB;AACF;AAWO,IAAM,wBAAwB;AAyBrC,eAAsB,WACpB,KACA,OACsB;AACtB,QAAM,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AACnD,QAAM,MAAM,MAAM;AAAA,IAChB,GAAG,IAAI,mBAAmB;AAAA,IAC1B;AAAA,MACE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA;AAAA,QAEhB,GAAI,IAAI,oBAAoB,EAAE,eAAe,UAAU,IAAI,iBAAiB,GAAG,IAAI,CAAC;AAAA,QACpF,GAAI,IAAI,2BAA2B,EAAE,uBAAuB,IAAI,yBAAyB,IAAI,CAAC;AAAA,QAC9F,GAAI,IAAI,+BAA+B,EAAE,2BAA2B,IAAI,6BAA6B,IAAI,CAAC;AAAA,MAC5G;AAAA,MACA,MAAM,KAAK,UAAU,EAAE,OAAO,uBAAuB,OAAO,MAAM,CAAC;AAAA,IACrE;AAAA,EACF;AACA,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAIA;AAAA,MACR,sBAAsB,IAAI,MAAM,MAAM,MAAM,IAAI,KAAK,GAAG,MAAM,GAAG,GAAG,CAAC;AAAA,IACvE;AAAA,EACF;AACA,QAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,QAAM,WAAW,KAAK,QAAQ,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,SAAS;AACxD,MAAI,QAAQ,WAAW,GAAG;AACxB,UAAM,IAAIA;AAAA,MACR,oDAAoD,qBAAqB;AAAA,IAC3E;AAAA,EACF;AACA,SAAO;AAAA,IACL;AAAA,IACA,OAAO;AAAA,IACP,MAAM,QAAQ,CAAC,EAAG;AAAA,EACpB;AACF;;;AFzEA,SAAS,cAAc,SAA6C;AAClE,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,SAAO,QACJ,IAAI,CAAC,MAAO,EAAE,SAAS,SAAS,EAAE,OAAO,EAAE,SAAS,gBAAgB,EAAE,UAAU,EAAG,EACnF,KAAK,EAAE;AACZ;AAMA,SAAS,WAAW,MAAkB,UAA4C;AAChF,MAAI,KAAK,WAAW,OAAW,QAAO,KAAK;AAC3C,QAAM,IAAI,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ,GAAG;AACrD,SAAO,MAAM,SAAY,SAAY,cAAc,CAAC;AACtD;AA0OA,IAAM,SAAS;AAAA,EACb,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMT,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUN,UAAU;AAAA,IACV,OAAO;AAAA,EACT;AAAA,EACA,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUN,OAAO;AAAA,EACT;AAAA,EACA,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMJ,UAAU;AAAA,EACZ;AAAA,EACA,MAAM;AAAA,IACJ,MAAM;AAAA,EACR;AAAA,EACA,UAAU;AAAA,IACR,WAAW;AAAA,EACb;AAAA,EACA,OAAO;AAAA;AAAA;AAAA,IAGL,MAAM;AAAA,IACN,WAAW;AAAA,EACb;AACF;AAEA,IAAM,qBAAqB;AAC3B,IAAM,sBAAsB;AAE5B,IAAM,0BAA0B;AAChC,IAAM,iCAAiC;AAIvC,IAAM,kBAAkB;AAExB,IAAM,iBAAiB;AAEvB,IAAM,wBAAwB;AAE9B,IAAM,4BAA4B;AAQlC,IAAM,wBAAkD,oBAAI,IAAI;AAGhE,IAAM,uBAAuB;AAM7B,SAAS,sBAAsB,UAAuB,MAAoB,KAAK,KAAc;AAC3F,QAAM,QAAQ,sBAAsB,IAAI,QAAQ;AAChD,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO,IAAI,IAAI;AACjB;AAGA,SAAS,QAAQ,OAAuB;AACtC,SAAO,IAAI,KAAK,KAAK,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAClD;AAOA,eAAe,mBACb,IACA,UACA,UACA,SACA,MACe;AACf,MAAI,KAAK,gBAAgB,QAAW;AAClC,UAAM,MAAM,MAAM,GAAG,IAAI,QAAQ,EAAE,MAAM,MAAM,IAAI;AACnD,UAAM,QAAQ,WAAW,OAAO,GAAG;AACnC,UAAM,GAAG,IAAI,UAAU,OAAO,QAAQ,OAAO,GAAG;AAAA,MAAE,eAAe;AAAA;AAAA,IAAmB,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,EAC9G;AACA,MAAI,KAAK,kBAAkB,QAAW;AACpC,UAAM,MAAM,MAAM,GAAG,IAAI,QAAQ,EAAE,MAAM,MAAM,IAAI;AACnD,UAAM,QAAQ,WAAW,OAAO,GAAG;AACnC,UAAM,GAAG,IAAI,UAAU,OAAO,QAAQ,OAAO,GAAG;AAAA,MAAE,eAAe;AAAA;AAAA,IAAqB,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,EAChH;AACF;AAYO,IAAM,qBAA+G;AAAA;AAAA;AAAA;AAAA;AAAA,EAK1H,oBAAoB,EAAE,OAAO,GAAM,QAAQ,GAAM,WAAW,KAAM,YAAY,KAAK;AAAA,EACnF,6BAA6B,EAAE,OAAO,GAAM,QAAQ,GAAM,WAAW,KAAM,YAAY,KAAK;AAAA;AAAA,EAE5F,4BAA4B,EAAE,OAAO,GAAM,QAAQ,IAAO,WAAW,KAAM,YAAY,KAAK;AAAA,EAC5F,qBAAqB,EAAE,OAAO,GAAM,QAAQ,IAAO,WAAW,KAAM,YAAY,KAAK;AAAA;AAAA;AAAA;AAAA,EAIrF,mBAAmB,EAAE,OAAO,GAAM,QAAQ,IAAO,WAAW,KAAM,YAAY,KAAK;AAAA;AAAA,EAEnF,0BAA0B,EAAE,OAAO,IAAO,QAAQ,IAAO,WAAW,KAAM,YAAY,MAAM;AAAA;AAAA;AAAA,EAG5F,mBAAmB,EAAE,OAAO,GAAM,QAAQ,IAAO,WAAW,KAAM,YAAY,KAAK;AAAA;AAAA,EAEnF,iBAAiB,EAAE,OAAO,GAAM,QAAQ,IAAO,WAAW,KAAM,YAAY,KAAK;AAAA;AAAA,EAEjF,oBAAoB,EAAE,OAAO,KAAM,QAAQ,KAAM,WAAW,OAAO,YAAY,IAAK;AAAA;AAAA,EAEpF,kBAAkB,EAAE,OAAO,MAAM,QAAQ,IAAO,WAAW,MAAM,YAAY,IAAK;AAAA;AAAA;AAAA,EAGlF,2BAA2B,EAAE,OAAO,MAAM,QAAQ,MAAM,WAAW,GAAM,YAAY,EAAK;AAAA;AAAA,EAE1F,YAAY,EAAE,OAAO,MAAM,QAAQ,KAAM,WAAW,GAAM,YAAY,EAAK;AAAA;AAAA,EAE3E,iBAAiB,EAAE,OAAO,MAAM,QAAQ,KAAM,WAAW,MAAM,YAAY,KAAK;AAAA,EAChF,qBAAqB,EAAE,OAAO,MAAM,QAAQ,MAAM,WAAW,MAAM,YAAY,KAAK;AAAA;AAAA,EAEpF,eAAe,EAAE,OAAO,MAAM,QAAQ,KAAM,WAAW,GAAM,YAAY,EAAK;AAAA,EAC9E,sBAAsB,EAAE,OAAO,MAAM,QAAQ,KAAM,WAAW,GAAM,YAAY,EAAK;AAAA;AAAA,EAErF,YAAY,EAAE,OAAO,GAAK,QAAQ,GAAK,WAAW,GAAK,YAAY,EAAI;AAAA,EACvE,eAAe,EAAE,OAAO,GAAK,QAAQ,GAAK,WAAW,GAAK,YAAY,EAAI;AAC5E;AAGA,IAAM,iBAAiB,mBAAmB,iBAAiB;AAO3D,SAAS,gBACP,QACA,OACQ;AACR,QAAM,QAAQ,mBAAmB,KAAK,KAAK;AAC3C,UACG,OAAO,QAAQ,MAAM,QACpB,OAAO,SAAS,MAAM,UACrB,OAAO,aAAa,KAAK,MAAM,aAC/B,OAAO,cAAc,KAAK,MAAM,cACnC;AAEJ;AAGA,SAAS,SAAS,OAAuB;AACvC,SAAO,IAAI,KAAK,KAAK,EAAE,YAAY,EAAE,MAAM,GAAG,CAAC;AACjD;AAKA,SAAS,wBAAwB,UAAuB,MAAoB,KAAK,KAAW;AAC1F,wBAAsB,IAAI,UAAU,IAAI,IAAI,oBAAoB;AAClE;AAKA,SAAS,sBAAsB,UAA6B;AAC1D,wBAAsB,OAAO,QAAQ;AACvC;AAGA,IAAM,kBAAkB;AAaxB,SAAS,sBAAsB,QAAyB;AACtD,SAAO,WAAW,OAAQ,UAAU,OAAO,SAAS;AACtD;AAEA,SAAS,eAAe,UAAwB,QAAyB;AAEvE,MAAI,QAAQ,QAAQ,UAAU;AAC9B,aAAW,KAAK,SAAU,UAAS,cAAc,EAAE,OAAO,EAAE;AAC5D,SAAO,KAAK,KAAK,QAAQ,CAAC;AAC5B;AAEA,SAAS,MAAM,IAAY,QAAqC;AAC9D,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,IAAI,WAAW,SAAS,EAAE;AAChC,QAAI,QAAQ;AACV,YAAM,UAAU,MAAM;AACpB,qBAAa,CAAC;AACd,eAAO,IAAI,aAAa,WAAW,YAAY,CAAC;AAAA,MAClD;AACA,UAAI,OAAO,QAAS,SAAQ;AAAA,UACvB,QAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAAA,IAC/D;AAAA,EACF,CAAC;AACH;AAUA,SAAS,iBAAiB,SAAyB;AACjD,QAAM,SAAS,KAAK,MAAM,KAAK,OAAO,IAAI,qBAAqB;AAC/D,SAAO,KAAK,IAAI,kBAAkB,KAAK,IAAI,GAAG,OAAO,IAAI,QAAQ,cAAc;AACjF;AAWA,IAAM,6BAA6B;AAanC,IAAM,gCAAgC;AAEtC,SAAS,sBACP,OACA,UACA,MACA,KACA,YAAY,OACoD;AAChE,QAAM,MAAM,WAAW,MAAM,QAAQ;AACrC,QAAM,WAAW,SAAS,OAAO,CAAC,MAAM,EAAE,SAAS,QAAQ;AAC3D,QAAM,OAAgC;AAAA,IACpC;AAAA,IACA,YAAY,KAAK,aAAa;AAAA,IAC9B,UAAU,SAAS,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,QAAQ,EAAE;AAAA,EACtE;AACA,MAAI,CAAC,2BAA2B,KAAK,KAAK,GAAG;AAC3C,SAAK,cAAc,KAAK,eAAe;AAAA,EACzC;AACA,MAAI,8BAA8B,KAAK,KAAK,GAAG;AAC7C,SAAK,WAAW,EAAE,MAAM,WAAW;AAAA,EACrC;AACA,MAAI,WAAW;AACb,SAAK,SAAS;AAAA,EAChB;AACA,MAAI,KAAK;AACP,UAAM,QAAQ,KAAK,eAAe,IAAI,UAAU;AAChD,SAAK,SAAS,QACV,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,eAAe,EAAE,MAAM,YAAY,EAAE,CAAC,IAClE;AAAA,EACN;AACA,MAAI,KAAK,SAAS,KAAK,MAAM,SAAS,GAAG;AACvC,SAAK,QAAQ,KAAK,MAAM,IAAI,CAAC,OAAO;AAAA,MAClC,MAAM,EAAE;AAAA,MACR,aAAa,EAAE,eAAe;AAAA,MAC9B,cAAc,EAAE;AAAA,IAClB,EAAE;AACF,UAAM,KAAK,KAAK,cAAc;AAC9B,SAAK,cACH,OAAO,SACH,EAAE,MAAM,OAAO,IACf,OAAO,SACL,EAAE,MAAM,OAAO,IACf,EAAE,MAAM,QAAQ,MAAM,GAAG,KAAK;AAAA,EACxC;AACA,SAAO;AAAA,IACL,KAAK,GAAG,IAAI,mBAAmB;AAAA,IAC/B,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,aAAa,IAAI;AAAA,MACjB,qBAAqB;AAAA,MACrB,kBAAkB;AAAA,IACpB;AAAA,IACA,MAAM,KAAK,UAAU,IAAI;AAAA,EAC3B;AACF;AAwBA,eAAe,kBAAkB,KAAa,WAA8C;AAC1F,MAAI,IAAI,YAAY;AAClB,QAAI;AACF,aAAO,EAAE,OAAO,MAAM,mBAAmB,IAAI,YAAY,SAAS,EAAE;AAAA,IACtE,SAAS,OAAO;AAGd,UAAI,CAAC,IAAI,oBAAqB,OAAM;AAAA,IACtC;AAAA,EACF;AACA,MAAI,IAAI,oBAAqB,QAAO,EAAE,OAAO,IAAI,oBAAoB;AAGrE,SAAO,oBAAoB,SAAS;AACtC;AAEA,SAAS,mBACP,OACA,UACA,MACA,KACA,aACA,YACgE;AAChE,QAAM,MAAM,WAAW,MAAM,QAAQ;AACrC,QAAM,WAAW,SACd,OAAO,CAAC,MAAM,EAAE,SAAS,QAAQ,EACjC,IAAI,CAAC,OAAO;AAAA,IACX,MAAM,EAAE,SAAS,cAAc,UAAU;AAAA,IACzC,OAAO,CAAC,EAAE,MAAM,cAAc,EAAE,OAAO,EAAE,CAAC;AAAA,EAC5C,EAAE;AACJ,QAAM,OAAgC;AAAA,IACpC;AAAA,IACA,kBAAkB;AAAA,MAChB,iBAAiB,KAAK,aAAa;AAAA,MACnC,aAAa,KAAK,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMjC,gBAAgB,EAAE,gBAAgB,EAAE;AAAA,IACtC;AAAA,EACF;AACA,MAAI,KAAK;AACP,SAAK,oBAAoB,EAAE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC,EAAE;AAAA,EACpD;AAKA,QAAM,UACJ,IAAI,mBACH,IAAI,aAAa,wBAAwB,IAAI,UAAU,IAAK,cAAc;AAC7E,QAAM,WAAW,IAAI,mBAAmB;AACxC,QAAM,OAAO,eAAe,OAAO,cAAc,QAAQ,6BAA6B,KAAK;AAC3F,SAAO;AAAA,IACL,KAAK,GAAG,IAAI,mBAAmB,qBAAqB,IAAI;AAAA,IACxD,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,eAAe,UAAU,WAAW;AAAA,IACtC;AAAA,IACA,MAAM,KAAK,UAAU,IAAI;AAAA,EAC3B;AACF;AAUA,SAAS,iBAAiB,UAAwB,KAAyD;AACzG,QAAM,MAAsC,CAAC;AAC7C,MAAI,IAAK,KAAI,KAAK,EAAE,MAAM,UAAU,SAAS,IAAI,CAAC;AAClD,aAAW,KAAK,UAAU;AACxB,QAAI,EAAE,SAAS,SAAU;AACzB,QAAI,OAAO,EAAE,YAAY,UAAU;AACjC,UAAI,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,QAAQ,CAAC;AAC7C;AAAA,IACF;AACA,QAAI,OAAO;AACX,UAAM,YAA4C,CAAC;AACnD,UAAM,UAA2D,CAAC;AAClE,eAAW,KAAK,EAAE,SAAS;AACzB,UAAI,EAAE,SAAS,OAAQ,SAAQ,EAAE;AAAA,eACxB,EAAE,SAAS;AAClB,kBAAU,KAAK,EAAE,IAAI,EAAE,IAAI,MAAM,YAAY,UAAU,EAAE,MAAM,EAAE,MAAM,WAAW,KAAK,UAAU,EAAE,KAAK,EAAE,EAAE,CAAC;AAAA,eACtG,EAAE,SAAS,cAAe,SAAQ,KAAK,EAAE,aAAa,EAAE,aAAa,SAAS,EAAE,QAAQ,CAAC;AAAA,IACpG;AACA,QAAI,QAAQ,SAAS,GAAG;AACtB,iBAAW,KAAK,QAAS,KAAI,KAAK,EAAE,MAAM,QAAQ,cAAc,EAAE,aAAa,SAAS,EAAE,QAAQ,CAAC;AACnG,UAAI,KAAM,KAAI,KAAK,EAAE,MAAM,QAAQ,SAAS,KAAK,CAAC;AAAA,IACpD,WAAW,UAAU,SAAS,GAAG;AAC/B,UAAI,KAAK,EAAE,MAAM,aAAa,SAAS,QAAQ,MAAM,YAAY,UAAU,CAAC;AAAA,IAC9E,OAAO;AACL,UAAI,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,KAAK,CAAC;AAAA,IAC1C;AAAA,EACF;AACA,SAAO;AACT;AAOA,SAAS,iBAAiB,UAAwB,KAAyD;AACzG,QAAM,YAAY,oBAAI,IAAoB;AAC1C,aAAW,WAAW,UAAU;AAC9B,QAAI,OAAO,QAAQ,YAAY,SAAU;AACzC,eAAW,SAAS,QAAQ,SAAS;AACnC,UAAI,MAAM,SAAS,WAAY,WAAU,IAAI,MAAM,IAAI,MAAM,IAAI;AAAA,IACnE;AAAA,EACF;AAEA,QAAM,MAAsC,CAAC;AAC7C,MAAI,IAAK,KAAI,KAAK,EAAE,MAAM,UAAU,SAAS,IAAI,CAAC;AAClD,aAAW,WAAW,UAAU;AAC9B,QAAI,QAAQ,SAAS,SAAU;AAC/B,QAAI,OAAO,QAAQ,YAAY,UAAU;AACvC,UAAI,KAAK,EAAE,MAAM,QAAQ,MAAM,SAAS,QAAQ,QAAQ,CAAC;AACzD;AAAA,IACF;AAEA,QAAI,OAAO;AACX,UAAM,YAA4C,CAAC;AACnD,UAAM,UAAyD,CAAC;AAChE,eAAW,SAAS,QAAQ,SAAS;AACnC,UAAI,MAAM,SAAS,OAAQ,SAAQ,MAAM;AAAA,eAChC,MAAM,SAAS,YAAY;AAClC,kBAAU,KAAK;AAAA,UACb,MAAM;AAAA,UACN,UAAU,EAAE,OAAO,UAAU,QAAQ,MAAM,MAAM,MAAM,WAAW,MAAM,MAAM;AAAA,QAChF,CAAC;AAAA,MACH,WAAW,MAAM,SAAS,eAAe;AACvC,gBAAQ,KAAK,EAAE,WAAW,MAAM,aAAa,SAAS,MAAM,QAAQ,CAAC;AAAA,MACvE;AAAA,IACF;AACA,QAAI,QAAQ,SAAS,GAAG;AACtB,iBAAW,UAAU,SAAS;AAC5B,YAAI,KAAK;AAAA,UACP,MAAM;AAAA,UACN,WAAW,UAAU,IAAI,OAAO,SAAS,KAAK,OAAO;AAAA,UACrD,SAAS,OAAO;AAAA,QAClB,CAAC;AAAA,MACH;AACA,UAAI,KAAM,KAAI,KAAK,EAAE,MAAM,QAAQ,SAAS,KAAK,CAAC;AAAA,IACpD,WAAW,UAAU,SAAS,GAAG;AAC/B,UAAI,KAAK,EAAE,MAAM,aAAa,SAAS,MAAM,YAAY,UAAU,CAAC;AAAA,IACtE,OAAO;AACL,UAAI,KAAK,EAAE,MAAM,QAAQ,MAAM,SAAS,KAAK,CAAC;AAAA,IAChD;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,YAAY,MAA8D;AACjF,MAAI,CAAC,KAAK,SAAS,KAAK,MAAM,WAAW,EAAG,QAAO;AACnD,SAAO,KAAK,MAAM,IAAI,CAAC,OAAO;AAAA,IAC5B,MAAM;AAAA,IACN,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,EAAE,eAAe,IAAI,YAAY,EAAE,WAAW;AAAA,EACvF,EAAE;AACJ;AAGA,SAAS,iBAAiB,IAAuC;AAC/D,MAAI,OAAO,OAAW,QAAO;AAC7B,MAAI,OAAO,UAAU,OAAO,OAAQ,QAAO;AAC3C,SAAO,EAAE,MAAM,YAAY,UAAU,EAAE,MAAM,GAAG,KAAK,EAAE;AACzD;AAEA,SAAS,iBACP,OACA,UACA,MACA,KACgE;AAChE,QAAM,MAAM,WAAW,MAAM,QAAQ;AACrC,QAAM,SAAuB,CAAC;AAC9B,MAAI,IAAK,QAAO,KAAK,EAAE,MAAM,UAAU,SAAS,IAAI,CAAC;AACrD,aAAW,KAAK,SAAU,KAAI,EAAE,SAAS,SAAU,QAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,cAAc,EAAE,OAAO,EAAE,CAAC;AAClH,SAAO;AAAA,IACL,KAAK,GAAG,IAAI,mBAAmB;AAAA,IAC/B,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,eAAe,UAAU,IAAI,YAAY;AAAA,IAC3C;AAAA,IACA,MAAM,KAAK,UAAU;AAAA,MACnB;AAAA,MACA,YAAY,KAAK,aAAa;AAAA,MAC9B,aAAa,KAAK,eAAe;AAAA,MACjC,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AACF;AAEA,SAAS,iBACP,OACA,UACA,MACA,KACgE;AAChE,MAAI,CAAC,IAAI,cAAc;AACrB,UAAM,IAAIC,iBAAgB,iDAAiD;AAAA,EAC7E;AACA,QAAM,MAAM,WAAW,MAAM,QAAQ;AACrC,QAAM,OAAgC;AAAA,IACpC;AAAA,IACA,YAAY,KAAK,aAAa;AAAA,IAC9B,aAAa,KAAK,eAAe;AAAA,IACjC,UAAU,iBAAiB,UAAU,GAAG;AAAA,EAC1C;AACA,QAAM,QAAQ,YAAY,IAAI;AAC9B,MAAI,OAAO;AACT,SAAK,QAAQ;AACb,UAAM,KAAK,iBAAiB,KAAK,cAAc,MAAM;AACrD,QAAI,OAAO,OAAW,MAAK,cAAc;AAAA,EAC3C;AACA,MAAI,UAAU,OAAO,KAAK,MAAM;AAC9B,SAAK,mBAAmB,KAAK,mBAAmB;AAAA,EAClD;AACA,SAAO;AAAA,IACL,KAAK,GAAG,IAAI,mBAAmB;AAAA,IAC/B,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,eAAe,UAAU,IAAI,YAAY;AAAA,IAC3C;AAAA,IACA,MAAM,KAAK,UAAU,IAAI;AAAA,EAC3B;AACF;AAEA,SAAS,qBACP,OACA,UACA,MACA,KACgE;AAChE,MAAI,CAAC,IAAI,kBAAkB;AACzB,UAAM,IAAIA,iBAAgB,2EAA2E;AAAA,EACvG;AACA,QAAM,MAAM,WAAW,MAAM,QAAQ;AACrC,QAAM,OAAgC;AAAA,IACpC;AAAA,IACA,YAAY,KAAK,aAAa;AAAA,IAC9B,aAAa,KAAK,eAAe;AAAA,IACjC,UAAU,iBAAiB,UAAU,GAAG;AAAA,EAC1C;AACA,QAAM,QAAQ,YAAY,IAAI;AAC9B,MAAI,OAAO;AACT,SAAK,QAAQ;AACb,UAAM,KAAK,iBAAiB,KAAK,cAAc,MAAM;AACrD,QAAI,OAAO,OAAW,MAAK,cAAc;AAAA,EAC3C;AACA,SAAO;AAAA,IACL,KAAK,GAAG,IAAI,mBAAmB;AAAA,IAC/B,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,eAAe,UAAU,IAAI,gBAAgB;AAAA,IAC/C;AAAA,IACA,MAAM,KAAK,UAAU,IAAI;AAAA,EAC3B;AACF;AAOA,SAAS,kBACP,OACA,UACA,MACA,KACgE;AAChE,MAAI,CAAC,IAAI,mBAAmB;AAC1B,UAAM,IAAIA,iBAAgB,mDAAmD;AAAA,EAC/E;AACA,QAAM,MAAM,WAAW,MAAM,QAAQ;AACrC,QAAM,WAAW,UAAU,OAAO,MAAM;AACxC,QAAM,UAAkC;AAAA,IACtC,gBAAgB;AAAA,IAChB,eAAe,UAAU,IAAI,iBAAiB;AAAA,EAChD;AACA,MAAI,IAAI,yBAA0B,SAAQ,qBAAqB,IAAI,IAAI;AACvE,MAAI,IAAI,6BAA8B,SAAQ,yBAAyB,IAAI,IAAI;AAC/E,QAAM,QAAQ,YAAY,IAAI;AAC9B,MAAI,UAAU;AACZ,QAAI,OAAO,KAAK,eAAe,UAAU;AACvC,YAAM,IAAIA,iBAAgB,gEAAgE;AAAA,IAC5F;AACA,WAAO;AAAA,MACL,KAAK,GAAG,IAAI,mBAAmB;AAAA,MAC/B;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACnB;AAAA,QACA,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,UAAU,iBAAiB,UAAU,GAAG;AAAA,QACxC,SAAS;AAAA,UACP,aAAa,KAAK,aAAa;AAAA,UAC/B,aAAa,KAAK,eAAe;AAAA,QACnC;AAAA,QACA,GAAI,SAAS,KAAK,eAAe,SAAS,EAAE,MAAM,IAAI,CAAC;AAAA,MACzD,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO;AAAA,IACL,KAAK,GAAG,IAAI,mBAAmB;AAAA,IAC/B;AAAA,IACA,MAAM,KAAK,UAAU;AAAA,MACnB;AAAA,MACA,YAAY,KAAK,aAAa;AAAA,MAC9B,aAAa,KAAK,eAAe;AAAA,MACjC,UAAU,iBAAiB,UAAU,GAAG;AAAA,MACxC,GAAI,QACA,EAAE,OAAO,aAAa,iBAAiB,KAAK,cAAc,MAAM,EAAE,IAClE,CAAC;AAAA,IACP,CAAC;AAAA,EACH;AACF;AAuBA,SAAS,uBAAuB,QAAqD;AACnF,UAAQ,QAAQ;AAAA,IACd,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO,SAAS,UAAU;AAAA,EAC9B;AACF;AAgBA,SAAS,eACP,MAUA;AACA,QAAM,IAAI;AACV,QAAM,aAA4B,EAAE,WAAW,CAAC,GAC7C,OAAO,CAAC,MAAM,EAAE,SAAS,cAAc,OAAO,EAAE,OAAO,YAAY,OAAO,EAAE,SAAS,QAAQ,EAC7F,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAK,MAAM,EAAE,MAAO,WAAW,EAAE,SAAS,CAAC,EAAE,EAAE;AACtE,SAAO;AAAA,IACL,SAAS,EAAE,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM,GAAG,QAAQ;AAAA,IAC5D,OAAO,EAAE,OAAO,gBAAgB;AAAA,IAChC,QAAQ,EAAE,OAAO,iBAAiB;AAAA,IAClC,WAAW,EAAE,OAAO,2BAA2B;AAAA,IAC/C,YAAY,EAAE,OAAO,+BAA+B;AAAA,IACpD,OAAO,EAAE;AAAA,IACT,WAAW,UAAU,SAAS,IAAI,YAAY;AAAA,IAC9C,YAAY,uBAAuB,EAAE,WAAW;AAAA,EAClD;AACF;AAEA,SAAS,YAAY,MAAmE;AACtF,QAAM,IAAI;AACV,QAAM,OACJ,EAAE,aAAa,CAAC,GAAG,SAAS,OAAO,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,KAAK;AAC1E,SAAO;AAAA,IACL,SAAS;AAAA,IACT,OAAO,EAAE,eAAe,oBAAoB;AAAA,IAC5C,QAAQ,EAAE,eAAe,wBAAwB;AAAA,EACnD;AACF;AAEA,SAAS,UAAU,MAAmF;AACpG,QAAM,IAAI;AACV,SAAO;AAAA,IACL,SAAS,EAAE,UAAU,CAAC,GAAG,SAAS,WAAW;AAAA,IAC7C,OAAO,EAAE,OAAO,iBAAiB;AAAA,IACjC,QAAQ,EAAE,OAAO,qBAAqB;AAAA,IACtC,OAAO,EAAE;AAAA,EACX;AACF;AA8BA,SAAS,oBAAoB,QAAqD;AAChF,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO,SAAS,UAAU;AAAA,EAC9B;AACF;AAGA,SAAS,cAAc,KAAkD;AACvE,MAAI,CAAC,IAAK,QAAO,CAAC;AAClB,MAAI;AACF,UAAM,IAAI,KAAK,MAAM,GAAG;AACxB,WAAO,OAAO,MAAM,YAAY,MAAM,OAAQ,IAAgC,CAAC;AAAA,EACjF,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAMA,SAAS,YAAY,MAOnB;AACA,QAAM,IAAI;AACV,QAAM,SAAS,EAAE,UAAU,CAAC;AAC5B,QAAM,aAA4B,QAAQ,SAAS,cAAc,CAAC,GAC/D,OAAO,CAAC,MAAM,OAAO,EAAE,UAAU,SAAS,QAAQ,EAClD,IAAI,CAAC,GAAG,OAAO;AAAA,IACd,IAAI,EAAE,MAAM,QAAQ,CAAC;AAAA,IACrB,MAAM,EAAE,SAAU;AAAA,IAClB,WAAW,cAAc,EAAE,UAAU,SAAS;AAAA,EAChD,EAAE;AACJ,SAAO;AAAA,IACL,SAAS,QAAQ,SAAS,WAAW;AAAA,IACrC,OAAO,EAAE,OAAO,iBAAiB;AAAA,IACjC,QAAQ,EAAE,OAAO,qBAAqB;AAAA,IACtC,OAAO,EAAE;AAAA,IACT,WAAW,UAAU,SAAS,IAAI,YAAY;AAAA,IAC9C,YAAY,oBAAoB,QAAQ,aAAa;AAAA,EACvD;AACF;AAGA,SAAS,gBAAgB,MAOvB;AACA,QAAM,WAAW;AACjB,QAAM,aAA4B,SAAS,SAAS,cAAc,CAAC,GAChE,OAAO,CAAC,SAAS,OAAO,KAAK,UAAU,SAAS,QAAQ,EACxD,IAAI,CAAC,MAAM,WAAW;AAAA,IACrB,IAAI,QAAQ,KAAK;AAAA,IACjB,MAAM,KAAK,SAAU;AAAA,IACrB,WACE,OAAO,KAAK,UAAU,cAAc,WAChC,cAAc,KAAK,SAAS,SAAS,IACpC,KAAK,UAAU,aAAa,CAAC;AAAA,EACtC,EAAE;AACJ,MAAI;AACJ,MAAI,UAAU,SAAS,EAAG,cAAa;AAAA,WAC9B,SAAS,gBAAgB,SAAU,cAAa;AAAA,WAChD,SAAS,KAAM,cAAa;AAAA,WAC5B,SAAS,YAAa,cAAa;AAC5C,SAAO;AAAA;AAAA;AAAA,IAGL,SAAS,SAAS,SAAS,WAAW;AAAA,IACtC,OAAO,SAAS,qBAAqB;AAAA,IACrC,QAAQ,SAAS,cAAc;AAAA,IAC/B,OAAO,SAAS;AAAA,IAChB,WAAW,UAAU,SAAS,IAAI,YAAY;AAAA,IAC9C;AAAA,EACF;AACF;AAmBA,eAAe,gBACb,UACA,SACA,WACA,QACA,QACA,OACyE;AAMzE,WAAS,gBAAgB,KAA2B;AAClD,4BAAwB,UAAU,SAAS,KAAK,GAAG;AACnD,UAAM;AAAA,EACR;AAEA,MAAI;AACJ,WAAS,UAAU,GAAG,WAAW,2BAA2B,WAAW;AACrE,QAAI;AACF,YAAM,WAAW,MAAM,UAAU,QAAQ,KAAK;AAAA,QAC5C,QAAQ;AAAA,QACR,SAAS,QAAQ;AAAA,QACjB,MAAM,QAAQ;AAAA,QACd;AAAA,MACF,CAAC;AACD,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,OAAO,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACjD,cAAM,YAAY,sBAAsB,SAAS,MAAM;AACvD,cAAM,MAAqB;AAAA,UACzB;AAAA,UACA,QAAQ,SAAS;AAAA,UACjB;AAAA,UACA,SAAS,GAAG,QAAQ,IAAI,OAAO,SAAS,MAAM,CAAC,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,QACxE;AACA,gBAAQ,OAAO,sBAAsB,EAAE,UAAU,QAAQ,SAAS,QAAQ,QAAQ,CAAC;AACnF,YAAI,CAAC,IAAI,aAAa,YAAY,2BAA2B;AAC3D,cAAI,IAAI,UAAW,iBAAgB,GAAG;AACtC,gBAAM;AAAA,QACR;AACA,kBAAU;AAAA,MACZ,OAAO;AACL,cAAM,mBAAmB,SAAS,QAAQ,IAAI,mBAAmB,KAAK;AACtE,8BAAsB,QAAQ;AAC9B,eAAO,EAAE,MAAM,MAAM,SAAS,KAAK,GAAG,kBAAkB,UAAU,QAAQ;AAAA,MAC5E;AAAA,IACF,SAAS,GAAG;AACV,UAAI,aAAa,gBAAgB,EAAE,SAAS,aAAc,OAAM;AAChE,UAAI,OAAO,MAAM,YAAY,MAAM,QAAQ,eAAe,GAAG;AAC3D,cAAM,MAAM;AACZ,YAAI,CAAC,IAAI,aAAa,YAAY,2BAA2B;AAC3D,cAAI,IAAI,UAAW,iBAAgB,GAAG;AACtC,gBAAM;AAAA,QACR;AACA,kBAAU;AAAA,MACZ,OAAO;AACL,cAAM,MAAqB;AAAA,UACzB;AAAA,UACA,QAAQ;AAAA,UACR,WAAW;AAAA,UACX,SAAS,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AAAA,QACpD;AACA,YAAI,YAAY,0BAA2B,iBAAgB,GAAG;AAC9D,kBAAU;AAAA,MACZ;AAAA,IACF;AAEA,UAAM,YAAY,iBAAiB,UAAU,CAAC;AAC9C,UAAM,MAAM,WAAW,MAAM;AAAA,EAC/B;AAEA,0BAAwB,UAAU,SAAS,KAAK,GAAG;AACnD,QAAM,WAAY,EAAE,UAAU,QAAQ,GAAG,WAAW,OAAO,SAAS,YAAY;AAClF;AAEA,SAAS,gBAAgB,KAAoC;AAC3D,SACE,OAAO,QAAQ,YACf,QAAQ,QACR,OAAQ,IAA6B,WAAW,YAChD,OAAQ,IAA8B,YAAY,YAClD,OAAQ,IAA+B,aAAa;AAExD;AAeA,IAAM,yBAAyB,oBAAI,IAAiB,CAAC,aAAa,QAAQ,YAAY,OAAO,CAAC;AAE9F,SAAS,KAAK,MAAe,MAAkB,eAAkC;AAC/E,MAAI,KAAK,OAAO;AAEd,UAAM,IAAI,KAAK;AACf,QAAI,EAAE,WAAW,QAAQ,EAAG,QAAO,EAAE,SAAS,EAAE,UAAU,aAAa,OAAO,EAAE,EAAE;AAClF,QAAI,EAAE,WAAW,QAAQ,EAAG,QAAO,EAAE,SAAS,EAAE,UAAU,UAAU,OAAO,EAAE,EAAE;AAC/E,QAAI,EAAE,WAAW,MAAM,EAAG,QAAO,EAAE,SAAS,EAAE,UAAU,QAAQ,OAAO,EAAE,EAAE;AAC3E,QAAI,EAAE,WAAW,UAAU,EAAG,QAAO,EAAE,SAAS,EAAE,UAAU,YAAY,OAAO,EAAE,EAAE;AACnF,QAAI,EAAE,WAAW,MAAM,EAAG,QAAO,EAAE,SAAS,EAAE,UAAU,SAAS,OAAO,EAAE,EAAE;AAC5E,WAAO,EAAE,SAAS,EAAE,UAAU,QAAQ,OAAO,EAAE,EAAE;AAAA,EACnD;AACA,QAAM,cAAc,kBAAkB,KAAK,wBAAwB;AACnE,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO;AAAA,QACL,SAAS,EAAE,UAAU,YAAY,OAAO,OAAO,SAAS,UAAU;AAAA,QAClE,UAAU,EAAE,UAAU,QAAQ,OAAO,OAAO,KAAK,SAAS;AAAA,MAC5D;AAAA,IACF,KAAK;AACH,aAAO,EAAE,SAAS,EAAE,UAAU,QAAQ,OAAO,OAAO,KAAK,SAAS,EAAE;AAAA,IACtE,KAAK;AACH,aAAO,cACH;AAAA,QACE,SAAS,EAAE,UAAU,UAAU,OAAO,OAAO,OAAO,MAAM;AAAA,QAC1D,UAAU,EAAE,UAAU,aAAa,OAAO,OAAO,UAAU,MAAM;AAAA,MACnE,IACA;AAAA,QACE,SAAS,EAAE,UAAU,aAAa,OAAO,OAAO,UAAU,MAAM;AAAA,QAChE,UAAU,EAAE,UAAU,UAAU,OAAO,OAAO,OAAO,MAAM;AAAA,MAC7D;AAAA,IACN,KAAK;AACH,aAAO;AAAA,QACL,SAAS,EAAE,UAAU,QAAQ,OAAO,OAAO,KAAK,KAAK;AAAA,QACrD,UAAU,EAAE,UAAU,aAAa,OAAO,OAAO,UAAU,KAAK;AAAA,MAClE;AAAA,IACF,KAAK;AAAA,IACL;AACE,aAAO,cACH;AAAA,QACE,SAAS,EAAE,UAAU,UAAU,OAAO,OAAO,OAAO,MAAM;AAAA,QAC1D,UAAU,EAAE,UAAU,aAAa,OAAO,OAAO,UAAU,SAAS;AAAA,MACtE,IACA;AAAA,QACE,SAAS,EAAE,UAAU,aAAa,OAAO,OAAO,UAAU,SAAS;AAAA,QACnE,UAAU,EAAE,UAAU,UAAU,OAAO,OAAO,OAAO,MAAM;AAAA,MAC7D;AAAA,EACR;AACF;AAcA,SAAS,iBAAiB,MAAsC;AAC9D,QAAM,OAA+B,CAAC;AACtC,MAAI,KAAK,QAAS,MAAK,UAAU,KAAK;AACtC,MAAI,KAAK,SAAU,MAAK,WAAW,KAAK;AACxC,MAAI,KAAK,MAAO,MAAK,QAAQ,KAAK;AAClC,MAAI,KAAK,MAAO,MAAK,QAAQ,KAAK;AAClC,SAAO,OAAO,KAAK,IAAI,EAAE,SAAS,IAAI,KAAK,UAAU,IAAI,IAAI;AAC/D;AAEA,eAAe,QACb,KACA,UACA,MACA,KACA,WACA,QACA,OACgP;AAChP,MAAI;AACJ,UAAQ,IAAI,UAAU;AAAA,IACpB,KAAK;AACH,YAAM,sBAAsB,IAAI,OAAO,UAAU,MAAM,GAAG;AAC1D;AAAA,IACF,KAAK,UAAU;AAKb,YAAM,aAAa,MAAM,kBAAkB,KAAK,SAAS;AACzD,YAAM,mBAAmB,IAAI,OAAO,UAAU,MAAM,KAAK,WAAW,OAAO,WAAW,OAAO;AAC7F;AAAA,IACF;AAAA,IACA,KAAK;AACH,YAAM,iBAAiB,IAAI,OAAO,UAAU,MAAM,GAAG;AACrD;AAAA,IACF,KAAK;AACH,YAAM,iBAAiB,IAAI,OAAO,UAAU,MAAM,GAAG;AACrD;AAAA,IACF,KAAK;AACH,YAAM,qBAAqB,IAAI,OAAO,UAAU,MAAM,GAAG;AACzD;AAAA,IACF,KAAK;AACH,YAAM,kBAAkB,IAAI,OAAO,UAAU,MAAM,GAAG;AACtD;AAAA,EACJ;AAEA,QAAM,cAAc,iBAAiB,IAAI;AACzC,MAAI,YAAa,KAAI,QAAQ,iBAAiB,IAAI;AAClD,QAAM,EAAE,MAAM,kBAAkB,SAAS,IAAI,MAAM;AAAA,IACjD,IAAI;AAAA,IACJ;AAAA,IACA;AAAA,IACA,KAAK;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACA,UAAQ,IAAI,UAAU;AAAA,IACpB,KAAK;AACH,aAAO,EAAE,QAAQ,eAAe,IAAI,GAAG,kBAAkB,SAAS;AAAA,IACpE,KAAK;AACH,aAAO,EAAE,QAAQ,YAAY,IAAI,GAAG,kBAAkB,SAAS;AAAA,IACjE,KAAK;AACH,aAAO,EAAE,QAAQ,UAAU,IAAI,GAAG,kBAAkB,SAAS;AAAA,IAC/D,KAAK;AACH,aAAO,EAAE,QAAQ,YAAY,IAAI,GAAG,kBAAkB,SAAS;AAAA,IACjE,KAAK;AACH,aAAO,EAAE,QAAQ,YAAY,IAAI,GAAG,kBAAkB,SAAS;AAAA,IACjE,KAAK;AACH,aAAO;AAAA,QACL,QAAQ,IAAI,UAAU,OAAO,MAAM,OAAO,gBAAgB,IAAI,IAAI,YAAY,IAAI;AAAA,QAClF;AAAA,QACA;AAAA,MACF;AAAA,EACJ;AACF;AA0BA,eAAsB,SACpB,UACA,KACA,OAAmB,CAAC,GACpB,OAAgB,CAAC,GACoB;AACrC,MAAI,SAAS,WAAW,GAAG;AACzB,UAAM,IAAIA,iBAAgB,4BAA4B;AAAA,EACxD;AACA,MAAI,CAAC,IAAI,qBAAqB;AAC5B,UAAM,IAAIA,iBAAgB,0CAA0C;AAAA,EACtE;AACA,QAAM,YAAY,KAAK,SAAS;AAChC,QAAM,MAAM,KAAK,QAAQ,MAAM,KAAK,IAAI;AACxC,QAAM,SAAS,KAAK;AACpB,QAAM,YAAY,IAAI;AAEtB,QAAM,OAAgB,KAAK,QAAQ;AACnC,QAAM,SAAS,WAAW,MAAM,QAAQ;AACxC,QAAM,gBAAgB,eAAe,UAAU,MAAM;AACrD,MAAI,QAAQ,KAAK,MAAM,MAAM,aAAa;AAI1C,MACE,IAAI,mBACJ,SAAS,UACT,CAAC,KAAK,SACN,IAAI,mBACJ;AACA,YAAQ,EAAE,SAAS,EAAE,UAAU,SAAS,OAAO,OAAO,MAAM,KAAK,GAAG,UAAU,MAAM,QAAQ;AAAA,EAC9F;AAGA,MACE,IAAI,uBACJ,SAAS,eACT,CAAC,KAAK,SACN,IAAI,mBACJ;AACA,YAAQ,EAAE,SAAS,EAAE,UAAU,SAAS,OAAO,OAAO,MAAM,UAAU,GAAG,UAAU,MAAM,QAAQ;AAAA,EACnG;AAGA,QAAM,KAAK,IAAI;AACf,QAAM,WAAW,kBAAkB,QAAQ,IAAI,CAAC,CAAC;AACjD,QAAM,WAAW,oBAAoB,SAAS,IAAI,CAAC,CAAC;AACpD,MAAI,IAAI;AACN,QAAI,KAAK,gBAAgB,QAAW;AAClC,YAAM,MAAM,MAAM,GAAG,IAAI,QAAQ,EAAE,MAAM,MAAM,IAAI;AACnD,YAAM,QAAQ,WAAW,OAAO,GAAG;AACnC,UAAI,SAAS,KAAK,aAAa;AAC7B,eAAO;AAAA,UACL,IAAI,eAAe,0BAA0B;AAAA,YAC3C,UAAU;AAAA,YACV,aAAa,KAAK;AAAA,UACpB,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AACA,QAAI,KAAK,kBAAkB,QAAW;AACpC,YAAM,MAAM,MAAM,GAAG,IAAI,QAAQ,EAAE,MAAM,MAAM,IAAI;AACnD,YAAM,QAAQ,WAAW,OAAO,GAAG;AACnC,UAAI,SAAS,KAAK,eAAe;AAC/B,eAAO;AAAA,UACL,IAAI,eAAe,4BAA4B;AAAA,YAC7C,UAAU;AAAA,YACV,eAAe,KAAK;AAAA,UACtB,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,aAAiF,CAAC;AAExF,MAAI,YAAY,CAAC,MAAM,SAAS,MAAM,QAAQ,EAAE,OAAO,OAAO;AAG9D,MAAI,KAAK,SAAS,KAAK,MAAM,SAAS,GAAG;AACvC,gBAAY,UAAU,OAAO,CAAC,MAAM,uBAAuB,IAAI,EAAE,QAAQ,CAAC;AAC1E,QAAI,UAAU,WAAW,GAAG;AAC1B,YAAM,IAAIA;AAAA,QACR,kDAAkD,CAAC,GAAG,sBAAsB,EAAE,KAAK,IAAI,CAAC,YAAY,IAAI;AAAA,MAC1G;AAAA,IACF;AAAA,EACF;AACA,aAAW,CAAC,UAAU,GAAG,KAAK,UAAU,QAAQ,GAAG;AAEjD,QAAI,sBAAsB,IAAI,UAAU,GAAG,GAAG;AAC5C,cAAQ,OAAO,4BAA4B,EAAE,UAAU,IAAI,SAAS,CAAC;AACrE,iBAAW,KAAK,EAAE,UAAU,IAAI,UAAU,SAAS,wBAAwB,CAAC;AAC5E;AAAA,IACF;AACA,QAAI,KAAK,QAAQ,SAAS;AACxB,aAAO;AAAA,QACL,IAAIC,eAAc,oBAAoB,EAAE,UAAU,IAAI,UAAU,OAAO,IAAI,MAAM,CAAC;AAAA,MACpF;AAAA,IACF;AACA,QAAI;AACF,YAAM,SAAS,MAAM,QAAQ,KAAK,UAAU,MAAM,KAAK,WAAW,QAAQ,GAAG;AAG7E,UAAI,CAAC,OAAO,OAAO,WAAW,EAAE,OAAO,OAAO,aAAa,OAAO,OAAO,UAAU,SAAS,IAAI;AAC9F,cAAM,EAAE,UAAU,IAAI,UAAU,QAAQ,KAAK,WAAW,OAAO,SAAS,gBAAgB;AAAA,MAC1F;AACA,cAAQ,OAAO,gBAAgB;AAAA,QAC7B,UAAU,IAAI;AAAA,QACd,OAAO,IAAI;AAAA,QACX;AAAA,QACA;AAAA,QACA,UAAU,OAAO;AAAA,QACjB,OAAO,KAAK;AAAA,QACZ,SAAS,KAAK;AAAA,QACd,OAAO,KAAK;AAAA,QACZ,UAAU,KAAK;AAAA,MACjB,CAAC;AACD,YAAM,YAAuB;AAAA,QAC3B,SAAS,OAAO,OAAO;AAAA,QACvB,UAAU,IAAI;AAAA,QACd,OAAO,OAAO,OAAO,SAAS,IAAI;AAAA,QAClC;AAAA,QACA,QAAQ;AAAA,UACN,OAAO,OAAO,OAAO;AAAA,UACrB,QAAQ,OAAO,OAAO;AAAA,UACtB,WAAW,OAAO,OAAO;AAAA,UACzB,YAAY,OAAO,OAAO;AAAA,QAC5B;AAAA,QACA,SAAS,IAAI,IAAI;AAAA,QACjB,UAAU,OAAO;AAAA,QACjB,kBAAkB,OAAO;AAAA,QACzB,YAAY,OAAO,OAAO;AAAA,QAC1B,WAAW,OAAO,OAAO;AAAA,MAC3B;AACA,YAAM,UAAU,gBAAgB,UAAU,QAAQ,UAAU,KAAK;AACjE,UAAI,KAAK,eAAe,UAAa,UAAU,KAAK,YAAY;AAC9D,YAAI,OAAO,KAAK,gBAAgB,UAAa,KAAK,kBAAkB,SAAY;AAC9E,gBAAM,mBAAmB,IAAI,UAAU,UAAU,SAAS,IAAI;AAAA,QAChE;AACA,eAAO;AAAA,UACL,IAAI,eAAe,yBAAyB;AAAA,YAC1C;AAAA,YACA,YAAY,KAAK;AAAA,YACjB,OAAO,UAAU;AAAA,YACjB,QAAQ,UAAU;AAAA,UACpB,CAAC;AAAA,QACH;AAAA,MACF;AAIA,UAAI,OAAO,KAAK,gBAAgB,UAAa,KAAK,kBAAkB,SAAY;AAC9E,cAAM,mBAAmB,IAAI,UAAU,UAAU,SAAS,IAAI;AAAA,MAChE;AAEA,UAAI,KAAK,YAAY,KAAK,QAAQ;AAChC,cAAM,MAAoB;AAAA,UACxB,GAAG,KAAK;AAAA,UACR,OAAO,UAAU;AAAA,UACjB,UAAU,UAAU;AAAA,UACpB,MAAM,UAAU;AAAA,UAChB,aAAa,UAAU,OAAO;AAAA,UAC9B,cAAc,UAAU,OAAO;AAAA,UAC/B,iBAAiB,UAAU,OAAO,aAAa;AAAA,UAC/C,kBAAkB,UAAU,OAAO,cAAc;AAAA,UACjD,WAAW,UAAU;AAAA,UACrB;AAAA,UACA,QAAQ,SAAS,IAAI,CAAC;AAAA,QACxB;AACA,aAAK,SAAS,GAAG,EAAE,MAAM,CAAC,MAAe;AACvC,kBAAQ,OAAO,sBAAsB,EAAE,SAAS,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,EAAE,CAAC;AAAA,QAC9F,CAAC;AAAA,MACH;AACA,aAAO,EAAE,MAAM,WAAW,OAAO,KAAK;AAAA,IACxC,SAAS,GAAG;AACV,UAAI,aAAa,gBAAgB,EAAE,SAAS,cAAc;AACxD,eAAO;AAAA,UACL,IAAIA,eAAc,oBAAoB,EAAE,UAAU,IAAI,UAAU,OAAO,IAAI,MAAM,CAAC;AAAA,QACpF;AAAA,MACF;AACA,UAAI,gBAAgB,CAAC,GAAG;AACtB,mBAAW,KAAK,EAAE,UAAU,EAAE,UAAU,QAAQ,EAAE,QAAQ,SAAS,EAAE,QAAQ,CAAC;AAC9E,YAAI,EAAE,WAAW,OAAO,aAAa,UAAU,SAAS,GAAG;AACzD,iBAAO;AAAA,YACL,IAAI,eAAe,uBAAuB,EAAE,QAAQ,IAAI,EAAE,UAAU,WAAW,CAAC;AAAA,UAClF;AAAA,QACF;AACA,gBAAQ,OAAO,kBAAkB,EAAE,UAAU,IAAI,UAAU,QAAQ,EAAE,OAAO,CAAC;AAC7E;AAAA,MACF;AACA,iBAAW,KAAK,EAAE,UAAU,IAAI,UAAU,SAAS,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,EAAE,CAAC;AAAA,IACjG;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAIA,eAAc,4BAA4B,EAAE,UAAU,YAAY,MAAM,cAAc,CAAC;AAAA,EAC7F;AACF;AAkDA,gBAAuB,iBACrB,UACA,KACA,OAAwC,CAAC,GACG;AAC5C,MAAI,SAAS,WAAW,GAAG;AACzB,UAAM,IAAID,iBAAgB,4BAA4B;AAAA,EACxD;AACA,MAAI,CAAC,IAAI,qBAAqB;AAC5B,UAAM,IAAIA,iBAAgB,0CAA0C;AAAA,EACtE;AAEA,QAAM,OAAgB,KAAK,QAAQ,CAAC;AACpC,QAAM,YAAY,KAAK,SAAS;AAChC,QAAM,MAAM,KAAK,QAAQ,MAAM,KAAK,IAAI;AACxC,QAAM,SAAS,KAAK;AACpB,QAAM,YAAY,IAAI;AAEtB,QAAM,OAAgB,KAAK,QAAQ;AACnC,QAAM,SAAS,WAAW,MAAM,QAAQ;AACxC,QAAM,gBAAgB,eAAe,UAAU,MAAM;AACrD,QAAM,QAAQ,KAAK,MAAM,MAAM,aAAa;AAC5C,QAAM,YACJ,MAAM,QAAQ,aAAa,UAAU,CAAC,IAAI,gBAAgB,MAAM,UAAU,aAAa,cACnF,MAAM,WACN,MAAM;AAIZ,MAAI,UAAU,aAAa,aAAa;AACtC,UAAM,SAAS,MAAM,SAAS,UAAU,KAAK,MAAM,IAAI;AACvD,QAAI,OAAO,UAAU,QAAQ,OAAO,SAAS,MAAM;AACjD,YAAM,IAAIC,eAAc,4BAA4B,EAAE,OAAO,OAAO,MAAM,CAAC;AAAA,IAC7E;AACA,UAAM,OAAO,KAAK;AAClB,WAAO,OAAO;AAAA,EAChB;AAGA,MAAI,sBAAsB,UAAU,UAAU,GAAG,GAAG;AAClD,YAAQ,OAAO,4BAA4B,EAAE,UAAU,UAAU,SAAS,CAAC;AAE3E,UAAM,SAAS,MAAM,SAAS,UAAU,KAAK,MAAM,IAAI;AACvD,QAAI,OAAO,UAAU,QAAQ,OAAO,SAAS,MAAM;AACjD,YAAM,IAAIA,eAAc,4BAA4B,EAAE,OAAO,OAAO,MAAM,CAAC;AAAA,IAC7E;AACA,UAAM,OAAO,KAAK;AAClB,WAAO,OAAO;AAAA,EAChB;AAEA,QAAM,MAAM,sBAAsB,UAAU,OAAO,UAAU,MAAM,KAAK,IAAI;AAE5E,QAAM,oBAAoB,iBAAiB,IAAI;AAC/C,MAAI,kBAAmB,KAAI,QAAQ,iBAAiB,IAAI;AAExD,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,UAAU,IAAI,KAAK;AAAA,MAClC,QAAQ;AAAA,MACR,SAAS,IAAI;AAAA,MACb,MAAM,IAAI;AAAA;AAAA;AAAA,MAGV,QAAQ,KAAK,UAAU,YAAY,QAAQ,GAAM;AAAA,IACnD,CAAC;AAAA,EACH,SAAS,GAAG;AACV,QAAI,aAAa,gBAAgB,EAAE,SAAS,cAAc;AACxD,YAAM,IAAIA,eAAc,oBAAoB;AAAA,QAC1C,UAAU,UAAU;AAAA,QACpB,OAAO,UAAU;AAAA,MACnB,CAAC;AAAA,IACH;AACA,UAAM,IAAIA,eAAc,2BAA2B;AAAA,MACjD,SAAS,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AAAA,IACpD,CAAC;AAAA,EACH;AAEA,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,OAAO,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACjD,UAAM,YAAY,sBAAsB,SAAS,MAAM;AACvD,QAAI,aAAa,SAAS,WAAW,KAAK;AACxC,8BAAwB,UAAU,UAAU,GAAG;AAAA,IACjD;AAEA,UAAM,SAAS,MAAM,SAAS,UAAU,KAAK,MAAM,IAAI;AACvD,QAAI,OAAO,UAAU,QAAQ,OAAO,SAAS,MAAM;AACjD,YAAM,IAAIA,eAAc,4BAA4B;AAAA,QAClD,aAAa,GAAG,UAAU,QAAQ,IAAI,OAAO,SAAS,MAAM,CAAC,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,QACpF,OAAO,OAAO;AAAA,MAChB,CAAC;AAAA,IACH;AACA,UAAM,OAAO,KAAK;AAClB,WAAO,OAAO;AAAA,EAChB;AAEA,MAAI,CAAC,SAAS,MAAM;AAClB,UAAM,IAAIA,eAAc,oCAAoC;AAAA,MAC1D,UAAU,UAAU;AAAA,IACtB,CAAC;AAAA,EACH;AAGA,QAAM,UAAU,IAAI,YAAY;AAChC,MAAI,kBAAkB;AACtB,MAAI,cAAc;AAClB,MAAI,eAAe;AACnB,MAAI,YAAY;AAChB,MAAI,aAAa;AACjB,MAAI;AAGJ,QAAM,aAAa,oBAAI,IAAwD;AAC/E,MAAI;AACJ,QAAM,mBAAuC,SAAS,QAAQ,IAAI,mBAAmB,KAAK;AAE1F,QAAM,SAAS,SAAS,KAAK,UAAU;AACvC,MAAI,SAAS;AAEb,MAAI;AACF,WAAO,MAAM;AACX,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,UAAI,KAAM;AACV,gBAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AAGhD,YAAM,QAAQ,OAAO,MAAM,IAAI;AAE/B,eAAS,MAAM,IAAI,KAAK;AAExB,iBAAW,QAAQ,OAAO;AACxB,YAAI,CAAC,KAAK,WAAW,QAAQ,EAAG;AAChC,cAAM,OAAO,KAAK,MAAM,CAAC,EAAE,KAAK;AAChC,YAAI,SAAS,SAAU;AACvB,YAAI;AACJ,YAAI;AACF,kBAAQ,KAAK,MAAM,IAAI;AAAA,QACzB,QAAQ;AACN;AAAA,QACF;AAEA,gBAAQ,MAAM,MAAM;AAAA,UAClB,KAAK;AACH,0BAAc,MAAM,SAAS,OAAO,gBAAgB;AACpD,wBAAY,MAAM,SAAS,OAAO,2BAA2B;AAC7D,yBAAa,MAAM,SAAS,OAAO,+BAA+B;AAClE,wBAAY,MAAM,SAAS;AAC3B;AAAA,UACF,KAAK;AACH,gBACE,MAAM,eAAe,SAAS,cAC9B,OAAO,MAAM,UAAU,YACvB,OAAO,MAAM,cAAc,OAAO,YAClC,OAAO,MAAM,cAAc,SAAS,UACpC;AACA,yBAAW,IAAI,MAAM,OAAO,EAAE,IAAI,MAAM,cAAc,IAAI,MAAM,MAAM,cAAc,MAAM,MAAM,GAAG,CAAC;AAAA,YACtG;AACA;AAAA,UACF,KAAK;AACH,gBAAI,MAAM,OAAO,SAAS,gBAAgB,OAAO,MAAM,MAAM,SAAS,UAAU;AAC9E,iCAAmB,MAAM,MAAM;AAC/B,oBAAM,MAAM,MAAM;AAAA,YACpB,WACE,MAAM,OAAO,SAAS,sBACtB,OAAO,MAAM,MAAM,iBAAiB,YACpC,OAAO,MAAM,UAAU,UACvB;AACA,oBAAM,QAAQ,WAAW,IAAI,MAAM,KAAK;AACxC,kBAAI,MAAO,OAAM,QAAQ,MAAM,MAAM;AAAA,YACvC;AACA;AAAA,UACF,KAAK;AACH,2BAAe,MAAM,OAAO,iBAAiB;AAC7C,gBAAI,MAAM,OAAO,YAAa,oBAAmB,uBAAuB,MAAM,MAAM,WAAW;AAC/F;AAAA,UACF;AACE;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AAAA,EACF,UAAE;AACA,WAAO,YAAY;AAAA,EACrB;AAEA,wBAAsB,UAAU,QAAQ;AACxC,UAAQ,OAAO,wBAAwB;AAAA,IACrC,UAAU,UAAU;AAAA,IACpB,OAAO,UAAU;AAAA,IACjB;AAAA,IACA;AAAA,IACA,OAAO,KAAK;AAAA,IACZ,SAAS,KAAK;AAAA,IACd,OAAO,KAAK;AAAA,IACZ,UAAU,KAAK;AAAA,EACjB,CAAC;AAED,QAAM,YAA2B,CAAC,GAAG,WAAW,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO;AAAA,IACpE,IAAI,EAAE;AAAA,IACN,MAAM,EAAE;AAAA,IACR,WAAW,cAAc,EAAE,IAAI;AAAA,EACjC,EAAE;AAEF,SAAO;AAAA,IACL,SAAS;AAAA,IACT,UAAU,UAAU;AAAA,IACpB,OAAO,aAAa,UAAU;AAAA,IAC9B;AAAA,IACA,QAAQ,EAAE,OAAO,aAAa,QAAQ,cAAc,WAAW,WAAW;AAAA,IAC1E,SAAS,IAAI,IAAI;AAAA,IACjB,UAAU;AAAA,IACV;AAAA,IACA,YAAY;AAAA,IACZ,WAAW,UAAU,SAAS,IAAI,YAAY;AAAA,EAChD;AACF;AA4BO,SAAS,gBAAgB,UAAkB,SAA4B;AAC5E,MAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,QAAM,SAAS;AACf,QAAM,iBAAiB,SAAS,MAAM,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAEvE,MAAI,eAAe,SAAS,OAAQ,QAAO;AAG3C,QAAM,eAAe,oBAAI,IAAY;AACrC,aAAW,UAAU,SAAS;AAC5B,UAAM,SAAS,OAAO,MAAM,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC7D,aAAS,IAAI,GAAG,KAAK,OAAO,SAAS,QAAQ,KAAK;AAChD,YAAM,QAAQ,OAAO,MAAM,GAAG,IAAI,MAAM,EAAE,KAAK,GAAG;AAClD,mBAAa,IAAI,KAAK;AAAA,IACxB;AAAA,EACF;AAEA,MAAI,aAAa,SAAS,EAAG,QAAO;AAGpC,WAAS,IAAI,GAAG,KAAK,eAAe,SAAS,QAAQ,KAAK;AACxD,UAAM,QAAQ,eAAe,MAAM,GAAG,IAAI,MAAM,EAAE,KAAK,GAAG;AAC1D,QAAI,aAAa,IAAI,KAAK,EAAG,QAAO;AAAA,EACtC;AAEA,SAAO;AACT;","names":["InternalError","ValidationError","InternalError","ValidationError","InternalError"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/gcp-token.ts","../src/embed.ts"],"sourcesContent":["import {\n InternalError,\n RateLimitError,\n ValidationError,\n toErrorResponse,\n type FactoryResponse,\n} from '@latimer-woods-tech/errors';\nimport type { Logger } from '@latimer-woods-tech/logger';\nimport { fetchAdcAccessToken, mintGcpAccessToken, serviceAccountProjectId } from './gcp-token.js';\n\n/**\n * A tool the model may call. `parameters` is a JSON Schema object describing\n * the tool's input. Provider-agnostic; normalized per provider at request time.\n */\nexport interface LLMTool {\n name: string;\n description?: string;\n /** JSON Schema for the tool's input arguments. */\n parameters: Record<string, unknown>;\n}\n\n/**\n * A tool invocation requested by the model, normalized across providers.\n */\nexport interface LLMToolCall {\n /** Provider-assigned call id; echo it back in the matching tool_result. */\n id: string;\n name: string;\n /** Parsed argument object the model passed to the tool. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Structured content block for tool-calling conversations. The field shapes\n * mirror the Anthropic Messages wire format so they pass through unchanged.\n */\nexport type LLMContentBlock =\n | { type: 'text'; text: string }\n | { type: 'tool_use'; id: string; name: string; input: Record<string, unknown> }\n | { type: 'tool_result'; tool_use_id: string; content: string; is_error?: boolean };\n\n/**\n * Single chat message exchanged with an LLM provider.\n *\n * `content` is a plain string in the common case. For tool-calling\n * conversations it may be an array of {@link LLMContentBlock}s (e.g. an\n * assistant turn carrying `tool_use` blocks, or a user turn carrying\n * `tool_result` blocks). Providers that don't support tool-calling receive\n * the text projection of the content (see `contentToText`).\n */\nexport interface LLMMessage {\n role: 'user' | 'assistant' | 'system';\n content: string | LLMContentBlock[];\n}\n\n/**\n * Flattens message content to plain text for providers/paths that only handle\n * strings. `tool_use` blocks contribute nothing; `tool_result` blocks\n * contribute their textual content.\n */\nfunction contentToText(content: string | LLMContentBlock[]): string {\n if (typeof content === 'string') return content;\n return content\n .map((b) => (b.type === 'text' ? b.text : b.type === 'tool_result' ? b.content : ''))\n .join('');\n}\n\n/**\n * Resolves the system prompt: explicit `opts.system` wins, else the first\n * `system` message, flattened to text. Returns `undefined` when neither is set.\n */\nfunction systemText(opts: LLMOptions, messages: LLMMessage[]): string | undefined {\n if (opts.system !== undefined) return opts.system;\n const c = messages.find((m) => m.role === 'system')?.content;\n return c === undefined ? undefined : contentToText(c);\n}\n\n/**\n * Quality tier selected by the caller. Routing is workload-split:\n * - `fast` → Grok 4.3 with Anthropic Haiku fallback (routine drafts/small jobs)\n * - `balanced` → Anthropic Sonnet (default)\n * - `smart` → Anthropic Opus OR Gemini 2.5 Flash if input is long-context (>150k tokens estimated)\n * - `verifier` → Groq Llama (cheap second opinion; only used from verifier code path)\n * - `workbench` → DeepSeek Chat with Groq fallback (boring, reviewable, non-sensitive batch work)\n */\nexport type LLMTier = 'fast' | 'balanced' | 'smart' | 'verifier' | 'workbench';\n\n/**\n * Options that influence LLM completion behaviour.\n */\nexport interface LLMOptions {\n /** Quality tier; see {@link LLMTier}. Defaults to `balanced`. */\n tier?: LLMTier;\n /** Explicit model override. Takes precedence over tier. */\n model?: string;\n maxTokens?: number;\n temperature?: number;\n system?: string;\n /** Token budget above which we force long-context routing (Gemini). */\n longContextThreshold?: number;\n /** Per-call cancellation signal. Aborts the in-flight provider request. */\n signal?: AbortSignal;\n /** Optional run identifier stamped on ledger rows + logs. */\n runId?: string;\n /** Optional project identifier stamped on ledger rows + logs. */\n project?: string;\n /** Optional actor identifier (supervisor / worker / human). */\n actor?: string;\n /** Optional workload label used in logs and cost-policy call sites. */\n workload?: string;\n /** Grok reasoning effort. Defaults to `none` for cost-controlled fast/draft calls. */\n reasoningEffort?: 'none' | 'low' | 'medium' | 'high';\n /** Anthropic prompt-cache control. Defaults to `true` for `system` prompts ≥ 1024 tokens. */\n promptCache?: boolean;\n /**\n * Maximum estimated cost in USD for this completion.\n * This cap is enforced after the provider returns because it uses actual\n * response token counts to compute the final cost.\n * If the post-call estimated cost exceeds this cap, `complete` returns a\n * {@link RateLimitError} with code `LLM_COST_CAP_EXCEEDED` and\n * `completionStream` throws the same error.\n * Pricing is based on {@link MODEL_PRICE_PER_1M}; unknown models default to\n * Opus rates (conservative upper bound).\n */\n maxCostUsd?: number;\n /**\n * Org-level daily cost cap in USD. Requires `env.LLM_COST_KV` to be set.\n * When today's cumulative spend read from KV is >= this value, `complete`\n * returns a {@link RateLimitError} with code `LLM_DAILY_CAP_EXCEEDED`\n * without making any provider call. After a successful call the daily\n * accumulator is updated in KV (TTL: 48 h).\n */\n dailyCapUsd?: number;\n /**\n * Org-level monthly cost cap in USD. Requires `env.LLM_COST_KV` to be set.\n * Same enforcement pattern as {@link dailyCapUsd} but keyed by YYYY-MM.\n * KV TTL: 40 days.\n */\n monthlyCapUsd?: number;\n /**\n * Metering context. When supplied and `deps.onRecord` is set, a {@link LLMRecordRow}\n * is emitted after every successful completion. Errors are swallowed.\n */\n ledger?: LLMRecordContext;\n /**\n * Tools the model may call. When present, routing **fails closed** to\n * tool-capable providers — failover never falls back to a provider that\n * can't honour the tool schema. See {@link LLMResult.toolCalls}.\n */\n tools?: LLMTool[];\n /**\n * Tool-selection policy. `'auto'` (default when `tools` is set) lets the\n * model decide; `'none'` forbids tool use; `{ name }` forces a specific tool.\n */\n toolChoice?: 'auto' | 'none' | { name: string };\n}\n\n/**\n * Provider that produced an LLM response.\n */\nexport type LLMProvider = 'anthropic' | 'gemini' | 'groq' | 'grok' | 'deepseek' | 'local';\n\n/**\n * Result returned by a successful completion.\n */\nexport interface LLMResult {\n content: string;\n provider: LLMProvider;\n model: string;\n tier: LLMTier;\n tokens: { input: number; output: number; cacheRead?: number; cacheWrite?: number };\n latency: number;\n /** Number of attempts before success (1 = primary succeeded). */\n attempts: number;\n /** Monotonic request id from AI Gateway, if present in headers. */\n gatewayRequestId?: string;\n /**\n * Why generation stopped, normalized across providers. `'tool_use'` means\n * the model is requesting one or more tool calls (see {@link toolCalls}).\n */\n stopReason?: 'end' | 'tool_use' | 'max_tokens' | 'other';\n /**\n * Tool calls the model requested, normalized across providers. Present\n * (non-empty) when `stopReason === 'tool_use'`.\n */\n toolCalls?: LLMToolCall[];\n /** True when a fallback provider served instead of the tier's intended primary — see #4204. */\n degraded: boolean;\n}\n\n/**\n * Environment bindings required by {@link complete}.\n *\n * `AI_GATEWAY_BASE_URL` is REQUIRED in 0.3.0. All provider calls flow through the\n * Cloudflare AI Gateway for unified logging, rate limiting, and cost telemetry.\n * In test/dev the caller may pass a custom fetch impl that short-circuits this.\n */\nexport interface LLMEnv {\n AI_GATEWAY_BASE_URL: string;\n ANTHROPIC_API_KEY: string;\n GROQ_API_KEY: string;\n /** Optional — only required for `{ tier: 'workbench' }` or `deepseek-*` model overrides. */\n DEEPSEEK_API_KEY?: string;\n /** Optional — only required when caller passes `{ model: 'grok-*' }` override. */\n GROK_API_KEY?: string;\n /**\n * Cost optimization: when true, `fast`-tier calls route to the self-hosted GPU\n * (qwen3:8b via the `custom-local-fast-gpu` AI Gateway provider) FIRST, with the normal\n * fast route (Grok→Haiku) as automatic fallback on any error. Off by default →\n * fully dormant. Requires {@link LLMEnv.GPU_LLM_API_TOKEN}.\n */\n LLM_LOCAL_FIRST?: boolean;\n /**\n * Agentic-work opt-in: route the `workbench` tier to local Qwen 27B first,\n * with DeepSeek as the independent cloud fallback.\n */\n LLM_LOCAL_WORKBENCH?: boolean;\n /** Bearer for the local GPU proxy (`GPU_LLM_API_TOKEN` in Secret Manager). Required to enable local. */\n GPU_LLM_API_TOKEN?: string;\n /** Cloudflare Access service-token id for the local GPU app; forwarded by the gateway to the origin. */\n GPU_LLM_ACCESS_CLIENT_ID?: string;\n /** Cloudflare Access service-token secret for the local GPU app. */\n GPU_LLM_ACCESS_CLIENT_SECRET?: string;\n /**\n * GCP service-account JSON key (base64-encoded or raw) with `roles/aiplatform.user`.\n * The credential for the `gemini` leg in environments WITHOUT a metadata server\n * (Cloudflare Workers): the package exchanges it for a fresh access token per\n * isolate and caches it (see `./gcp-token.ts`).\n *\n * Optional. When neither this nor {@link LLMEnv.VERTEX_ACCESS_TOKEN} is set, the\n * leg falls back to Application Default Credentials — the GCP metadata server's\n * ambient service-account token (keyless; the default on Cloud Run / GCE). Off\n * GCP with no static credential, the metadata probe fails and the chain falls\n * through to its next leg.\n */\n GCP_SA_KEY?: string;\n /**\n * Pre-minted Google Cloud access token with `aiplatform.endpoints.predict`.\n * Explicit override, used only when {@link LLMEnv.GCP_SA_KEY} is absent and\n * before ADC is attempted. Valid for ~1 hour from minting, so it cannot be a\n * durable Worker secret.\n */\n VERTEX_ACCESS_TOKEN?: string;\n /** Vertex project. Defaults to the `GCP_SA_KEY`'s own `project_id`. */\n VERTEX_PROJECT?: string;\n /** Vertex location. Defaults to `us-central1`. */\n VERTEX_LOCATION?: string;\n /**\n * Optional KV store for org-level daily/monthly cost tracking and enforcement.\n * When provided alongside {@link LLMOptions.dailyCapUsd} or {@link LLMOptions.monthlyCapUsd},\n * `complete` will block calls that would exceed the declared cap.\n * Any KV-like store satisfying `get`/`put` works (e.g. Cloudflare KV, in-memory stub).\n */\n LLM_COST_KV?: CostKvStore;\n}\n\n/**\n * Minimal KV store interface for org-level LLM cost tracking.\n * Cloudflare KV satisfies this. An in-memory stub is sufficient for tests.\n */\nexport interface CostKvStore {\n get(key: string): Promise<string | null>;\n put(key: string, value: string, options?: { expirationTtl?: number }): Promise<void>;\n}\n\n/**\n * Caller-supplied context stamped on every metering row.\n * Mirrors the `LLMRecordContext` in `@latimer-woods-tech/llm-meter`; kept inline\n * to avoid a circular dependency (llm-meter imports llm).\n */\nexport interface LLMRecordContext {\n project: string;\n actor: string;\n runId?: string;\n workload?: string;\n tenantId?: string;\n}\n\n/**\n * Row shape passed to the optional {@link LLMDeps.onRecord} callback.\n * Callers can wire this directly to `recordCall` from `@latimer-woods-tech/llm-meter`.\n */\nexport interface LLMRecordRow extends LLMRecordContext {\n model: string;\n provider: LLMProvider;\n tier: LLMTier;\n /** True when a fallback provider served instead of the tier's intended primary — see #4204. */\n degraded: boolean;\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n latencyMs: number;\n costUsd: number;\n yyyyMm: string;\n}\n\n/**\n * Resolve the metering context for a call.\n *\n * `LLMOptions` carries the same four field names in TWO places: top-level\n * `project`/`actor`/`runId`/`workload` (documented for logs and cost-policy\n * call sites) and the nested {@link LLMOptions.ledger}, which is the ONLY one\n * the `onRecord` metering callback reads. Both shapes typecheck, so picking\n * the wrong one is silent — you get logging and no ledger row.\n *\n * Every caller picked the wrong one. Measured across the estate 2026-08-23:\n * **8 of 8** call sites that supply metering context set it at the top level\n * (`admin-studio` test-analyst + ai, `daily-brief` wisdom + insights,\n * `inbound-oracle`, `supervisor` reflect, `lead-gen`, `linkedin-publisher`)\n * and **0** use `ledger`. A 0% adoption rate on one of two identical-looking\n * APIs is a design defect, not eight independent mistakes — so the fix belongs\n * here rather than in eight call sites that already read as correct.\n *\n * Explicit `ledger` always wins; the top-level fields are a fallback. A call\n * needs BOTH `project` and `actor` to be meterable, because those two are the\n * required columns on the ledger row — a partial context would write a row\n * that cannot be attributed, which is worse than no row.\n */\nexport function resolveLedgerContext(opts: LLMOptions): LLMRecordContext | undefined {\n if (opts.ledger) return opts.ledger;\n if (!opts.project || !opts.actor) return undefined;\n const ctx: LLMRecordContext = { project: opts.project, actor: opts.actor };\n if (opts.runId !== undefined) ctx.runId = opts.runId;\n if (opts.workload !== undefined) ctx.workload = opts.workload;\n return ctx;\n}\n\n/**\n * Optional dependencies for {@link complete}.\n */\nexport interface LLMDeps {\n fetch?: typeof fetch;\n logger?: Logger;\n now?: () => number;\n /**\n * Optional metering callback. Called after every successful completion.\n * Errors are swallowed so metering never blocks the caller.\n * Wire to `recordCall` from `@latimer-woods-tech/llm-meter`.\n */\n onRecord?: (row: LLMRecordRow) => Promise<void>;\n}\n\n// Model catalogue — keep in sync with docs/architecture/FACTORY_V1.md § LLM substrate.\nconst MODELS = {\n anthropic: {\n // `claude-haiku-4-20250514` was never a model Anthropic served — it 404s\n // (verified live against /v1/messages). Date suffixes are never appended to\n // an alias; the alias is `claude-haiku-4-5`, which resolves server-side to\n // `claude-haiku-4-5-20251001`. This is the `fast` tier's FALLBACK, so the\n // 404 stayed invisible for as long as the Grok primary held.\n fast: 'claude-haiku-4-5',\n // Current-generation Sonnet/Opus (2026-08 refresh). The previous pins —\n // `claude-sonnet-4-6` / `claude-opus-4-7` — were a generation behind, and\n // the opus-4-7 leg was HARD-BROKEN: Claude 4.7+ rejects `temperature`\n // (400 \"`temperature` is deprecated for this model\", verified live\n // 2026-08-14) and buildAnthropicRequest always sent one, so every `smart`\n // call 400'd on its Anthropic primary and silently served Gemini Flash.\n // Sonnet 5 / Opus 5 cost the same list price or less than the models they\n // replace ($3/$15, $5/$25 per 1M). The sampling/thinking request-shape\n // differences these models introduce are handled in buildAnthropicRequest.\n balanced: 'claude-sonnet-5',\n smart: 'claude-opus-5',\n },\n gemini: {\n // `gemini-2.5-flash`, not `-pro`: the leg's job here is a fast, reliable,\n // JSON-returning fallback. Gemini 2.5 *Pro* mandates a thinking budget of\n // ≥128 tokens that is drawn from `maxOutputTokens` and CANNOT be disabled\n // (thinkingBudget=0 is rejected). On the render-runner's large judge /\n // generation prompts — and any low-`maxTokens` call (headline uses 40) —\n // the thinking phase exhausts the whole budget and Vertex returns 200 with\n // an empty candidate (finishReason MAX_TOKENS, no text), which the router\n // treats as a failed leg. Flash supports `thinkingBudget: 0` (set in\n // buildGeminiRequest), so text is always emitted. Both are Vertex-served.\n smart: 'gemini-2.5-flash',\n },\n groq: {\n // Groq deprecated `llama-3.3-70b-versatile` on 2026-08-16 — every\n // `verifier` call 404'd `model_not_found` from then on (the tier has no\n // fallback, so it was hard-broken; second time this tier has died to a\n // model retirement). `openai/gpt-oss-120b` is Groq's recommended\n // migration target and is served to our org (verified against /v1/models\n // and a live completion, 2026-08-23).\n verifier: 'openai/gpt-oss-120b',\n },\n grok: {\n fast: 'grok-4.3',\n },\n deepseek: {\n workbench: 'deepseek-chat',\n },\n local: {\n // Self-hosted qwen3:8b on the dedicated fast GPU, reached through the\n // `custom-local-fast-gpu` AI Gateway provider's native Ollama chat route.\n fast: 'qwen3:8b',\n workbench: 'qwen3.6:27b',\n },\n} as const;\n\nconst DEFAULT_MAX_TOKENS = 1024;\nconst DEFAULT_TEMPERATURE = 0.7;\n/** Vertex region used when the caller does not pin one. */\nconst DEFAULT_VERTEX_LOCATION = 'us-central1';\nconst DEFAULT_LONG_CONTEXT_THRESHOLD = 150_000; // tokens\n\n// ─── Per-provider exponential backoff constants ────────────────────────────\n/** Base delay in ms for the first retry. */\nconst BACKOFF_BASE_MS = 500;\n/** Maximum backoff cap in ms. */\nconst BACKOFF_CAP_MS = 8_000;\n/** Max random jitter added to each backoff delay, in ms. */\nconst BACKOFF_JITTER_MAX_MS = 250;\n/** Maximum number of attempts per provider (1 initial + 2 retries). */\nconst PER_PROVIDER_MAX_ATTEMPTS = 3;\n\n// ─── Per-provider cooldown state (module-level) ────────────────────────────\n/**\n * Tracks when a provider's cooldown period expires.\n * Keyed by {@link LLMProvider}; value is the `Date.now()` epoch ms at which\n * the cooldown expires. Absent key means \"not cooling down\".\n */\nconst providerCooldownUntil: Map<LLMProvider, number> = new Map();\n\n/** Cooldown duration in ms after a provider exhausts all retries. */\nconst PROVIDER_COOLDOWN_MS = 30_000;\n\n/**\n * Returns `true` if the provider is currently in its cooldown window.\n * Uses the injected `now` function (or `Date.now`) for testability.\n */\nfunction isProviderCoolingDown(provider: LLMProvider, now: () => number = Date.now): boolean {\n const until = providerCooldownUntil.get(provider);\n if (until === undefined) return false;\n return now() < until;\n}\n\n/** Returns `YYYY-MM-DD` from a Unix timestamp (ms). Used for daily KV cost keys. */\nfunction isoDate(nowMs: number): string {\n return new Date(nowMs).toISOString().slice(0, 10);\n}\n\n/**\n * Record actual call spend in org-level daily/monthly KV buckets.\n * This is intentionally best-effort: Cloudflare KV does not provide an atomic\n * compare-and-swap, so concurrent requests can race and undercount spend.\n */\nasync function recordOrgCostUsage(\n kv: CostKvStore,\n todayKey: string,\n monthKey: string,\n costUsd: number,\n opts: LLMOptions,\n): Promise<void> {\n if (opts.dailyCapUsd !== undefined) {\n const raw = await kv.get(todayKey).catch(() => null);\n const spent = parseFloat(raw ?? '0');\n await kv.put(todayKey, String(spent + costUsd), { expirationTtl: 172_800 /* 48 h */ }).catch(() => undefined);\n }\n if (opts.monthlyCapUsd !== undefined) {\n const raw = await kv.get(monthKey).catch(() => null);\n const spent = parseFloat(raw ?? '0');\n await kv.put(monthKey, String(spent + costUsd), { expirationTtl: 3_456_000 /* 40 d */ }).catch(() => undefined);\n }\n}\n\n/**\n * USD cost per 1 million tokens for each model.\n * Source: Anthropic / Google / xAI pricing pages as of 2026-05.\n * Keep these model names in sync with the default routing constants in\n * {@link MODELS}; unknown models fall back to Opus rates (conservative upper bound).\n *\n * CANONICAL pricing source for the platform. `@latimer-woods-tech/llm-meter`\n * derives its cents-denominated rates from this table and a drift-guard test\n * there fails CI if they diverge — make all rate changes here.\n */\nexport const MODEL_PRICE_PER_1M: Record<string, { input: number; output: number; cacheRead: number; cacheWrite: number }> = {\n // Anthropic Haiku 4.5 — `claude-haiku-4-5` is the routed alias; the dated id\n // is what the API echoes back in `response.model`, so both must price.\n // (Rates corrected 2026-08: the previous $0.80/$4.00 rows UNDERSTATED the\n // actual $1/$5 list price.)\n 'claude-haiku-4-5': { input: 1.00, output: 5.00, cacheRead: 0.10, cacheWrite: 1.25 },\n 'claude-haiku-4-5-20251001': { input: 1.00, output: 5.00, cacheRead: 0.10, cacheWrite: 1.25 },\n // Anthropic Sonnet 4\n 'claude-sonnet-4-20250514': { input: 3.00, output: 15.00, cacheRead: 0.30, cacheWrite: 3.75 },\n 'claude-sonnet-4-6': { input: 3.00, output: 15.00, cacheRead: 0.30, cacheWrite: 3.75 },\n // Anthropic Sonnet 5 — the routed `balanced` primary. List $3/$15; intro\n // pricing ($2/$10) runs through 2026-08-31 — priced at list here, the\n // conservative upper bound (table convention).\n 'claude-sonnet-5': { input: 3.00, output: 15.00, cacheRead: 0.30, cacheWrite: 3.75 },\n // Anthropic Opus 4 (dated id) — genuinely the $15/$75 era.\n 'claude-opus-4-20250514': { input: 15.00, output: 75.00, cacheRead: 1.50, cacheWrite: 18.75 },\n // Opus 4.7 was NEVER $15/$75 — it launched at $5/$25. The old row copied the\n // Opus 4 rate and overstated every smart-tier ledger entry 3x.\n 'claude-opus-4-7': { input: 5.00, output: 25.00, cacheRead: 0.50, cacheWrite: 6.25 },\n // Anthropic Opus 5 — the routed `smart` primary. Same $5/$25 as Opus 4.8/4.7.\n 'claude-opus-5': { input: 5.00, output: 25.00, cacheRead: 0.50, cacheWrite: 6.25 },\n // Gemini 2.5 Flash — the routed `smart`/long-context model (JSON fallback leg).\n 'gemini-2.5-flash': { input: 0.30, output: 2.50, cacheRead: 0.075, cacheWrite: 0.30 },\n // Gemini 2.5 Pro — retained for historical ledger rows (was the routed model).\n 'gemini-2.5-pro': { input: 1.25, output: 10.00, cacheRead: 0.31, cacheWrite: 4.50 },\n // Groq GPT-OSS 120B (`verifier` tier). Groq bills cached input at $0.075/1M\n // with no separate cache-write charge (implicit caching), so cacheWrite is 0.\n 'openai/gpt-oss-120b': { input: 0.15, output: 0.60, cacheRead: 0.075, cacheWrite: 0.00 },\n // Groq Llama 3.3 70B Versatile — retained for historical ledger rows\n // (routed `verifier` until Groq retired the model on 2026-08-16).\n 'llama-3.3-70b-versatile': { input: 0.59, output: 0.79, cacheRead: 0.00, cacheWrite: 0.00 },\n // Grok 4.3\n 'grok-4.3': { input: 1.25, output: 2.50, cacheRead: 0.00, cacheWrite: 0.00 },\n // DeepSeek API pricing as of 2026-05: cache-write conservatively uses cache-miss input pricing.\n 'deepseek-chat': { input: 0.27, output: 1.10, cacheRead: 0.07, cacheWrite: 0.27 },\n 'deepseek-reasoner': { input: 0.55, output: 2.19, cacheRead: 0.14, cacheWrite: 0.55 },\n // Deprecated aliases retained for historical ledger rows.\n 'grok-4-fast': { input: 1.25, output: 2.50, cacheRead: 0.00, cacheWrite: 0.00 },\n 'grok-3-mini-latest': { input: 1.25, output: 2.50, cacheRead: 0.00, cacheWrite: 0.00 },\n // Self-hosted qwen3 on the GPU box — zero marginal cost (electricity aside).\n 'qwen3:8b': { input: 0.0, output: 0.0, cacheRead: 0.0, cacheWrite: 0.0 },\n 'qwen3.6:27b': { input: 0.0, output: 0.0, cacheRead: 0.0, cacheWrite: 0.0 },\n};\n\n/** Fallback pricing used for unrecognised models (Opus rates — conservative upper bound). */\nconst PRICE_FALLBACK = MODEL_PRICE_PER_1M['claude-opus-4-7']!;\n\n/**\n * Estimates the USD cost of a single LLM completion from token counts.\n * Returns 0 for zero-token results. Uses {@link MODEL_PRICE_PER_1M} with\n * {@link PRICE_FALLBACK} for unknown models.\n */\nfunction estimateCostUsd(\n tokens: { input: number; output: number; cacheRead?: number; cacheWrite?: number },\n model: string,\n): number {\n const price = MODEL_PRICE_PER_1M[model] ?? PRICE_FALLBACK;\n return (\n (tokens.input * price.input +\n tokens.output * price.output +\n (tokens.cacheRead ?? 0) * price.cacheRead +\n (tokens.cacheWrite ?? 0) * price.cacheWrite) /\n 1_000_000\n );\n}\n\n/** Returns `YYYY-MM` from a Unix timestamp (ms). Used for monthly KV cost keys. */\nfunction isoMonth(nowMs: number): string {\n return new Date(nowMs).toISOString().slice(0, 7);\n}\n\n/**\n * Marks a provider as cooling down for {@link PROVIDER_COOLDOWN_MS} milliseconds.\n */\nfunction markProviderCoolingDown(provider: LLMProvider, now: () => number = Date.now): void {\n providerCooldownUntil.set(provider, now() + PROVIDER_COOLDOWN_MS);\n}\n\n/**\n * Clears the cooldown state for a provider after a successful call.\n */\nfunction clearProviderCooldown(provider: LLMProvider): void {\n providerCooldownUntil.delete(provider);\n}\n\n// ─── Legacy backoff constant (kept for the existing callWithBackoff signature) ─\nconst BASE_BACKOFF_MS = 250;\n\ninterface ProviderError {\n provider: LLMProvider;\n status: number;\n retryable: boolean;\n message: string;\n}\n\n/**\n * Returns `true` for status codes that should trigger a retry.\n * Only 429 and 5xx (transient server errors) qualify; other 4xx are terminal.\n */\nfunction isRetryableForBackoff(status: number): boolean {\n return status === 429 || (status >= 500 && status < 600);\n}\n\nfunction estimateTokens(messages: LLMMessage[], system?: string): number {\n // Cheap estimator: ~4 chars/token. Good enough for threshold routing.\n let chars = system?.length ?? 0;\n for (const m of messages) chars += contentToText(m.content).length;\n return Math.ceil(chars / 4);\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n const t = setTimeout(resolve, ms);\n if (signal) {\n const onAbort = () => {\n clearTimeout(t);\n reject(new DOMException('Aborted', 'AbortError'));\n };\n if (signal.aborted) onAbort();\n else signal.addEventListener('abort', onAbort, { once: true });\n }\n });\n}\n\n/**\n * Computes the exponential backoff delay for a given attempt with jitter.\n *\n * Formula: `Math.min(base * 2^attempt + jitter, cap)`\n * where `jitter` is a random value in `[0, BACKOFF_JITTER_MAX_MS)`.\n *\n * @param attempt - Zero-based attempt index (0 = first retry after initial failure).\n */\nfunction computeBackoffMs(attempt: number): number {\n const jitter = Math.floor(Math.random() * BACKOFF_JITTER_MAX_MS);\n return Math.min(BACKOFF_BASE_MS * Math.pow(2, attempt) + jitter, BACKOFF_CAP_MS);\n}\n\n// ─── Provider request builders ─────────────────────────────────────────────\n\n/**\n * Claude 4.7-and-later models reject the classic sampling params: sending\n * `temperature` (or `top_p`/`top_k`) returns 400 \"`temperature` is deprecated\n * for this model\" (verified live against /v1/messages, 2026-08-14). Models\n * matching this pattern must not receive a `temperature` field at all — this\n * is exactly what hard-broke the `smart` tier while it pinned opus-4-7.\n */\nconst ANTHROPIC_SAMPLING_REMOVED = /^claude-(opus-4-[78]|opus-5|sonnet-5|fable-5|mythos-5)/;\n\n/**\n * Models that run ADAPTIVE THINKING by default when the `thinking` field is\n * omitted. Thinking tokens draw from `max_tokens`, and this package's budgets\n * are small (DEFAULT_MAX_TOKENS 1024; the headline call passes 40) — left on,\n * the model can spend the whole budget thinking and truncate the visible\n * text. Same failure mode as Gemini 2.5 Pro's mandatory thinking budget (see\n * the MODELS.gemini comment), same fix: turn it off explicitly.\n * `{type:'disabled'}` is accepted on both models at the default effort\n * (verified live 2026-08-14). Do NOT add `claude-fable-5`/`claude-mythos-5`\n * here — those models reject an explicit `disabled` (thinking is always on).\n */\nconst ANTHROPIC_THINKING_DEFAULT_ON = /^claude-(opus-5|sonnet-5)$/;\n\nfunction buildAnthropicRequest(\n model: string,\n messages: LLMMessage[],\n opts: LLMOptions,\n env: LLMEnv,\n streaming = false,\n): { url: string; headers: Record<string, string>; body: string } {\n const sys = systemText(opts, messages);\n const filtered = messages.filter((m) => m.role !== 'system');\n const body: Record<string, unknown> = {\n model,\n max_tokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,\n messages: filtered.map((m) => ({ role: m.role, content: m.content })),\n };\n if (!ANTHROPIC_SAMPLING_REMOVED.test(model)) {\n body.temperature = opts.temperature ?? DEFAULT_TEMPERATURE;\n }\n if (ANTHROPIC_THINKING_DEFAULT_ON.test(model)) {\n body.thinking = { type: 'disabled' };\n }\n if (streaming) {\n body.stream = true;\n }\n if (sys) {\n const cache = opts.promptCache ?? sys.length >= 4096;\n body.system = cache\n ? [{ type: 'text', text: sys, cache_control: { type: 'ephemeral' } }]\n : sys;\n }\n if (opts.tools && opts.tools.length > 0) {\n body.tools = opts.tools.map((t) => ({\n name: t.name,\n description: t.description ?? '',\n input_schema: t.parameters,\n }));\n const tc = opts.toolChoice ?? 'auto';\n body.tool_choice =\n tc === 'auto'\n ? { type: 'auto' }\n : tc === 'none'\n ? { type: 'none' }\n : { type: 'tool', name: tc.name };\n }\n return {\n url: `${env.AI_GATEWAY_BASE_URL}/anthropic/v1/messages`,\n headers: {\n 'content-type': 'application/json',\n 'x-api-key': env.ANTHROPIC_API_KEY,\n 'anthropic-version': '2023-06-01',\n 'anthropic-beta': 'prompt-caching-2024-07-31',\n },\n body: JSON.stringify(body),\n };\n}\n\n/** A resolved Vertex credential: a bearer token plus (for ADC) the ambient project id. */\ninterface VertexAuth {\n token: string;\n /** Project id discovered from the metadata server (ADC path only). */\n project?: string;\n}\n\n/**\n * Resolve a Vertex credential, in priority order:\n * 1. `GCP_SA_KEY` — mint a fresh token from the service-account key (the\n * credential Cloudflare Workers supply, where there is no metadata server).\n * 2. `VERTEX_ACCESS_TOKEN` — a pre-minted token (legacy / CI override).\n * 3. Application Default Credentials — the GCP metadata server's ambient\n * service-account token. This is the KEYLESS default on Cloud Run / GCE:\n * neither env var need be set, mirroring the post-WIF-migration pattern\n * the render-runner already uses for every other GCP API call.\n *\n * @throws ValidationError when no static credential is set AND the metadata\n * server is unreachable (i.e. off-GCP) — the leg fails before the request so\n * the router falls through to the next provider, never calling Vertex\n * unauthenticated.\n */\nasync function resolveVertexAuth(env: LLMEnv, fetchImpl: typeof fetch): Promise<VertexAuth> {\n if (env.GCP_SA_KEY) {\n try {\n return { token: await mintGcpAccessToken(env.GCP_SA_KEY, fetchImpl) };\n } catch (error) {\n // A pre-minted token is worth trying if minting broke. With nothing to fall\n // back to, surface the minting failure rather than swallowing it.\n if (!env.VERTEX_ACCESS_TOKEN) throw error;\n }\n }\n if (env.VERTEX_ACCESS_TOKEN) return { token: env.VERTEX_ACCESS_TOKEN };\n // Keyless default: Application Default Credentials from the GCP metadata\n // server. Throws (→ chain falls through) when off-GCP.\n return fetchAdcAccessToken(fetchImpl);\n}\n\nfunction buildGeminiRequest(\n model: string,\n messages: LLMMessage[],\n opts: LLMOptions,\n env: LLMEnv,\n accessToken: string,\n adcProject?: string,\n): { url: string; headers: Record<string, string>; body: string } {\n const sys = systemText(opts, messages);\n const contents = messages\n .filter((m) => m.role !== 'system')\n .map((m) => ({\n role: m.role === 'assistant' ? 'model' : 'user',\n parts: [{ text: contentToText(m.content) }],\n }));\n const body: Record<string, unknown> = {\n contents,\n generationConfig: {\n maxOutputTokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,\n temperature: opts.temperature ?? DEFAULT_TEMPERATURE,\n // Disable \"thinking\": Gemini 2.5 draws thinking tokens from\n // maxOutputTokens, so on a large prompt (or a small token budget) the\n // model can spend the entire budget thinking and return an empty\n // candidate (finishReason MAX_TOKENS). This leg wants deterministic text\n // out, so thinking is turned off (supported by gemini-2.5-flash).\n thinkingConfig: { thinkingBudget: 0 },\n },\n };\n if (sys) {\n body.systemInstruction = { parts: [{ text: sys }] };\n }\n // Project resolution: an explicit VERTEX_PROJECT wins; else the service\n // account's own project (GCP_SA_KEY path); else the ambient project id the\n // metadata server reported (ADC path). So a Cloud Run job needs no Vertex\n // config at all — the runner's own project is used.\n const project =\n env.VERTEX_PROJECT ||\n (env.GCP_SA_KEY ? serviceAccountProjectId(env.GCP_SA_KEY) : (adcProject ?? ''));\n const location = env.VERTEX_LOCATION || DEFAULT_VERTEX_LOCATION;\n const path = `v1/projects/${project}/locations/${location}/publishers/google/models/${model}:generateContent`;\n return {\n url: `${env.AI_GATEWAY_BASE_URL}/google-vertex-ai/${path}`,\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${accessToken}`,\n },\n body: JSON.stringify(body),\n };\n}\n\n// ─── OpenAI-style (Grok / DeepSeek / Groq) tool-calling helpers ──────────────\n\n/**\n * Converts provider-agnostic messages to OpenAI chat-completions format,\n * translating the Anthropic-shaped tool blocks: `tool_use` → an assistant\n * message with `tool_calls`; `tool_result` → a standalone `tool` message keyed\n * by `tool_call_id`. Plain-string content passes through unchanged.\n */\nfunction toOpenAiMessages(messages: LLMMessage[], sys: string | undefined): Array<Record<string, unknown>> {\n const out: Array<Record<string, unknown>> = [];\n if (sys) out.push({ role: 'system', content: sys });\n for (const m of messages) {\n if (m.role === 'system') continue;\n if (typeof m.content === 'string') {\n out.push({ role: m.role, content: m.content });\n continue;\n }\n let text = '';\n const toolCalls: Array<Record<string, unknown>> = [];\n const results: Array<{ tool_use_id: string; content: string }> = [];\n for (const b of m.content) {\n if (b.type === 'text') text += b.text;\n else if (b.type === 'tool_use')\n toolCalls.push({ id: b.id, type: 'function', function: { name: b.name, arguments: JSON.stringify(b.input) } });\n else if (b.type === 'tool_result') results.push({ tool_use_id: b.tool_use_id, content: b.content });\n }\n if (results.length > 0) {\n for (const r of results) out.push({ role: 'tool', tool_call_id: r.tool_use_id, content: r.content });\n if (text) out.push({ role: 'user', content: text });\n } else if (toolCalls.length > 0) {\n out.push({ role: 'assistant', content: text || null, tool_calls: toolCalls });\n } else {\n out.push({ role: m.role, content: text });\n }\n }\n return out;\n}\n\n/**\n * Converts normalized messages to Ollama's native chat shape. Native tool calls\n * carry object arguments (rather than OpenAI's JSON string), and tool results\n * identify the function by name rather than by call id.\n */\nfunction toOllamaMessages(messages: LLMMessage[], sys: string | undefined): Array<Record<string, unknown>> {\n const toolNames = new Map<string, string>();\n for (const message of messages) {\n if (typeof message.content === 'string') continue;\n for (const block of message.content) {\n if (block.type === 'tool_use') toolNames.set(block.id, block.name);\n }\n }\n\n const out: Array<Record<string, unknown>> = [];\n if (sys) out.push({ role: 'system', content: sys });\n for (const message of messages) {\n if (message.role === 'system') continue;\n if (typeof message.content === 'string') {\n out.push({ role: message.role, content: message.content });\n continue;\n }\n\n let text = '';\n const toolCalls: Array<Record<string, unknown>> = [];\n const results: Array<{ toolUseId: string; content: string }> = [];\n for (const block of message.content) {\n if (block.type === 'text') text += block.text;\n else if (block.type === 'tool_use') {\n toolCalls.push({\n type: 'function',\n function: { index: toolCalls.length, name: block.name, arguments: block.input },\n });\n } else if (block.type === 'tool_result') {\n results.push({ toolUseId: block.tool_use_id, content: block.content });\n }\n }\n if (results.length > 0) {\n for (const result of results) {\n out.push({\n role: 'tool',\n tool_name: toolNames.get(result.toolUseId) ?? result.toolUseId,\n content: result.content,\n });\n }\n if (text) out.push({ role: 'user', content: text });\n } else if (toolCalls.length > 0) {\n out.push({ role: 'assistant', content: text, tool_calls: toolCalls });\n } else {\n out.push({ role: message.role, content: text });\n }\n }\n return out;\n}\n\n/** Builds the OpenAI `tools` array from {@link LLMOptions.tools}, or undefined. */\nfunction openAiTools(opts: LLMOptions): Array<Record<string, unknown>> | undefined {\n if (!opts.tools || opts.tools.length === 0) return undefined;\n return opts.tools.map((t) => ({\n type: 'function',\n function: { name: t.name, description: t.description ?? '', parameters: t.parameters },\n }));\n}\n\n/** Maps {@link LLMOptions.toolChoice} to the OpenAI `tool_choice` value. */\nfunction openAiToolChoice(tc: LLMOptions['toolChoice']): unknown {\n if (tc === undefined) return undefined;\n if (tc === 'auto' || tc === 'none') return tc;\n return { type: 'function', function: { name: tc.name } };\n}\n\nfunction buildGroqRequest(\n model: string,\n messages: LLMMessage[],\n opts: LLMOptions,\n env: LLMEnv,\n): { url: string; headers: Record<string, string>; body: string } {\n const sys = systemText(opts, messages);\n const merged: LLMMessage[] = [];\n if (sys) merged.push({ role: 'system', content: sys });\n for (const m of messages) if (m.role !== 'system') merged.push({ role: m.role, content: contentToText(m.content) });\n return {\n url: `${env.AI_GATEWAY_BASE_URL}/groq/openai/v1/chat/completions`,\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${env.GROQ_API_KEY}`,\n },\n body: JSON.stringify({\n model,\n max_tokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,\n temperature: opts.temperature ?? DEFAULT_TEMPERATURE,\n messages: merged,\n }),\n };\n}\n\nfunction buildGrokRequest(\n model: string,\n messages: LLMMessage[],\n opts: LLMOptions,\n env: LLMEnv,\n): { url: string; headers: Record<string, string>; body: string } {\n if (!env.GROK_API_KEY) {\n throw new ValidationError('GROK_API_KEY required for grok-* model override');\n }\n const sys = systemText(opts, messages);\n const body: Record<string, unknown> = {\n model,\n max_tokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,\n temperature: opts.temperature ?? DEFAULT_TEMPERATURE,\n messages: toOpenAiMessages(messages, sys),\n };\n const tools = openAiTools(opts);\n if (tools) {\n body.tools = tools;\n const tc = openAiToolChoice(opts.toolChoice ?? 'auto');\n if (tc !== undefined) body.tool_choice = tc;\n }\n if (model === MODELS.grok.fast) {\n body.reasoning_effort = opts.reasoningEffort ?? 'none';\n }\n return {\n url: `${env.AI_GATEWAY_BASE_URL}/grok/v1/chat/completions`,\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${env.GROK_API_KEY}`,\n },\n body: JSON.stringify(body),\n };\n}\n\nfunction buildDeepSeekRequest(\n model: string,\n messages: LLMMessage[],\n opts: LLMOptions,\n env: LLMEnv,\n): { url: string; headers: Record<string, string>; body: string } {\n if (!env.DEEPSEEK_API_KEY) {\n throw new ValidationError('DEEPSEEK_API_KEY required for workbench tier or deepseek-* model override');\n }\n const sys = systemText(opts, messages);\n const body: Record<string, unknown> = {\n model,\n max_tokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,\n temperature: opts.temperature ?? DEFAULT_TEMPERATURE,\n messages: toOpenAiMessages(messages, sys),\n };\n const tools = openAiTools(opts);\n if (tools) {\n body.tools = tools;\n const tc = openAiToolChoice(opts.toolChoice ?? 'auto');\n if (tc !== undefined) body.tool_choice = tc;\n }\n return {\n url: `${env.AI_GATEWAY_BASE_URL}/deepseek/chat/completions`,\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${env.DEEPSEEK_API_KEY}`,\n },\n body: JSON.stringify(body),\n };\n}\n\n/**\n * Builds a local Qwen request behind Cloudflare AI Gateway. Qwen 8B uses the\n * dedicated fast origin's native Ollama contract so `think:false` is enforced;\n * Qwen 27B retains the OpenAI-compatible agentic/tool-call route.\n */\nfunction buildLocalRequest(\n model: string,\n messages: LLMMessage[],\n opts: LLMOptions,\n env: LLMEnv,\n): { url: string; headers: Record<string, string>; body: string } {\n if (!env.GPU_LLM_API_TOKEN) {\n throw new ValidationError('GPU_LLM_API_TOKEN required for the local provider');\n }\n const sys = systemText(opts, messages);\n const fastMode = model === MODELS.local.fast;\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n authorization: `Bearer ${env.GPU_LLM_API_TOKEN}`,\n };\n if (env.GPU_LLM_ACCESS_CLIENT_ID) headers['CF-Access-Client-Id'] = env.GPU_LLM_ACCESS_CLIENT_ID;\n if (env.GPU_LLM_ACCESS_CLIENT_SECRET) headers['CF-Access-Client-Secret'] = env.GPU_LLM_ACCESS_CLIENT_SECRET;\n const tools = openAiTools(opts);\n if (fastMode) {\n if (typeof opts.toolChoice === 'object') {\n throw new ValidationError('named tool choice is not supported by the native Qwen 8B route');\n }\n return {\n url: `${env.AI_GATEWAY_BASE_URL}/custom-local-fast-gpu/api/chat`,\n headers,\n body: JSON.stringify({\n model,\n stream: false,\n think: false,\n messages: toOllamaMessages(messages, sys),\n options: {\n num_predict: opts.maxTokens ?? DEFAULT_MAX_TOKENS,\n temperature: opts.temperature ?? DEFAULT_TEMPERATURE,\n },\n ...(tools && opts.toolChoice !== 'none' ? { tools } : {}),\n }),\n };\n }\n return {\n url: `${env.AI_GATEWAY_BASE_URL}/custom-local-gpu/v1/chat/completions`,\n headers,\n body: JSON.stringify({\n model,\n // Ollama enables Qwen thinking by default. Without this OpenAI-\n // compatible control, short bounded calls can spend every token in the\n // hidden `reasoning` field and return HTTP 200 with empty visible text.\n reasoning_effort: 'none',\n max_tokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,\n temperature: opts.temperature ?? DEFAULT_TEMPERATURE,\n messages: toOpenAiMessages(messages, sys),\n ...(tools\n ? { tools, tool_choice: openAiToolChoice(opts.toolChoice ?? 'auto') }\n : {}),\n }),\n };\n}\n\n// ─── Response parsers ──────────────────────────────────────────────────────\n\ninterface AnthropicResponse {\n content?: Array<{\n type: string;\n text?: string;\n id?: string;\n name?: string;\n input?: Record<string, unknown>;\n }>;\n stop_reason?: string;\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n };\n model?: string;\n}\n\n/** Maps a provider stop reason to the normalized {@link LLMResult.stopReason}. */\nfunction normalizeAnthropicStop(reason: string | undefined): LLMResult['stopReason'] {\n switch (reason) {\n case 'end_turn':\n case 'stop_sequence':\n return 'end';\n case 'tool_use':\n return 'tool_use';\n case 'max_tokens':\n return 'max_tokens';\n default:\n return reason ? 'other' : undefined;\n }\n}\n\ninterface GeminiResponse {\n candidates?: Array<{ content?: { parts?: Array<{ text?: string }> } }>;\n usageMetadata?: {\n promptTokenCount?: number;\n candidatesTokenCount?: number;\n };\n}\n\ninterface GroqResponse {\n choices?: Array<{ message?: { content?: string } }>;\n usage?: { prompt_tokens?: number; completion_tokens?: number };\n model?: string;\n}\n\nfunction parseAnthropic(\n json: unknown,\n): {\n content: string;\n input: number;\n output: number;\n cacheRead: number;\n cacheWrite: number;\n model?: string;\n toolCalls?: LLMToolCall[];\n stopReason?: LLMResult['stopReason'];\n} {\n const r = json as AnthropicResponse;\n const toolCalls: LLMToolCall[] = (r.content ?? [])\n .filter((c) => c.type === 'tool_use' && typeof c.id === 'string' && typeof c.name === 'string')\n .map((c) => ({ id: c.id!, name: c.name!, arguments: c.input ?? {} }));\n return {\n content: r.content?.find((c) => c.type === 'text')?.text ?? '',\n input: r.usage?.input_tokens ?? 0,\n output: r.usage?.output_tokens ?? 0,\n cacheRead: r.usage?.cache_read_input_tokens ?? 0,\n cacheWrite: r.usage?.cache_creation_input_tokens ?? 0,\n model: r.model,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n stopReason: normalizeAnthropicStop(r.stop_reason),\n };\n}\n\nfunction parseGemini(json: unknown): { content: string; input: number; output: number } {\n const r = json as GeminiResponse;\n const text =\n r.candidates?.[0]?.content?.parts?.map((p) => p.text ?? '').join('') ?? '';\n return {\n content: text,\n input: r.usageMetadata?.promptTokenCount ?? 0,\n output: r.usageMetadata?.candidatesTokenCount ?? 0,\n };\n}\n\nfunction parseGroq(json: unknown): { content: string; input: number; output: number; model?: string } {\n const r = json as GroqResponse;\n return {\n content: r.choices?.[0]?.message?.content ?? '',\n input: r.usage?.prompt_tokens ?? 0,\n output: r.usage?.completion_tokens ?? 0,\n model: r.model,\n };\n}\n\ninterface OpenAiResponse {\n choices?: Array<{\n message?: {\n content?: string | null;\n tool_calls?: Array<{ id?: string; function?: { name?: string; arguments?: string } }>;\n };\n finish_reason?: string;\n }>;\n usage?: { prompt_tokens?: number; completion_tokens?: number };\n model?: string;\n}\n\ninterface OllamaChatResponse {\n model?: string;\n message?: {\n content?: string;\n thinking?: string;\n tool_calls?: Array<{\n function?: { name?: string; arguments?: Record<string, unknown> | string };\n }>;\n };\n done?: boolean;\n done_reason?: string;\n prompt_eval_count?: number;\n eval_count?: number;\n}\n\n/** Maps an OpenAI `finish_reason` to the normalized {@link LLMResult.stopReason}. */\nfunction normalizeOpenAiStop(reason: string | undefined): LLMResult['stopReason'] {\n switch (reason) {\n case 'stop':\n return 'end';\n case 'tool_calls':\n case 'function_call':\n return 'tool_use';\n case 'length':\n return 'max_tokens';\n default:\n return reason ? 'other' : undefined;\n }\n}\n\n/** Best-effort parse of an OpenAI tool-call arguments string; `{}` on failure. */\nfunction parseToolArgs(raw: string | undefined): Record<string, unknown> {\n if (!raw) return {};\n try {\n const v = JSON.parse(raw) as unknown;\n return typeof v === 'object' && v !== null ? (v as Record<string, unknown>) : {};\n } catch {\n return {};\n }\n}\n\n/**\n * Parses an OpenAI chat-completions response (Grok, DeepSeek), extracting\n * normalized tool calls and a stop reason in addition to text + tokens.\n */\nfunction parseOpenAi(json: unknown): {\n content: string;\n input: number;\n output: number;\n model?: string;\n toolCalls?: LLMToolCall[];\n stopReason?: LLMResult['stopReason'];\n} {\n const r = json as OpenAiResponse;\n const choice = r.choices?.[0];\n const toolCalls: LLMToolCall[] = (choice?.message?.tool_calls ?? [])\n .filter((c) => typeof c.function?.name === 'string')\n .map((c, i) => ({\n id: c.id ?? `call_${i}`,\n name: c.function!.name!,\n arguments: parseToolArgs(c.function?.arguments),\n }));\n return {\n content: choice?.message?.content ?? '',\n input: r.usage?.prompt_tokens ?? 0,\n output: r.usage?.completion_tokens ?? 0,\n model: r.model,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n stopReason: normalizeOpenAiStop(choice?.finish_reason),\n };\n}\n\n/** Parses Ollama's native non-streaming `/api/chat` response for Qwen 8B. */\nfunction parseOllamaChat(json: unknown): {\n content: string;\n input: number;\n output: number;\n model?: string;\n toolCalls?: LLMToolCall[];\n stopReason?: LLMResult['stopReason'];\n} {\n const response = json as OllamaChatResponse;\n const toolCalls: LLMToolCall[] = (response.message?.tool_calls ?? [])\n .filter((call) => typeof call.function?.name === 'string')\n .map((call, index) => ({\n id: `call_${index}`,\n name: call.function!.name!,\n arguments:\n typeof call.function?.arguments === 'string'\n ? parseToolArgs(call.function.arguments)\n : (call.function?.arguments ?? {}),\n }));\n let stopReason: LLMResult['stopReason'];\n if (toolCalls.length > 0) stopReason = 'tool_use';\n else if (response.done_reason === 'length') stopReason = 'max_tokens';\n else if (response.done) stopReason = 'end';\n else if (response.done_reason) stopReason = 'other';\n return {\n // Deliberately exclude message.thinking: callers receive only the bounded\n // classification answer even if a non-conforming origin returns a trace.\n content: response.message?.content ?? '',\n input: response.prompt_eval_count ?? 0,\n output: response.eval_count ?? 0,\n model: response.model,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n stopReason,\n };\n}\n\n// ─── Core call with backoff ────────────────────────────────────────────────\n\n/**\n * Calls a provider with per-provider exponential backoff.\n *\n * Retries up to {@link PER_PROVIDER_MAX_ATTEMPTS} times on 429 or transient 5xx.\n * Other 4xx codes are treated as terminal and not retried.\n * AbortError is never retried — it bubbles immediately.\n *\n * @param provider - Provider name, used for error tagging.\n * @param request - Pre-built HTTP request descriptor.\n * @param fetchImpl - Fetch implementation (injectable for tests).\n * @param signal - Optional AbortSignal for cancellation.\n * @param logger - Optional logger for per-attempt warnings.\n * @param nowFn - Optional clock injection for testability.\n * @returns Parsed JSON body, optional AI Gateway request ID, and attempt count.\n */\nasync function callWithBackoff(\n provider: LLMProvider,\n request: { url: string; headers: Record<string, string>; body: string },\n fetchImpl: typeof fetch,\n signal: AbortSignal | undefined,\n logger: Logger | undefined,\n nowFn?: () => number,\n): Promise<{ json: unknown; gatewayRequestId?: string; attempts: number }> {\n /**\n * Helper: mark provider cooling down and then throw the error.\n * Called whenever we determine we've exhausted all retries for the provider.\n * AbortError is never counted as a provider exhaustion — it bypasses this.\n */\n function exhaustAndThrow(err: ProviderError): never {\n markProviderCoolingDown(provider, nowFn ?? Date.now);\n throw err;\n }\n\n let lastErr: ProviderError | undefined;\n for (let attempt = 1; attempt <= PER_PROVIDER_MAX_ATTEMPTS; attempt++) {\n try {\n const response = await fetchImpl(request.url, {\n method: 'POST',\n headers: request.headers,\n body: request.body,\n signal,\n });\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n const retryable = isRetryableForBackoff(response.status);\n const err: ProviderError = {\n provider,\n status: response.status,\n retryable,\n message: `${provider} ${String(response.status)}: ${text.slice(0, 300)}`,\n };\n logger?.warn?.('llm.provider.error', { provider, status: response.status, attempt });\n if (!err.retryable || attempt === PER_PROVIDER_MAX_ATTEMPTS) {\n if (err.retryable) exhaustAndThrow(err); // retryable but exhausted\n throw err; // terminal non-retryable error — no cooldown\n }\n lastErr = err;\n } else {\n const gatewayRequestId = response.headers.get('cf-aig-request-id') ?? undefined;\n clearProviderCooldown(provider);\n return { json: await response.json(), gatewayRequestId, attempts: attempt };\n }\n } catch (e) {\n if (e instanceof DOMException && e.name === 'AbortError') throw e;\n if (typeof e === 'object' && e !== null && 'retryable' in e) {\n const err = e as ProviderError;\n if (!err.retryable || attempt === PER_PROVIDER_MAX_ATTEMPTS) {\n if (err.retryable) exhaustAndThrow(err); // retryable but exhausted\n throw err; // terminal — no cooldown\n }\n lastErr = err;\n } else {\n const err: ProviderError = {\n provider,\n status: 0,\n retryable: true,\n message: e instanceof Error ? e.message : String(e),\n };\n if (attempt === PER_PROVIDER_MAX_ATTEMPTS) exhaustAndThrow(err);\n lastErr = err;\n }\n }\n // Exponential backoff with jitter: base=500ms, cap=8000ms, jitter up to 250ms\n const backoffMs = computeBackoffMs(attempt - 1);\n await sleep(backoffMs, signal);\n }\n // Fallthrough — should not be reached, but mark cooling down defensively.\n markProviderCoolingDown(provider, nowFn ?? Date.now);\n throw lastErr ?? ({ provider, status: 0, retryable: false, message: 'exhausted' } as ProviderError);\n}\n\nfunction isProviderError(err: unknown): err is ProviderError {\n return (\n typeof err === 'object' &&\n err !== null &&\n typeof (err as { status?: unknown }).status === 'number' &&\n typeof (err as { message?: unknown }).message === 'string' &&\n typeof (err as { provider?: unknown }).provider === 'string'\n );\n}\n\n// ─── Routing ───────────────────────────────────────────────────────────────\n\ninterface RoutePlan {\n primary: { provider: LLMProvider; model: string };\n fallback?: { provider: LLMProvider; model: string };\n}\n\n/**\n * Providers whose request builder + response parser support tool-calling.\n * When `opts.tools` is set, routing is restricted to this set and **fails\n * closed** rather than silently calling a provider that would ignore the\n * tools. Expanded in 1b as the other providers' tool formats are normalized.\n */\nconst TOOL_CAPABLE_PROVIDERS = new Set<LLMProvider>(['anthropic', 'grok', 'deepseek', 'local']);\n\nfunction plan(tier: LLMTier, opts: LLMOptions, tokenEstimate: number): RoutePlan {\n if (opts.model) {\n // Explicit override — best-effort provider detection.\n const m = opts.model;\n if (m.startsWith('claude')) return { primary: { provider: 'anthropic', model: m } };\n if (m.startsWith('gemini')) return { primary: { provider: 'gemini', model: m } };\n if (m.startsWith('grok')) return { primary: { provider: 'grok', model: m } };\n if (m.startsWith('deepseek')) return { primary: { provider: 'deepseek', model: m } };\n if (m.startsWith('qwen')) return { primary: { provider: 'local', model: m } };\n return { primary: { provider: 'groq', model: m } };\n }\n const longContext = tokenEstimate >= (opts.longContextThreshold ?? DEFAULT_LONG_CONTEXT_THRESHOLD);\n switch (tier) {\n case 'workbench':\n return {\n primary: { provider: 'deepseek', model: MODELS.deepseek.workbench },\n fallback: { provider: 'groq', model: MODELS.groq.verifier },\n };\n case 'verifier':\n return { primary: { provider: 'groq', model: MODELS.groq.verifier } };\n case 'smart':\n return longContext\n ? {\n primary: { provider: 'gemini', model: MODELS.gemini.smart },\n fallback: { provider: 'anthropic', model: MODELS.anthropic.smart },\n }\n : {\n primary: { provider: 'anthropic', model: MODELS.anthropic.smart },\n fallback: { provider: 'gemini', model: MODELS.gemini.smart },\n };\n case 'fast':\n return {\n primary: { provider: 'grok', model: MODELS.grok.fast },\n fallback: { provider: 'anthropic', model: MODELS.anthropic.fast },\n };\n case 'balanced':\n default:\n return longContext\n ? {\n primary: { provider: 'gemini', model: MODELS.gemini.smart },\n fallback: { provider: 'anthropic', model: MODELS.anthropic.balanced },\n }\n : {\n primary: { provider: 'anthropic', model: MODELS.anthropic.balanced },\n fallback: { provider: 'gemini', model: MODELS.gemini.smart },\n };\n }\n}\n\n/**\n * Build the `cf-aig-metadata` header value for the Cloudflare AI Gateway.\n *\n * Carries caller attribution (project / workload / actor / runId) so a single\n * shared gateway can be sliced per-app and per-feature in the AI Gateway\n * dashboard and logs. This replaces the per-app-gateway convention: rather than\n * one gateway per app (which has to be provisioned and silently 401s when it\n * isn't), one gateway tags every request with who made it.\n *\n * Returns `undefined` when no attribution fields are set (header omitted).\n * The CF AI Gateway accepts a JSON object of string/number/boolean values.\n */\nfunction buildAigMetadata(opts: LLMOptions): string | undefined {\n const meta: Record<string, string> = {};\n if (opts.project) meta.project = opts.project;\n if (opts.workload) meta.workload = opts.workload;\n if (opts.actor) meta.actor = opts.actor;\n if (opts.runId) meta.runId = opts.runId;\n return Object.keys(meta).length > 0 ? JSON.stringify(meta) : undefined;\n}\n\nasync function callOne(\n leg: { provider: LLMProvider; model: string },\n messages: LLMMessage[],\n opts: LLMOptions,\n env: LLMEnv,\n fetchImpl: typeof fetch,\n logger: Logger | undefined,\n nowFn?: () => number,\n): Promise<{ parsed: { content: string; input: number; output: number; cacheRead?: number; cacheWrite?: number; model?: string; toolCalls?: LLMToolCall[]; stopReason?: LLMResult['stopReason'] }; gatewayRequestId?: string; attempts: number }> {\n let req: { url: string; headers: Record<string, string>; body: string };\n switch (leg.provider) {\n case 'anthropic':\n req = buildAnthropicRequest(leg.model, messages, opts, env);\n break;\n case 'gemini': {\n // Credential resolution is a network call (mint from key, or the metadata\n // server for ADC), so it must happen here rather than inside the\n // (synchronous) request builders. Cached per isolate, so it is almost\n // always a no-op after the first gemini call.\n const vertexAuth = await resolveVertexAuth(env, fetchImpl);\n req = buildGeminiRequest(leg.model, messages, opts, env, vertexAuth.token, vertexAuth.project);\n break;\n }\n case 'groq':\n req = buildGroqRequest(leg.model, messages, opts, env);\n break;\n case 'grok':\n req = buildGrokRequest(leg.model, messages, opts, env);\n break;\n case 'deepseek':\n req = buildDeepSeekRequest(leg.model, messages, opts, env);\n break;\n case 'local':\n req = buildLocalRequest(leg.model, messages, opts, env);\n break;\n }\n // Attribution for the shared AI Gateway — one gateway, sliced per-app/feature.\n const aigMetadata = buildAigMetadata(opts);\n if (aigMetadata) req.headers['cf-aig-metadata'] = aigMetadata;\n const { json, gatewayRequestId, attempts } = await callWithBackoff(\n leg.provider,\n req,\n fetchImpl,\n opts.signal,\n logger,\n nowFn,\n );\n switch (leg.provider) {\n case 'anthropic':\n return { parsed: parseAnthropic(json), gatewayRequestId, attempts };\n case 'gemini':\n return { parsed: parseGemini(json), gatewayRequestId, attempts };\n case 'groq':\n return { parsed: parseGroq(json), gatewayRequestId, attempts };\n case 'grok':\n return { parsed: parseOpenAi(json), gatewayRequestId, attempts };\n case 'deepseek':\n return { parsed: parseOpenAi(json), gatewayRequestId, attempts };\n case 'local':\n return {\n parsed: leg.model === MODELS.local.fast ? parseOllamaChat(json) : parseOpenAi(json),\n gatewayRequestId,\n attempts,\n };\n }\n}\n\n/**\n * Run a completion through the routing plan for the requested tier.\n *\n * Routing summary (0.3.0):\n * - `fast` → Grok 4.3; Anthropic Haiku fallback when Grok is unavailable\n * - `balanced` → Anthropic Sonnet; Gemini 2.5 Flash if `longContextThreshold` exceeded\n * - `smart` → Anthropic Opus; Gemini 2.5 Flash if long-context\n * - `verifier` → Groq Llama 3.3 70B (no fallback — verifier is inherently cheap/best-effort)\n * - `workbench` → DeepSeek Chat; Groq fallback for boring/reviewable internal batch jobs\n *\n * All provider traffic flows through Cloudflare AI Gateway at `AI_GATEWAY_BASE_URL`.\n *\n * Per-provider reliability guarantees (0.4.0):\n * - Exponential backoff with jitter on 429 / 5xx (base 500ms, cap 8s, up to 2 retries).\n * - Provider cooldown: after exhausting retries the provider is marked cooling down\n * for 30 seconds; subsequent calls skip it and go straight to the fallback leg.\n *\n * @param messages - Ordered chat history.\n * @param env - API key + gateway bindings.\n * @param opts - Optional tier/model/parameters override.\n * @param deps - Optional fetch/logger/clock injection (for testing).\n * @returns A {@link FactoryResponse} carrying either an {@link LLMResult} or\n * an error (`LLM_ALL_PROVIDERS_FAILED`, `LLM_RATE_LIMITED`, or `INTERNAL_ERROR`).\n */\nexport async function complete(\n messages: LLMMessage[],\n env: LLMEnv,\n opts: LLMOptions = {},\n deps: LLMDeps = {},\n): Promise<FactoryResponse<LLMResult>> {\n if (messages.length === 0) {\n throw new ValidationError('messages must not be empty');\n }\n if (!env.AI_GATEWAY_BASE_URL) {\n throw new ValidationError('AI_GATEWAY_BASE_URL is required in 0.3.0');\n }\n const fetchImpl = deps.fetch ?? fetch;\n const now = deps.now ?? (() => Date.now());\n const logger = deps.logger;\n const startedAt = now();\n\n const tier: LLMTier = opts.tier ?? 'balanced';\n const system = systemText(opts, messages);\n const tokenEstimate = estimateTokens(messages, system);\n let route = plan(tier, opts, tokenEstimate);\n // Cost optimization: prefer the dedicated Qwen 8B GPU ($0) for cheap `fast`-tier\n // work, with the original fast route (Grok→Haiku) as automatic fallback on any\n // error. Dormant unless LLM_LOCAL_FIRST + GPU_LLM_API_TOKEN are set.\n if (\n env.LLM_LOCAL_FIRST &&\n tier === 'fast' &&\n !opts.model &&\n env.GPU_LLM_API_TOKEN\n ) {\n route = { primary: { provider: 'local', model: MODELS.local.fast }, fallback: route.primary };\n }\n // Qwen 27B owns local agentic work. DeepSeek remains the independent fallback;\n // explicit model overrides always win.\n if (\n env.LLM_LOCAL_WORKBENCH &&\n tier === 'workbench' &&\n !opts.model &&\n env.GPU_LLM_API_TOKEN\n ) {\n route = { primary: { provider: 'local', model: MODELS.local.workbench }, fallback: route.primary };\n }\n\n // ── Org-level daily / monthly cap pre-check ──────────────────────────────\n const kv = env.LLM_COST_KV;\n const todayKey = `llm:daily-cost:${isoDate(now())}`;\n const monthKey = `llm:monthly-cost:${isoMonth(now())}`;\n if (kv) {\n if (opts.dailyCapUsd !== undefined) {\n const raw = await kv.get(todayKey).catch(() => null);\n const spent = parseFloat(raw ?? '0');\n if (spent >= opts.dailyCapUsd) {\n return toErrorResponse(\n new RateLimitError('LLM_DAILY_CAP_EXCEEDED', {\n spentUsd: spent,\n dailyCapUsd: opts.dailyCapUsd,\n }),\n );\n }\n }\n if (opts.monthlyCapUsd !== undefined) {\n const raw = await kv.get(monthKey).catch(() => null);\n const spent = parseFloat(raw ?? '0');\n if (spent >= opts.monthlyCapUsd) {\n return toErrorResponse(\n new RateLimitError('LLM_MONTHLY_CAP_EXCEEDED', {\n spentUsd: spent,\n monthlyCapUsd: opts.monthlyCapUsd,\n }),\n );\n }\n }\n }\n\n const attemptLog: Array<{ provider: LLMProvider; status?: number; message: string }> = [];\n\n let routeLegs = [route.primary, route.fallback].filter(Boolean) as Array<{ provider: LLMProvider; model: string }>;\n // Tool-calling fails closed: never fall back to a provider that can't honour\n // the tool schema. Narrow the route to tool-capable providers when tools are set.\n if (opts.tools && opts.tools.length > 0) {\n routeLegs = routeLegs.filter((l) => TOOL_CAPABLE_PROVIDERS.has(l.provider));\n if (routeLegs.length === 0) {\n throw new ValidationError(\n `tool-calling requires a tool-capable provider (${[...TOOL_CAPABLE_PROVIDERS].join(', ')}); tier '${tier}' has none — use tier fast/balanced/smart or a claude-* model override`,\n );\n }\n }\n for (const [legIndex, leg] of routeLegs.entries()) {\n // Skip providers that are currently in their cooldown window.\n if (isProviderCoolingDown(leg.provider, now)) {\n logger?.warn?.('llm.provider.coolingDown', { provider: leg.provider });\n attemptLog.push({ provider: leg.provider, message: 'skipped: cooling down' });\n continue;\n }\n if (opts.signal?.aborted) {\n return toErrorResponse(\n new InternalError('llm call aborted', { provider: leg.provider, model: leg.model }),\n );\n }\n try {\n const result = await callOne(leg, messages, opts, env, fetchImpl, logger, now);\n // A tool_use turn legitimately has no text content — only treat a\n // genuinely empty response (no text AND no tool calls) as a failure.\n if (!result.parsed.content && !(result.parsed.toolCalls && result.parsed.toolCalls.length > 0)) {\n throw { provider: leg.provider, status: 200, retryable: false, message: 'empty content' } satisfies ProviderError;\n }\n logger?.info?.('llm.complete', {\n // `false` means NO caller named a tier, so this call took the\n // package default (`balanced` — claude-sonnet-5, the second most\n // expensive route). Emitted so a defaulted tier is greppable rather\n // than invisible: an expensive route chosen by silence is the thing\n // that made the video pipeline bill Sonnet while its docs said Haiku.\n tierExplicit: opts.tier !== undefined,\n provider: leg.provider,\n model: leg.model,\n tier,\n tokenEstimate,\n attempts: result.attempts,\n runId: opts.runId,\n project: opts.project,\n actor: opts.actor,\n workload: opts.workload,\n });\n // The tier's route is [primary, fallback] in order (see `routeLegs` above) — a\n // fallback served iff this leg is anything past index 0. This is the\n // \"servedBy != intended\" signal from #4204: cheap to compute here, and\n // distinct from `attempts` (which conflates retries on the SAME provider\n // with actually falling back to a DIFFERENT one).\n const degraded = legIndex > 0;\n const llmResult: LLMResult = {\n content: result.parsed.content,\n provider: leg.provider,\n model: result.parsed.model ?? leg.model,\n tier,\n tokens: {\n input: result.parsed.input,\n output: result.parsed.output,\n cacheRead: result.parsed.cacheRead,\n cacheWrite: result.parsed.cacheWrite,\n },\n latency: now() - startedAt,\n attempts: result.attempts,\n gatewayRequestId: result.gatewayRequestId,\n stopReason: result.parsed.stopReason,\n toolCalls: result.parsed.toolCalls,\n degraded,\n };\n const costUsd = estimateCostUsd(llmResult.tokens, llmResult.model);\n if (opts.maxCostUsd !== undefined && costUsd > opts.maxCostUsd) {\n if (kv && (opts.dailyCapUsd !== undefined || opts.monthlyCapUsd !== undefined)) {\n await recordOrgCostUsage(kv, todayKey, monthKey, costUsd, opts);\n }\n return toErrorResponse(\n new RateLimitError('LLM_COST_CAP_EXCEEDED', {\n costUsd,\n maxCostUsd: opts.maxCostUsd,\n model: llmResult.model,\n tokens: llmResult.tokens,\n }),\n );\n }\n // ── Update org-level cost accumulators in KV ─────────────────────────\n // The KV writes are best-effort. Cloudflare KV does not support atomic\n // compare-and-swap, so concurrent increments may undercount spend.\n if (kv && (opts.dailyCapUsd !== undefined || opts.monthlyCapUsd !== undefined)) {\n await recordOrgCostUsage(kv, todayKey, monthKey, costUsd, opts);\n }\n // ── Metering callback ────────────────────────────────────────────────\n const ledgerCtx = resolveLedgerContext(opts);\n if (deps.onRecord && ledgerCtx) {\n const row: LLMRecordRow = {\n ...ledgerCtx,\n model: llmResult.model,\n provider: llmResult.provider,\n tier: llmResult.tier,\n degraded: llmResult.degraded,\n inputTokens: llmResult.tokens.input,\n outputTokens: llmResult.tokens.output,\n cacheReadTokens: llmResult.tokens.cacheRead ?? 0,\n cacheWriteTokens: llmResult.tokens.cacheWrite ?? 0,\n latencyMs: llmResult.latency,\n costUsd,\n yyyyMm: isoMonth(now()),\n };\n deps.onRecord(row).catch((e: unknown) => {\n logger?.warn?.('llm.onRecord.error', { message: e instanceof Error ? e.message : String(e) });\n });\n }\n return { data: llmResult, error: null };\n } catch (e) {\n if (e instanceof DOMException && e.name === 'AbortError') {\n return toErrorResponse(\n new InternalError('llm call aborted', { provider: leg.provider, model: leg.model }),\n );\n }\n if (isProviderError(e)) {\n attemptLog.push({ provider: e.provider, status: e.status, message: e.message });\n if (e.status === 429 && legIndex === routeLegs.length - 1) {\n return toErrorResponse(\n new RateLimitError(`llm rate limited on ${e.provider}`, { attempts: attemptLog }),\n );\n }\n logger?.warn?.('llm.leg.failed', { provider: leg.provider, status: e.status });\n continue;\n }\n attemptLog.push({ provider: leg.provider, message: e instanceof Error ? e.message : String(e) });\n }\n }\n\n return toErrorResponse(\n new InternalError('LLM_ALL_PROVIDERS_FAILED', { attempts: attemptLog, tier, tokenEstimate }),\n );\n}\n\n// ─── Streaming ────────────────────────────────────────────────────────────\n\n/**\n * Anthropic server-sent event shapes used by the streaming parser.\n * Only the fields we consume are typed; the rest are ignored.\n */\ninterface AnthropicStreamEvent {\n type: string;\n index?: number;\n delta?: { type?: string; text?: string; partial_json?: string; stop_reason?: string };\n content_block?: { type?: string; id?: string; name?: string };\n message?: {\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n };\n model?: string;\n };\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n };\n}\n\n/**\n * Streams a completion from the primary Anthropic provider, yielding text chunks\n * as they arrive. Falls back to the non-streaming {@link complete} function when\n * the provider does not support streaming (i.e. a non-Anthropic primary is selected).\n *\n * The generator's **return value** (accessible via `gen.return()` or by consuming\n * the full iteration) is an {@link LLMResult} with the same shape as {@link complete}.\n *\n * Usage pattern:\n * ```ts\n * const gen = completionStream(messages, env, opts);\n * for await (const chunk of gen) {\n * // stream chunk to client\n * }\n * const result = (await gen.return(undefined)).value; // LLMResult\n * ```\n *\n * @param messages - Ordered chat history.\n * @param env - API key + gateway bindings.\n * @param opts - Optional tier/model/parameters override. Accepts `deps` as nested field.\n * @returns An async generator that yields `string` chunks and returns an {@link LLMResult}.\n */\nexport async function* completionStream(\n messages: LLMMessage[],\n env: LLMEnv,\n opts: LLMOptions & { deps?: LLMDeps } = {},\n): AsyncGenerator<string, LLMResult, unknown> {\n if (messages.length === 0) {\n throw new ValidationError('messages must not be empty');\n }\n if (!env.AI_GATEWAY_BASE_URL) {\n throw new ValidationError('AI_GATEWAY_BASE_URL is required in 0.3.0');\n }\n\n const deps: LLMDeps = opts.deps ?? {};\n const fetchImpl = deps.fetch ?? fetch;\n const now = deps.now ?? (() => Date.now());\n const logger = deps.logger;\n const startedAt = now();\n\n const tier: LLMTier = opts.tier ?? 'balanced';\n const system = systemText(opts, messages);\n const tokenEstimate = estimateTokens(messages, system);\n const route = plan(tier, opts, tokenEstimate);\n const streamLeg =\n route.primary.provider === 'grok' && !env.GROK_API_KEY && route.fallback?.provider === 'anthropic'\n ? route.fallback\n : route.primary;\n\n // Only Anthropic supports streaming in the current implementation.\n // For all other primaries, fall back to non-streaming complete().\n if (streamLeg.provider !== 'anthropic') {\n const result = await complete(messages, env, opts, deps);\n if (result.error !== null || result.data === null) {\n throw new InternalError('LLM_ALL_PROVIDERS_FAILED', { error: result.error });\n }\n yield result.data.content;\n return result.data;\n }\n\n // Check cooldown before attempting the streaming call.\n if (isProviderCoolingDown(streamLeg.provider, now)) {\n logger?.warn?.('llm.provider.coolingDown', { provider: streamLeg.provider });\n // Fall back to non-streaming complete() which will handle the fallback leg.\n const result = await complete(messages, env, opts, deps);\n if (result.error !== null || result.data === null) {\n throw new InternalError('LLM_ALL_PROVIDERS_FAILED', { error: result.error });\n }\n yield result.data.content;\n return result.data;\n }\n\n const req = buildAnthropicRequest(streamLeg.model, messages, opts, env, true);\n // Attribution for the shared AI Gateway (matches the non-streaming path).\n const streamAigMetadata = buildAigMetadata(opts);\n if (streamAigMetadata) req.headers['cf-aig-metadata'] = streamAigMetadata;\n\n let response: Response;\n try {\n response = await fetchImpl(req.url, {\n method: 'POST',\n headers: req.headers,\n body: req.body,\n // Fall back to a 60 s default when the caller provides no signal — prevents\n // a hung provider connection from consuming the Worker's wall-clock budget.\n signal: opts.signal ?? AbortSignal.timeout(60_000),\n });\n } catch (e) {\n if (e instanceof DOMException && e.name === 'AbortError') {\n throw new InternalError('llm call aborted', {\n provider: streamLeg.provider,\n model: streamLeg.model,\n });\n }\n throw new InternalError('llm stream fetch failed', {\n message: e instanceof Error ? e.message : String(e),\n });\n }\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n const retryable = isRetryableForBackoff(response.status);\n if (retryable && response.status === 429) {\n markProviderCoolingDown(streamLeg.provider, now);\n }\n // Fall back to non-streaming complete() which will try the fallback leg.\n const result = await complete(messages, env, opts, deps);\n if (result.error !== null || result.data === null) {\n throw new InternalError('LLM_ALL_PROVIDERS_FAILED', {\n streamError: `${streamLeg.provider} ${String(response.status)}: ${text.slice(0, 300)}`,\n error: result.error,\n });\n }\n yield result.data.content;\n return result.data;\n }\n\n if (!response.body) {\n throw new InternalError('llm stream response body is null', {\n provider: streamLeg.provider,\n });\n }\n\n // Stream SSE events from Anthropic.\n const decoder = new TextDecoder();\n let accumulatedText = '';\n let inputTokens = 0;\n let outputTokens = 0;\n let cacheRead = 0;\n let cacheWrite = 0;\n let modelName: string | undefined;\n // Tool-use blocks arrive as content_block_start (id/name) then a sequence of\n // input_json_delta fragments that concatenate into the arguments JSON string.\n const toolBlocks = new Map<number, { id: string; name: string; json: string }>();\n let streamStopReason: LLMResult['stopReason'];\n const gatewayRequestId: string | undefined = response.headers.get('cf-aig-request-id') ?? undefined;\n\n const reader = response.body.getReader();\n let buffer = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n // SSE lines are delimited by '\\n'. Events are separated by '\\n\\n'.\n const lines = buffer.split('\\n');\n // Keep the last (potentially incomplete) line in the buffer.\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue;\n const data = line.slice(6).trim();\n if (data === '[DONE]') break;\n let event: AnthropicStreamEvent;\n try {\n event = JSON.parse(data) as AnthropicStreamEvent;\n } catch {\n continue; // Skip malformed SSE lines.\n }\n\n switch (event.type) {\n case 'message_start':\n inputTokens = event.message?.usage?.input_tokens ?? 0;\n cacheRead = event.message?.usage?.cache_read_input_tokens ?? 0;\n cacheWrite = event.message?.usage?.cache_creation_input_tokens ?? 0;\n modelName = event.message?.model;\n break;\n case 'content_block_start':\n if (\n event.content_block?.type === 'tool_use' &&\n typeof event.index === 'number' &&\n typeof event.content_block.id === 'string' &&\n typeof event.content_block.name === 'string'\n ) {\n toolBlocks.set(event.index, { id: event.content_block.id, name: event.content_block.name, json: '' });\n }\n break;\n case 'content_block_delta':\n if (event.delta?.type === 'text_delta' && typeof event.delta.text === 'string') {\n accumulatedText += event.delta.text;\n yield event.delta.text;\n } else if (\n event.delta?.type === 'input_json_delta' &&\n typeof event.delta.partial_json === 'string' &&\n typeof event.index === 'number'\n ) {\n const block = toolBlocks.get(event.index);\n if (block) block.json += event.delta.partial_json;\n }\n break;\n case 'message_delta':\n outputTokens = event.usage?.output_tokens ?? outputTokens;\n if (event.delta?.stop_reason) streamStopReason = normalizeAnthropicStop(event.delta.stop_reason);\n break;\n default:\n break;\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n clearProviderCooldown(streamLeg.provider);\n logger?.info?.('llm.completionStream', {\n // `false` means NO caller named a tier, so this call took the\n // package default (`balanced` — claude-sonnet-5, the second most\n // expensive route). Emitted so a defaulted tier is greppable rather\n // than invisible: an expensive route chosen by silence is the thing\n // that made the video pipeline bill Sonnet while its docs said Haiku.\n tierExplicit: opts.tier !== undefined,\n provider: streamLeg.provider,\n model: streamLeg.model,\n tier,\n tokenEstimate,\n runId: opts.runId,\n project: opts.project,\n actor: opts.actor,\n workload: opts.workload,\n });\n\n const toolCalls: LLMToolCall[] = [...toolBlocks.values()].map((b) => ({\n id: b.id,\n name: b.name,\n arguments: parseToolArgs(b.json),\n }));\n\n return {\n content: accumulatedText,\n provider: streamLeg.provider,\n model: modelName ?? streamLeg.model,\n tier,\n tokens: { input: inputTokens, output: outputTokens, cacheRead, cacheWrite },\n latency: now() - startedAt,\n attempts: 1,\n gatewayRequestId,\n stopReason: streamStopReason,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n // streamLeg is only ever route.fallback in the grok-no-key special case above —\n // same \"servedBy != intended\" signal as the non-streaming path, see #4204.\n degraded: streamLeg !== route.primary,\n };\n}\n\n// ─── Grounding assertion ───────────────────────────────────────────────────\n\n/**\n * Returns `true` if `response` contains at least one verbatim phrase of at\n * least 5 consecutive whitespace-delimited tokens that also appears in one of\n * the `sources` strings.\n *\n * Returns `true` unconditionally when `sources` is empty (no grounding\n * documents means grounding cannot be violated).\n *\n * This is a lightweight guard for RAG pipelines — it detects obvious\n * hallucinations where the model generates content not present in any\n * retrieved source. It is NOT a semantic similarity check.\n *\n * @param response - The LLM-generated text to inspect.\n * @param sources - Retrieved source documents to check against.\n * @returns `true` if the response is grounded, `false` if hallucination detected.\n *\n * @example\n * ```ts\n * const grounded = assertGrounding(llmAnswer, retrievedDocs);\n * if (!grounded) {\n * // flag or re-rank the response\n * }\n * ```\n */\nexport function assertGrounding(response: string, sources: string[]): boolean {\n if (sources.length === 0) return true;\n\n const WINDOW = 5;\n const responseTokens = response.split(/\\s+/).filter((t) => t.length > 0);\n\n if (responseTokens.length < WINDOW) return false;\n\n // Build a set of all 5-token ngrams from each source for O(n) lookup.\n const sourceNgrams = new Set<string>();\n for (const source of sources) {\n const tokens = source.split(/\\s+/).filter((t) => t.length > 0);\n for (let i = 0; i <= tokens.length - WINDOW; i++) {\n const ngram = tokens.slice(i, i + WINDOW).join(' ');\n sourceNgrams.add(ngram);\n }\n }\n\n if (sourceNgrams.size === 0) return false;\n\n // Slide a window of WINDOW tokens over the response and check for a match.\n for (let i = 0; i <= responseTokens.length - WINDOW; i++) {\n const ngram = responseTokens.slice(i, i + WINDOW).join(' ');\n if (sourceNgrams.has(ngram)) return true;\n }\n\n return false;\n}\n\n// ─── Exported helpers (kept for existing consumers) ───────────────────────\n\nexport { MODELS, isProviderCoolingDown, markProviderCoolingDown, clearProviderCooldown, PROVIDER_COOLDOWN_MS };\nexport { BASE_BACKOFF_MS };\n\n// ─── Embeddings ────────────────────────────────────────────────────────────\n\nexport { embed, embedLocal, DEFAULT_EMBEDDING_MODEL, LOCAL_EMBEDDING_MODEL } from './embed.js';\nexport type { AiBinding, EmbedResult, EmbeddingModel, LocalEmbedEnv } from './embed.js';\n\nexport { mintGcpAccessToken, clearGcpTokenCache, serviceAccountProjectId } from './gcp-token.js';\n","/**\n * Mint short-lived Google Cloud OAuth2 access tokens from a service-account key,\n * inside a Cloudflare Worker.\n *\n * The `gemini` leg authenticates to Vertex AI with a GCP access token, which\n * expires after one hour. A token therefore cannot be a durable Worker secret —\n * any stored value is stale almost immediately. Callers supply the long-lived\n * service-account key instead, and this module exchanges it for a fresh token\n * via the JWT-bearer flow, caching the result per isolate.\n *\n * Web Crypto only — no Node.js built-ins, no `Buffer`. Lifted from\n * `apps/admin-studio/src/lib/gcp-secrets.ts`, where this exact flow has been\n * running in production; hoisted here so every consumer of the `gemini` leg\n * gets a working credential rather than just the one app that hand-rolled it.\n */\nimport { InternalError, ValidationError } from '@latimer-woods-tech/errors';\n\n/** The subset of a GCP service-account JSON key needed for the JWT-bearer flow. */\ninterface ServiceAccountKey {\n project_id: string;\n private_key: string;\n client_email: string;\n token_uri: string;\n}\n\ninterface OAuth2Response {\n access_token: string;\n expires_in: number;\n}\n\n/** Refresh this long before expiry so an in-flight request never races the clock. */\nconst EXPIRY_SKEW_MS = 300_000;\n\n/**\n * Per-isolate token cache, keyed by service-account email.\n *\n * Caching a *string* across requests is safe. Caching an I/O object (a socket, a\n * database client, a `Response`) is not — Workers rejects reuse of I/O created\n * on behalf of a different request. This holds only the token text and its\n * expiry, so it is reused freely.\n */\nconst tokenCache = new Map<string, { token: string; expiresAt: number }>();\n\n/** Decode a base64url-encoded service-account key, tolerating raw JSON too. */\nfunction parseServiceAccountKey(gcpSaKey: string): ServiceAccountKey {\n const text = gcpSaKey.trimStart().startsWith('{') ? gcpSaKey : atob(gcpSaKey);\n const key = JSON.parse(text) as ServiceAccountKey;\n if (!key.client_email || !key.private_key || !key.token_uri) {\n throw new ValidationError('GCP_SA_KEY is missing client_email, private_key, or token_uri');\n }\n return key;\n}\n\n/** Encode bytes as base64url (RFC 4648 §5): `-`/`_` for `+`/`/`, no padding. */\nfunction base64UrlEncode(bytes: Uint8Array): string {\n let binary = '';\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!);\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '');\n}\n\n/** Import a PEM-encoded PKCS#8 private key as a Web Crypto signing key. */\nasync function importPrivateKey(pem: string): Promise<CryptoKey> {\n const body = pem\n .split('\\n')\n .filter((line) => !line.startsWith('-----'))\n .join('');\n const binary = atob(body);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return crypto.subtle.importKey(\n 'pkcs8',\n bytes.buffer,\n { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },\n false,\n ['sign'],\n );\n}\n\n/** Build a service-account JWT assertion signed with RS256, per GCP's spec. */\nasync function createAssertion(key: ServiceAccountKey, nowSeconds: number): Promise<string> {\n const encoder = new TextEncoder();\n const header = base64UrlEncode(encoder.encode(JSON.stringify({ alg: 'RS256', typ: 'JWT' })));\n const payload = base64UrlEncode(\n encoder.encode(\n JSON.stringify({\n iss: key.client_email,\n scope: 'https://www.googleapis.com/auth/cloud-platform',\n aud: key.token_uri,\n exp: nowSeconds + 3600,\n iat: nowSeconds,\n }),\n ),\n );\n const signingInput = `${header}.${payload}`;\n const cryptoKey = await importPrivateKey(key.private_key);\n const signature = await crypto.subtle.sign(\n 'RSASSA-PKCS1-v1_5',\n cryptoKey,\n encoder.encode(signingInput).buffer,\n );\n return `${signingInput}.${base64UrlEncode(new Uint8Array(signature))}`;\n}\n\n/**\n * Exchange a service-account key for a cloud-platform access token, reusing a\n * cached token until it is within {@link EXPIRY_SKEW_MS} of expiring.\n *\n * @param gcpSaKey - Service-account JSON key, base64-encoded or raw.\n * @param fetchImpl - Fetch implementation (injectable for tests).\n * @returns A bearer token valid for Vertex AI.\n * @throws ValidationError when the key is malformed; InternalError when the exchange fails.\n */\nexport async function mintGcpAccessToken(gcpSaKey: string, fetchImpl: typeof fetch): Promise<string> {\n const key = parseServiceAccountKey(gcpSaKey);\n\n const now = Date.now();\n const cached = tokenCache.get(key.client_email);\n if (cached && cached.expiresAt - EXPIRY_SKEW_MS > now) return cached.token;\n\n const assertion = await createAssertion(key, Math.floor(now / 1000));\n const response = await fetchImpl(key.token_uri, {\n method: 'POST',\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',\n assertion,\n }).toString(),\n }).catch((cause: unknown) => {\n throw new InternalError(`GCP token exchange failed: ${String(cause)}`);\n });\n\n if (!response.ok) {\n throw new InternalError(`GCP token exchange returned ${response.status}`);\n }\n\n const data = (await response.json()) as OAuth2Response;\n if (!data.access_token) {\n throw new InternalError('GCP token exchange returned no access_token');\n }\n\n // Trust the server's TTL rather than assuming the 3600s default.\n const ttlMs = (data.expires_in > 0 ? data.expires_in : 3600) * 1000;\n tokenCache.set(key.client_email, { token: data.access_token, expiresAt: now + ttlMs });\n return data.access_token;\n}\n\n// ─── Application Default Credentials (GCP metadata server) ───────────────────\n/**\n * GCE / Cloud Run metadata server base. Reachable only from GCP compute; the\n * hostname does not resolve elsewhere, so an off-GCP probe fails fast.\n */\nconst METADATA_BASE = 'http://metadata.google.internal/computeMetadata/v1';\n/** Required header for every metadata-server request (anti-SSRF guard). */\nconst METADATA_HEADER = { 'Metadata-Flavor': 'Google' } as const;\n/**\n * Bound the metadata probe so a non-GCP environment (where the host may hang\n * rather than refuse) falls through to the next provider quickly.\n */\nconst METADATA_TIMEOUT_MS = 2_000;\n\n/** Per-isolate cache for the ADC (metadata-server) credential. */\nlet adcCache: { token: string; project?: string; expiresAt: number } | undefined;\n\ninterface AdcCredential {\n token: string;\n /** The instance's own GCP project id, as reported by the metadata server. */\n project?: string;\n}\n\n/**\n * Obtain an access token (and project id) from the GCP metadata server using\n * Application Default Credentials — the keyless credential every Cloud Run /\n * GCE workload already has via its attached service account. No key, no env\n * var: the instance SA's cloud-platform token is served by the metadata\n * server. This mirrors the render-runner's own `_meta_token` shell helper.\n *\n * Fails (throws {@link InternalError}) when the metadata server is unreachable\n * or returns no token — i.e. off-GCP — so the caller falls through to the next\n * provider leg. Never throws a bare `AbortError` (which the router treats as a\n * hard cancellation): an internal timeout is surfaced as an `InternalError`.\n *\n * @param fetchImpl - Fetch implementation (injectable for tests).\n * @returns A bearer token valid for Vertex AI plus the ambient project id.\n */\nexport async function fetchAdcAccessToken(fetchImpl: typeof fetch): Promise<AdcCredential> {\n const now = Date.now();\n if (adcCache && adcCache.expiresAt - EXPIRY_SKEW_MS > now) {\n return { token: adcCache.token, project: adcCache.project };\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), METADATA_TIMEOUT_MS);\n try {\n const tokenRes = await fetchImpl(\n `${METADATA_BASE}/instance/service-accounts/default/token`,\n { headers: METADATA_HEADER, signal: controller.signal },\n );\n if (!tokenRes.ok) {\n throw new InternalError(`GCP metadata token endpoint returned ${tokenRes.status}`);\n }\n const data = (await tokenRes.json()) as OAuth2Response;\n if (!data.access_token) {\n throw new InternalError('GCP metadata token endpoint returned no access_token');\n }\n\n // Project id is best-effort: a caller may pin VERTEX_PROJECT instead.\n let project: string | undefined;\n try {\n const projRes = await fetchImpl(`${METADATA_BASE}/project/project-id`, {\n headers: METADATA_HEADER,\n signal: controller.signal,\n });\n if (projRes.ok) project = (await projRes.text()).trim() || undefined;\n } catch {\n /* project id is optional — the token is still usable */\n }\n\n const ttlMs = (data.expires_in > 0 ? data.expires_in : 3600) * 1000;\n adcCache = { token: data.access_token, project, expiresAt: now + ttlMs };\n return { token: data.access_token, project };\n } catch (cause) {\n // Normalize an internal-timeout AbortError into an InternalError so the\n // router falls through to the next leg instead of hard-cancelling the call.\n if (cause instanceof InternalError) throw cause;\n throw new InternalError(`GCP metadata credential unavailable: ${String(cause)}`);\n } finally {\n clearTimeout(timer);\n }\n}\n\n/** Reset the per-isolate token cache. Tests only. */\nexport function clearGcpTokenCache(): void {\n tokenCache.clear();\n adcCache = undefined;\n}\n\n/** The project the service account belongs to — the Vertex project by default. */\nexport function serviceAccountProjectId(gcpSaKey: string): string {\n return parseServiceAccountKey(gcpSaKey).project_id;\n}\n","/**\n * Workers AI embedding API.\n *\n * Pins to bge-base-en-v1.5 (768-dim cosine) — the platform standard.\n * @remarks Local-rail errors use @latimer-woods-tech/errors (observability ratchet).\n * model_version is returned with every result so callers can record it\n * for provenance-aware re-embed when the model is swapped.\n *\n * Contract: inject the Workers AI binding (env.AI) — never import a vendor SDK.\n */\n\nimport { InternalError } from '@latimer-woods-tech/errors';\n\n/** The platform-standard embedding model (768-dim cosine) used when no override is given. */\nexport const DEFAULT_EMBEDDING_MODEL = '@cf/baai/bge-base-en-v1.5';\n\n/** Supported embedding model identifiers — widen deliberately, since dims are part of the contract. */\nexport type EmbeddingModel = '@cf/baai/bge-base-en-v1.5';\n\n/** One embedding call's output: the vectors plus the provenance needed to re-embed later. */\nexport interface EmbedResult {\n vectors: number[][];\n model: string;\n dims: number;\n}\n\n/** Minimal Workers AI binding shape needed for embeddings. */\nexport interface AiBinding {\n run(\n model: string,\n inputs: { text: string | string[] },\n ): Promise<{ data: number[][] }>;\n}\n\n/**\n * Embeds one or more text strings using the Workers AI binding.\n *\n * @param ai - Workers AI binding (`env.AI`) — injected, not imported.\n * @param input - Single string or array of strings to embed.\n * @param opts - Optional model override (must be a supported EmbeddingModel).\n * @returns Embedding vectors, model name, and dimension count.\n *\n * @throws When the AI binding call fails (let the caller decide whether to catch).\n */\nexport async function embed(\n ai: AiBinding,\n input: string | string[],\n opts?: { model?: EmbeddingModel },\n): Promise<EmbedResult> {\n const model = opts?.model ?? DEFAULT_EMBEDDING_MODEL;\n const texts = Array.isArray(input) ? input : [input];\n const result = await ai.run(model, { text: texts });\n const vectors = result.data;\n if (!vectors || vectors.length === 0) {\n throw new Error(`embed(): Workers AI returned no vectors for model ${model}`);\n }\n return {\n vectors,\n model,\n dims: vectors[0]!.length,\n };\n}\n\n/**\n * Self-hosted GPU embedding model (nomic-embed-text, 768-dim), reached through\n * the same CF AI Gateway custom provider as the local chat provider.\n *\n * ⚠️ VECTOR-SPACE WARNING: nomic-embed-text and bge-base-en-v1.5 are BOTH 768-dim\n * but occupy DIFFERENT embedding spaces. Their vectors are NOT comparable. A store\n * built with one model must be QUERIED — and, when migrating, RE-EMBEDDED — with\n * the SAME model. Never write vectors from both models into one store.\n */\nexport const LOCAL_EMBEDDING_MODEL = 'nomic-embed-text';\n\n/** Env subset needed to reach the local embedding rail (shares GPU_LLM_* with the chat provider). */\nexport interface LocalEmbedEnv {\n AI_GATEWAY_BASE_URL: string;\n GPU_LLM_API_TOKEN?: string;\n GPU_LLM_ACCESS_CLIENT_ID?: string;\n GPU_LLM_ACCESS_CLIENT_SECRET?: string;\n}\n\n/**\n * Embeds text on the self-hosted GPU rail (nomic-embed-text) via the AI Gateway\n * custom provider — the $0 alternative to Workers AI {@link embed}.\n *\n * Unlike the local CHAT provider, this has NO cross-provider fallback ON PURPOSE:\n * falling back to Workers AI (bge) would silently write an incompatible-space\n * vector into a nomic store and corrupt retrieval. If the rail is unavailable\n * this THROWS and the caller decides (retry local, or defer the write) — it must\n * NOT substitute a different model.\n *\n * @param env - Gateway base URL + GPU_LLM_* auth (same secrets as the chat provider).\n * @param input - Single string or array of strings to embed.\n * @returns Embedding vectors, model `nomic-embed-text`, and dimension count.\n * @throws On any transport/HTTP/empty-response error (no silent fallback).\n */\nexport async function embedLocal(\n env: LocalEmbedEnv,\n input: string | string[],\n): Promise<EmbedResult> {\n const texts = Array.isArray(input) ? input : [input];\n const res = await fetch(\n `${env.AI_GATEWAY_BASE_URL}/custom-local-gpu/v1/embeddings`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n // CF Access service-token headers only sent when present (bearer-only rails still work).\n ...(env.GPU_LLM_API_TOKEN ? { Authorization: `Bearer ${env.GPU_LLM_API_TOKEN}` } : {}),\n ...(env.GPU_LLM_ACCESS_CLIENT_ID ? { 'CF-Access-Client-Id': env.GPU_LLM_ACCESS_CLIENT_ID } : {}),\n ...(env.GPU_LLM_ACCESS_CLIENT_SECRET ? { 'CF-Access-Client-Secret': env.GPU_LLM_ACCESS_CLIENT_SECRET } : {}),\n },\n body: JSON.stringify({ model: LOCAL_EMBEDDING_MODEL, input: texts }),\n },\n );\n if (!res.ok) {\n throw new InternalError(\n `embedLocal(): rail ${res.status}: ${(await res.text()).slice(0, 160)}`,\n );\n }\n const json = (await res.json()) as { data?: Array<{ embedding: number[] }> };\n const vectors = (json.data ?? []).map((d) => d.embedding);\n if (vectors.length === 0) {\n throw new InternalError(\n `embedLocal(): rail returned no vectors for model ${LOCAL_EMBEDDING_MODEL}`,\n );\n }\n return {\n vectors,\n model: LOCAL_EMBEDDING_MODEL,\n dims: vectors[0]!.length,\n };\n}\n"],"mappings":"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","names":["InternalError","ValidationError","InternalError","ValidationError","InternalError"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@latimer-woods-tech/llm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"repository": {
|
|
@@ -37,12 +37,13 @@
|
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@latimer-woods-tech/monitoring": "^0.2.0",
|
|
39
39
|
"@types/node": "^25.6.0",
|
|
40
|
-
"typescript": "^
|
|
41
|
-
"
|
|
42
|
-
"vitest": "^3.2.4",
|
|
40
|
+
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
41
|
+
"@typescript-eslint/parser": "^7.0.0",
|
|
43
42
|
"@vitest/coverage-v8": "^3.2.4",
|
|
44
43
|
"eslint": "^8.57.0",
|
|
45
|
-
"
|
|
46
|
-
"
|
|
44
|
+
"fast-check": "^3.23.2",
|
|
45
|
+
"tsup": "^8.1.0",
|
|
46
|
+
"typescript": "^5.4.0",
|
|
47
|
+
"vitest": "^3.2.4"
|
|
47
48
|
}
|
|
48
49
|
}
|