@hourslabs/domovoi 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +262 -19
- package/dist/budget-tracker.d.ts +72 -0
- package/dist/budget-tracker.d.ts.map +1 -0
- package/dist/calibration/index.js.map +1 -1
- package/dist/context-storage.d.ts +44 -0
- package/dist/context-storage.d.ts.map +1 -0
- package/dist/engine/decide.d.ts +9 -0
- package/dist/engine/decide.d.ts.map +1 -1
- package/dist/engine/distribution.d.ts +1 -1
- package/dist/engine/distribution.d.ts.map +1 -1
- package/dist/engine/finalize.d.ts +7 -0
- package/dist/engine/finalize.d.ts.map +1 -1
- package/dist/errors.d.ts +18 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +256 -39
- package/dist/index.js.map +1 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/scope.d.ts +98 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/testing/distribution.d.ts +66 -0
- package/dist/testing/distribution.d.ts.map +1 -0
- package/dist/testing/index.d.ts +7 -3
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +136 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/tracer.d.ts +37 -0
- package/dist/tracer.d.ts.map +1 -0
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,32 +37,30 @@ Some decisions in software resist code. Is "SQ *COFFEE 0421" a restaurant or a g
|
|
|
37
37
|
|
|
38
38
|
Rules and trained classifiers have tackled this for decades. Rules break on edge cases, then multiply until they collapse under their own weight. Classifiers return a confidence score and leave the handling to you. They don't know what they don't know.
|
|
39
39
|
|
|
40
|
-
domovoi is a single function call at the decision point. Unlike agent frameworks or workflow engines, it doesn't restructure how you build — it drops into existing code like any other dependency. Ask it, get a typed `Verdict`,
|
|
40
|
+
domovoi is a single function call at the decision point. Unlike agent frameworks or workflow engines, it doesn't restructure how you build — it drops into existing code like any other dependency. Ask it, get a typed `Verdict`, and dispatch.
|
|
41
41
|
|
|
42
|
-
The `Verdict` is the core idea.
|
|
42
|
+
The `Verdict` is the core idea. Rather than a string or a confidence score, domovoi returns one of three typed states: `Classified` when confident, `Uncertain` when the top answer falls below threshold, `Unknown` when no answer is possible. Uncertainty becomes a first-class value your type system understands, not a silent wrong answer. Everything around the `Verdict` stays deterministic.
|
|
43
43
|
|
|
44
44
|
```tsx
|
|
45
45
|
import { domovoi, match } from "@hourslabs/domovoi";
|
|
46
46
|
|
|
47
|
-
async function processTransaction(
|
|
48
|
-
|
|
49
|
-
if (await fraud.isSuspicious(txn)) return holds.queue(txn);
|
|
47
|
+
async function processTransaction(transaction: Transaction): Promise<void> {
|
|
48
|
+
if (await fraud.isSuspicious(transaction)) return holds.queue(transaction);
|
|
50
49
|
|
|
50
|
+
const account = await accounts.get(transaction.accountId);
|
|
51
51
|
const verdict = await domovoi.classify(
|
|
52
|
-
|
|
53
|
-
account.budget.categories
|
|
52
|
+
transaction.merchant, // e.g. "NETFLIX.COM"
|
|
53
|
+
account.budget.categories, // e.g. ["shopping", "groceries", ...]
|
|
54
54
|
);
|
|
55
55
|
|
|
56
56
|
await match(verdict, {
|
|
57
|
-
classified: ({ value })
|
|
58
|
-
uncertain: ({ top, runnerUp }) =>
|
|
59
|
-
|
|
60
|
-
unknown: ({ reason }) =>
|
|
61
|
-
retryQueue.schedule(txn, { reason, delayMs: 5 * 60_000 }),
|
|
57
|
+
classified: ({ value }) => budget.attribute(account, transaction, value),
|
|
58
|
+
uncertain: ({ top, runnerUp }) => budget.attributePending(account, transaction, top, runnerUp),
|
|
59
|
+
unknown: ({ reason }) => transactions.markUncategorized(transaction, reason),
|
|
62
60
|
});
|
|
63
61
|
|
|
64
|
-
await receipts.archive(
|
|
65
|
-
events.emit("
|
|
62
|
+
await receipts.archive(transaction);
|
|
63
|
+
events.emit("transaction.processed", transaction.id);
|
|
66
64
|
}
|
|
67
65
|
```
|
|
68
66
|
|
|
@@ -101,14 +99,73 @@ The `AMZN MKTP` row shows why the third state exists: it could be shopping or gr
|
|
|
101
99
|
|
|
102
100
|
---
|
|
103
101
|
|
|
104
|
-
##
|
|
102
|
+
## Where Heuristics Break Down
|
|
105
103
|
|
|
106
|
-
|
|
104
|
+
Use domovoi for decisions that are obvious to a human, hard to encode in rules, and safe to get wrong within bounds.
|
|
107
105
|
|
|
108
|
-
- **Intent routing** — refund, complaint, or question.
|
|
106
|
+
- **Intent routing** — refund, complaint, or question. Rule sets and regex won't cover the full input space.
|
|
109
107
|
- **Content classification** — tag an article, ticket, or submission against your taxonomy. Replace brittle keyword rules with a classifier that handles edge cases.
|
|
110
|
-
- **Tiered dispatch** — chain
|
|
111
|
-
- **Free-form validation + privacy filters** — does this description match the product? Does this
|
|
108
|
+
- **Tiered dispatch** — chain models (e.g., cheap → expensive). The cheaper model handles easy cases; the stronger model runs only on `Uncertain`. Costs drop meaningfully when ~70–80% resolve at the lower tier.
|
|
109
|
+
- **Free-form validation + privacy filters** — does this description match the product? Does this bio violate guidelines? Does this input contain PII or a prompt-injection attempt?
|
|
110
|
+
|
|
111
|
+
This same shape shows up across mainstream libraries: [Mozilla Readability](https://github.com/mozilla/readability) and [Mercury Parser](https://github.com/postlight/parser) for DOM-based article extraction, [GitHub Linguist](https://github.com/github-linguist/linguist) for language detection, and [email-reply-parser](https://github.com/crisp-oss/email-reply-parser/blob/master/lib/regex.ts) and [Talon](https://github.com/mailgun/talon) for email fragment parsing. Under the hood, they rely on dense stacks of regex and heuristics that grow in complexity without ever fully solving the problem.
|
|
112
|
+
|
|
113
|
+
Here's a paraphrased example from email-reply-parser:
|
|
114
|
+
|
|
115
|
+
<table>
|
|
116
|
+
<tr><th>Before</th><th>With domovoi</th></tr>
|
|
117
|
+
<tr>
|
|
118
|
+
<td valign="top">
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const QUOTE_HEADERS = [
|
|
122
|
+
/^-*\s*(On\s.+\swrote:{0,1})\s*-*$/m, // EN
|
|
123
|
+
/^-*\s*(Le\s.+\sécrit\s?:{0,1})\s*-*$/m, // FR
|
|
124
|
+
/^\s*(Am\s.+schrieb.+):$/m, // DE
|
|
125
|
+
/^(在[\s\S]+写道:)$/m, // ZH
|
|
126
|
+
/^(20[0-9]{2}\..+\s작성:)$/m, // KO
|
|
127
|
+
// ...25 more locale variants
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
const SIGNATURE_SEPS = [
|
|
131
|
+
/^\s*-{2,4}$/, /^\s*_{2,4}$/, /^-- $/,
|
|
132
|
+
// ...18 patterns total
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
function classify(line: string) {
|
|
136
|
+
if (QUOTE_HEADERS.some(r => r.test(line)))
|
|
137
|
+
return "quote";
|
|
138
|
+
if (SIGNATURE_SEPS.some(r => r.test(line)))
|
|
139
|
+
return "signature";
|
|
140
|
+
return "body";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// "-- pricing tier --" → signature.
|
|
144
|
+
// Body silently dropped.
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
</td>
|
|
148
|
+
<td valign="top">
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
const fragment = await domovoi.classify(
|
|
152
|
+
line,
|
|
153
|
+
["quote", "signature", "body"],
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
await match(fragment, {
|
|
157
|
+
classified: ({ value }) =>
|
|
158
|
+
record(line, value),
|
|
159
|
+
uncertain: ({ top, runnerUp }) =>
|
|
160
|
+
record(line, top, { lowConfidence: runnerUp }),
|
|
161
|
+
unknown: () =>
|
|
162
|
+
record(line, "body"),
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
</td>
|
|
167
|
+
</tr>
|
|
168
|
+
</table>
|
|
112
169
|
|
|
113
170
|
---
|
|
114
171
|
|
|
@@ -246,6 +303,192 @@ controller.abort("budget_exceeded");
|
|
|
246
303
|
|
|
247
304
|
---
|
|
248
305
|
|
|
306
|
+
## Scopes
|
|
307
|
+
|
|
308
|
+
A `domovoi.classify` call deep in a request handler needs three things from its environment: a cost ceiling, a cancellation signal, and observability. Threading them through every layer of your stack as arguments is tedious. Scopes make them ambient.
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
import { domovoi } from "@hourslabs/domovoi";
|
|
312
|
+
|
|
313
|
+
await domovoi.scope(
|
|
314
|
+
{ budget: { tokens: 50_000 }, signal: req.signal, tracer },
|
|
315
|
+
async () => {
|
|
316
|
+
// Every classify inside this scope inherits the budget, the signal,
|
|
317
|
+
// and the tracer.
|
|
318
|
+
await processBatch(items);
|
|
319
|
+
},
|
|
320
|
+
);
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
If `processBatch` calls helpers that classify, they share the same running budget. When the budget hits zero, the next classify returns `Unknown { reason: { type: "budget_exceeded", spent, limit } }` rather than spend more.
|
|
324
|
+
|
|
325
|
+
### Predictable cost
|
|
326
|
+
|
|
327
|
+
The failure mode that makes finance teams nervous about LLM-backed code is the runaway loop — an infinite call quietly burning through a month of budget in an afternoon. Scope budgets are the circuit breaker:
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
await domovoi.scope({ budget: { tokens: 10_000 } }, async () => {
|
|
331
|
+
for (const item of items) {
|
|
332
|
+
const v = await domovoi.classify(item.text, ["a", "b"]);
|
|
333
|
+
if (v.kind === "unknown" && v.reason.type === "budget_exceeded") break;
|
|
334
|
+
// ...
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Default mode is graceful: classify returns `Unknown` when the limit is hit. For hard-fail behavior, set `onExceeded: "throw"` — classify throws `BudgetExceededError` instead.
|
|
340
|
+
|
|
341
|
+
### Scope-level cancellation
|
|
342
|
+
|
|
343
|
+
Scope signals combine with per-call signals via `AbortSignal.any`. Either firing aborts the in-flight provider call.
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
const ac = new AbortController();
|
|
347
|
+
setTimeout(() => ac.abort("user navigated away"), 5_000);
|
|
348
|
+
|
|
349
|
+
await domovoi.scope({ signal: ac.signal }, async () => {
|
|
350
|
+
await domovoi.classify(input, space);
|
|
351
|
+
});
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Observability
|
|
355
|
+
|
|
356
|
+
Pass a `Tracer` and domovoi emits one span per provider call, following the [OpenTelemetry GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/) for `gen_ai.*` fields and reserving `domovoi.*` for verdict-shaped concepts:
|
|
357
|
+
|
|
358
|
+
| Attribute | Carries |
|
|
359
|
+
|---|---|
|
|
360
|
+
| `gen_ai.provider.name` | `"openai"`, `"anthropic"`, etc. |
|
|
361
|
+
| `gen_ai.request.model` | requested model id |
|
|
362
|
+
| `gen_ai.usage.input_tokens` / `output_tokens` | per-call token counts |
|
|
363
|
+
| `domovoi.verdict.kind` | `"classified"` / `"uncertain"` / `"unknown"` |
|
|
364
|
+
| `domovoi.verdict.value` | selected label when classified |
|
|
365
|
+
| `domovoi.cache.hit` | whether the call was served from cache |
|
|
366
|
+
|
|
367
|
+
A short adapter wires your existing OpenTelemetry tracer in:
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
import { trace } from "@opentelemetry/api";
|
|
371
|
+
import type { Tracer } from "@hourslabs/domovoi";
|
|
372
|
+
|
|
373
|
+
const otel = trace.getTracer("my-app");
|
|
374
|
+
const tracer: Tracer = {
|
|
375
|
+
startSpan: (name, attrs) => otel.startSpan(name, { attributes: attrs }),
|
|
376
|
+
};
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Datadog, Honeycomb, Grafana Cloud, and Dynatrace populate their AI Observability views from `gen_ai.*` attributes automatically.
|
|
380
|
+
|
|
381
|
+
### Resolution order
|
|
382
|
+
|
|
383
|
+
Each `domovoi.classify` call resolves its budget, signal, and tracer in this order:
|
|
384
|
+
|
|
385
|
+
1. Per-call option, e.g. `domovoi.classify(..., { signal })`
|
|
386
|
+
2. Nearest enclosing `domovoi.scope`
|
|
387
|
+
3. No enforcement, no tracing, no budget
|
|
388
|
+
|
|
389
|
+
`AbortSignal` is the one exception — per-call and scope signals combine, rather than the per-call value overriding the scope.
|
|
390
|
+
|
|
391
|
+
Nested scopes inherit unspecified fields from the parent. A child `budget` overrides the parent and starts a fresh counter. A child `tracer` overrides. A child `signal` combines.
|
|
392
|
+
|
|
393
|
+
### Bind: scope across async boundaries
|
|
394
|
+
|
|
395
|
+
Queue workers, cron jobs, and `setTimeout` callbacks run *outside* the original async context. `domovoi.bind` captures the current scope and re-applies it on later invocation:
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
await domovoi.scope({ budget: { tokens: 50_000 }, tracer }, async () => {
|
|
399
|
+
const job = domovoi.bind(async (item: Item) => {
|
|
400
|
+
return domovoi.classify(item.text, ["a", "b"]);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Queue runs `job` later, in a different async context. The captured
|
|
404
|
+
// scope is re-applied: budget and tracer still flow through.
|
|
405
|
+
await queue.push(job, items);
|
|
406
|
+
});
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Mirrors Node's `AsyncLocalStorage.bind` and OpenTelemetry's `context.bind`. Outside any scope, `domovoi.bind(fn)` returns `fn` unchanged.
|
|
410
|
+
|
|
411
|
+
### Backward compatibility
|
|
412
|
+
|
|
413
|
+
Calls outside any scope are unchanged: no enforcement, no tracing, no budget. Existing code keeps working without changes.
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## Testing
|
|
418
|
+
|
|
419
|
+
Two primitives in `@hourslabs/domovoi/testing` cover the testing surface:
|
|
420
|
+
|
|
421
|
+
- `mockProvider({ behavior })` — a `Provider` stub for unit-testing engine logic without hitting a real LLM.
|
|
422
|
+
- `distribution(fn, { n })` — runs `n` real samples and returns Wilson-CI-backed assertions about behavior stability.
|
|
423
|
+
|
|
424
|
+
### mockProvider — unit tests without an LLM
|
|
425
|
+
|
|
426
|
+
Most engine and threshold logic doesn't need a real model. `mockProvider` lets you supply a deterministic Distribution per call:
|
|
427
|
+
|
|
428
|
+
```ts
|
|
429
|
+
import { mockProvider } from "@hourslabs/domovoi/testing";
|
|
430
|
+
|
|
431
|
+
const stub = mockProvider({
|
|
432
|
+
behavior: () => ({ probs: { yes: 0.92, no: 0.08 }, coverage: 0.95 }),
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
const c = domovoi.classifier({
|
|
436
|
+
space: ["yes", "no"] as const,
|
|
437
|
+
thresholds: { high: 0.7, low: 0.3 },
|
|
438
|
+
providers: [stub],
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
const v = await c("input that the mock ignores");
|
|
442
|
+
// v.kind === "classified", v.value === "yes" — deterministic, no network
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Useful for threshold logic, provider-chain fallback, calibrator math, error-handling paths — anything that's about *engine* behavior rather than *model* behavior. Zero LLM calls; runs anywhere.
|
|
446
|
+
|
|
447
|
+
### distribution — assert against AI behavior
|
|
448
|
+
|
|
449
|
+
Single-sample assertions on AI behavior are meaningless: the model varies between runs. `distribution()` runs `n` real samples and turns "the classifier should reliably tag greetings" into a one-liner backed by a Wilson confidence interval:
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
import { distribution } from "@hourslabs/domovoi/testing";
|
|
453
|
+
|
|
454
|
+
const dist = await distribution(
|
|
455
|
+
() => domovoi.classify("hello there", ["greeting", "request"] as const),
|
|
456
|
+
{ n: 100 },
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
dist.coverage("greeting"); // 0.94
|
|
460
|
+
dist.confidenceInterval("greeting"); // [0.88, 0.98] — 95% Wilson CI
|
|
461
|
+
dist.modeKind(); // "classified" | "uncertain" | "unknown"
|
|
462
|
+
|
|
463
|
+
dist.expectStable({
|
|
464
|
+
minCoverage: 0.9, // OR per-label: { greeting: 0.9, request: 0.5 }
|
|
465
|
+
maxUncertain: 0.05,
|
|
466
|
+
maxUnknown: 0.02,
|
|
467
|
+
});
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
Default concurrency is `Math.min(n, 5)` — `n=100` finishes in ~6 seconds against a 300ms p50 provider, well under typical rate limits. Pass `concurrency: 1` to serialize when running multiple `distribution()` tests in parallel.
|
|
471
|
+
|
|
472
|
+
`distribution()` makes `n` real LLM calls. At gpt-4o-mini and `n=100`, that's ~$0.005 per test — belongs in `test:e2e`, not the per-commit unit tier.
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
## Cost
|
|
477
|
+
|
|
478
|
+
A `domovoi.classify` call on gpt-4o-mini costs about $0.00004 — roughly 1/25 of a cent. ~180 input tokens (system prompt + label space + your input) at $0.15/M, plus ~15 output tokens at $0.60/M.
|
|
479
|
+
|
|
480
|
+
| Calls / month | Cost |
|
|
481
|
+
|---------------|---------|
|
|
482
|
+
| 100k | $4 |
|
|
483
|
+
| 10M | $400 |
|
|
484
|
+
| 1B | $40,000 |
|
|
485
|
+
|
|
486
|
+
The default `memoryCache` deduplicates byte-exact-match inputs within a process — significant savings on workloads with repeated inputs (log severity tags, predefined enums, boilerplate replies), little impact on free-form user content where every input is unique. The `Cache` extension point lets you back domovoi with any store for cross-process or persistent caching.
|
|
487
|
+
|
|
488
|
+
Reach for deterministic tools instead when: syntactic problems with stable rules (URL parsing, format validation, tokenizers), very high volume with thin margins (100B+ ad impressions / day), or hard-real-time loops where a cache miss (~300ms p50) blows the SLA.
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
249
492
|
## Calibration
|
|
250
493
|
|
|
251
494
|
Three closed-form scaling factories from `@hourslabs/domovoi/calibration`:
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Running token-budget counter held inside a `ResolvedScope`. Shared across
|
|
3
|
+
* nested `domovoi.scope({...})` calls that inherit the parent's budget,
|
|
4
|
+
* which is why this is a class rather than a closure: the "shared mutable
|
|
5
|
+
* counter" model needs to be explicit.
|
|
6
|
+
*
|
|
7
|
+
* Two-phase enforcement:
|
|
8
|
+
* - `precheck()` before each provider call — short-circuits if already
|
|
9
|
+
* exhausted, so a runaway loop stops on its next iteration.
|
|
10
|
+
* - `charge(tokens)` after each provider call — accumulates real spend.
|
|
11
|
+
*
|
|
12
|
+
* Default mode is `"graceful"`: when exhausted, the engine returns
|
|
13
|
+
* `Unknown { reason: { type: "budget_exceeded", spent, limit } }` instead
|
|
14
|
+
* of throwing. Users who want hard-fail semantics opt into `"throw"` mode.
|
|
15
|
+
*
|
|
16
|
+
* Honest contract: `precheck` measures "have we already exceeded?", not
|
|
17
|
+
* "would this call exceed?" — a single in-flight call can overshoot the
|
|
18
|
+
* limit by one call's worth of tokens. Tighter pre-charge enforcement
|
|
19
|
+
* (estimate via tokenizer before call) is possible but defer; document
|
|
20
|
+
* the overshoot in the README so users know.
|
|
21
|
+
*/
|
|
22
|
+
export type BudgetMode = "graceful" | "throw";
|
|
23
|
+
export type ScopeBudget = {
|
|
24
|
+
readonly tokens?: number;
|
|
25
|
+
readonly onExceeded?: BudgetMode;
|
|
26
|
+
};
|
|
27
|
+
export type BudgetSnapshot = {
|
|
28
|
+
readonly spent: number;
|
|
29
|
+
readonly limit: number;
|
|
30
|
+
readonly mode: BudgetMode;
|
|
31
|
+
};
|
|
32
|
+
type PrecheckResult = {
|
|
33
|
+
readonly ok: true;
|
|
34
|
+
} | {
|
|
35
|
+
readonly ok: false;
|
|
36
|
+
readonly spent: number;
|
|
37
|
+
readonly limit: number;
|
|
38
|
+
};
|
|
39
|
+
export declare class BudgetTracker {
|
|
40
|
+
private readonly limit;
|
|
41
|
+
readonly mode: BudgetMode;
|
|
42
|
+
private spent;
|
|
43
|
+
constructor(limit: number, mode: BudgetMode);
|
|
44
|
+
/**
|
|
45
|
+
* Build a tracker from a public `ScopeBudget`, or return `undefined` if
|
|
46
|
+
* the budget is absent / has no `tokens` field. Used during scope merge.
|
|
47
|
+
*
|
|
48
|
+
* Throws `ConfigError` if `tokens` is non-finite or non-positive — these
|
|
49
|
+
* are construction-time validation failures, not runtime budget exhaustion.
|
|
50
|
+
*/
|
|
51
|
+
static from(budget: ScopeBudget | undefined): BudgetTracker | undefined;
|
|
52
|
+
/** Check whether the budget is already exhausted. Called pre-call. */
|
|
53
|
+
precheck(): PrecheckResult;
|
|
54
|
+
/**
|
|
55
|
+
* Accumulate spend. Called post-call with the actual token cost.
|
|
56
|
+
* Negative or non-finite values are silently clamped to 0 — provider
|
|
57
|
+
* adapters occasionally return malformed token counts, and a budget
|
|
58
|
+
* tracker shouldn't panic on bad telemetry from upstream.
|
|
59
|
+
*/
|
|
60
|
+
charge(tokens: number): void;
|
|
61
|
+
/**
|
|
62
|
+
* Throw `BudgetExceededError` if mode is `"throw"` and budget is exhausted.
|
|
63
|
+
* Caller invokes after `charge()` to enforce hard-fail semantics; under
|
|
64
|
+
* `"graceful"` mode this is a no-op and the next `precheck()` returns
|
|
65
|
+
* `{ ok: false, ... }` for graceful Unknown construction.
|
|
66
|
+
*/
|
|
67
|
+
enforce(): void;
|
|
68
|
+
snapshot(): BudgetSnapshot;
|
|
69
|
+
private isExhausted;
|
|
70
|
+
}
|
|
71
|
+
export {};
|
|
72
|
+
//# sourceMappingURL=budget-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget-tracker.d.ts","sourceRoot":"","sources":["../src/budget-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,OAAO,CAAC;AAE9C,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;CAC3B,CAAC;AAEF,KAAK,cAAc,GACf;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAA;CAAE,GACrB;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3E,qBAAa,aAAa;IAItB,OAAO,CAAC,QAAQ,CAAC,KAAK;aACN,IAAI,EAAE,UAAU;IAJlC,OAAO,CAAC,KAAK,CAAK;gBAGC,KAAK,EAAE,MAAM,EACd,IAAI,EAAE,UAAU;IAGlC;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS;IAWvE,sEAAsE;IACtE,QAAQ,IAAI,cAAc;IAO1B;;;;;OAKG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAK5B;;;;;OAKG;IACH,OAAO,IAAI,IAAI;IAMf,QAAQ,IAAI,cAAc;IAI1B,OAAO,CAAC,WAAW;CAGpB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/errors.ts","../../src/calibration/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Error taxonomy for domovoi.\n *\n * Four classes (`DomovoiError` base + `ProviderError`, `ConfigError`,\n * `BudgetExhaustedError`) with a stable `code` field for fine-grained\n * discrimination. All accept `{ cause }` for ES2022 chaining.\n *\n * The engine canonicalizes anything thrown from `Provider.sample` that isn't\n * already a `DomovoiError` into `ProviderError({ cause })`, so callers always\n * see a known error type. Under the default `onErrorPolicy: \"fallback\"`,\n * runtime errors become `Unknown` Verdict variants rather than throw;\n * `ConfigError` always throws.\n */\n\n/**\n * Stable error codes carried in `error.code`. Discriminate on these rather\n * than on `instanceof` of removed-historical subclasses.\n */\nexport type ErrorCode =\n // ConfigError — construction-time\n | \"decision_space_collision\"\n | \"decision_space_too_large\"\n | \"missing_provider_config\"\n | \"malformed_provider_config\"\n | \"unknown_provider_factory\"\n | \"missing_credential\"\n | \"incompatible_calibrator\"\n | \"invalid_classifier_name\"\n | \"invalid_thresholds\"\n | \"invalid_space\"\n | \"empty_providers\"\n // ProviderError — runtime\n | \"provider_network\"\n | \"provider_rate_limit\"\n | \"provider_timeout\"\n | \"provider_unauthorized\"\n | \"provider_server_error\"\n | \"provider_malformed_response\"\n | \"invalid_distribution\"\n // BudgetExhaustedError — runtime\n | \"per_call_timeout\"\n | \"chain_timeout\"\n | \"max_calls\";\n\nexport class DomovoiError extends Error {\n readonly code: string;\n\n constructor(message: string, options?: { code?: string; cause?: unknown }) {\n super(message, options?.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = \"DomovoiError\";\n this.code = options?.code ?? \"unspecified\";\n }\n}\n\nexport class ProviderError extends DomovoiError {\n constructor(message: string, options?: { code?: string; cause?: unknown }) {\n super(message, options);\n this.name = \"ProviderError\";\n }\n}\n\nexport class ConfigError extends DomovoiError {\n constructor(message: string, options?: { code?: string; cause?: unknown }) {\n super(message, options);\n this.name = \"ConfigError\";\n }\n}\n\nexport class BudgetExhaustedError extends DomovoiError {\n readonly attemptedProviders: readonly string[];\n readonly elapsedMs: number;\n readonly scope: \"per_call_timeout\" | \"chain_timeout\" | \"max_calls\";\n\n constructor(\n message: string,\n options: {\n scope: \"per_call_timeout\" | \"chain_timeout\" | \"max_calls\";\n attemptedProviders: readonly string[];\n elapsedMs: number;\n cause?: unknown;\n },\n ) {\n super(message, { code: options.scope, cause: options.cause });\n this.name = \"BudgetExhaustedError\";\n this.scope = options.scope;\n this.attemptedProviders = options.attemptedProviders;\n this.elapsedMs = options.elapsedMs;\n }\n}\n\n/**\n * Wraps any non-DomovoiError thrown value in `ProviderError({ cause })`.\n * `DomovoiError` subtypes — including `ProviderError` subclasses defined by\n * external Provider implementations — pass through unchanged.\n */\nexport function canonicalizeProviderThrow(thrown: unknown): DomovoiError {\n if (thrown instanceof DomovoiError) return thrown;\n if (thrown instanceof Error) {\n return new ProviderError(thrown.message || \"Provider call failed\", {\n code: \"provider_network\",\n cause: thrown,\n });\n }\n return new ProviderError(String(thrown), {\n code: \"provider_network\",\n cause: thrown,\n });\n}\n\n/**\n * Convert an Error to the JSON-safe shape stored in\n * `Verdict.meta.providerErrors`. Cause chains are preserved recursively.\n */\nexport function serializeError(err: unknown): {\n readonly name: string;\n readonly message: string;\n readonly code?: string;\n readonly cause?: ReturnType<typeof serializeError>;\n readonly stack?: string;\n} {\n if (!(err instanceof Error)) {\n return { name: \"Error\", message: String(err) };\n }\n const code = err instanceof DomovoiError ? err.code : undefined;\n const cause = err.cause !== undefined ? serializeError(err.cause) : undefined;\n return {\n name: err.name,\n message: err.message,\n ...(code !== undefined ? { code } : {}),\n ...(cause !== undefined ? { cause } : {}),\n ...(err.stack !== undefined ? { stack: err.stack } : {}),\n };\n}\n","/**\n * Built-in calibrator factories.\n *\n * - `identity` — pass-through; the default.\n * - `temperatureScaling(T)` — `p^(1/T) / Σ p_j^(1/T)`; equivalent to\n * scaling logits before softmax.\n * - `plattScaling({ a, b })` — sigmoid scaling; binary spaces only.\n *\n * `Calibrator.apply` must be pure and stateless. The engine runs it\n * per-caller after the Distribution is loaded from the cache, so impurity\n * would leak across callers sharing a cache row.\n */\n\nimport { ConfigError } from \"../errors.js\";\nimport type { Distribution } from \"../types.js\";\n\nexport interface Calibrator {\n /** Identifier; `\"identity\"` is reserved and recognized by the engine. */\n readonly kind: string;\n apply<T extends string>(d: Distribution<T>): Distribution<T>;\n}\n\nexport const identity: Calibrator = {\n kind: \"identity\",\n apply<T extends string>(d: Distribution<T>): Distribution<T> {\n return d;\n },\n};\n\n/**\n * - `T = 1` → identity.\n * - `T > 1` → softens (less peaked).\n * - `T < 1` → sharpens (more peaked).\n *\n * Operates on probabilities (not logits) and leaves `coverage` unchanged.\n * Throws `ConfigError` on `T <= 0`.\n *\n * @example\n * calibrator: temperatureScaling(0.85) // user-fit on held-out eval set\n */\nexport function temperatureScaling(T: number): Calibrator {\n if (Number.isNaN(T) || T <= 0) {\n throw new ConfigError(`temperatureScaling(T): T must be > 0; got ${T}.`, {\n code: \"incompatible_calibrator\",\n });\n }\n const inverseT = 1 / T;\n return {\n kind: \"temperature\",\n apply<U extends string>(d: Distribution<U>): Distribution<U> {\n const scaledEntries = (Object.entries(d.probs) as [string, number][]).map(\n ([label, prob]) => [label, prob === 0 ? 0 : prob ** inverseT] as const,\n );\n const partitionFn = scaledEntries.reduce(\n (running, [, scaledProb]) => running + scaledProb,\n 0,\n );\n // Degenerate input (every prob is 0) — return as-is rather than divide by 0.\n if (partitionFn === 0) return d;\n const normalizedProbs = Object.fromEntries(\n scaledEntries.map(([label, scaledProb]) => [label, scaledProb / partitionFn] as const),\n );\n return {\n probs: normalizedProbs as Distribution<U>[\"probs\"],\n coverage: d.coverage,\n };\n },\n };\n}\n\n/**\n * Platt scaling: `sigmoid(a*z + b)` where `z = logit(p_pos)`.\n *\n * Binary-only — the second label in the distribution (in user-given order)\n * is treated as the positive class. Construction-time space-size validation\n * happens in the classifier; `apply()` re-checks at runtime as a defensive\n * guard against direct misuse.\n */\nexport function plattScaling(params: { a: number; b: number }): Calibrator {\n if (\n Number.isNaN(params.a) ||\n Number.isNaN(params.b) ||\n !Number.isFinite(params.a) ||\n !Number.isFinite(params.b)\n ) {\n throw new ConfigError(\n `plattScaling({ a, b }): a and b must be finite numbers; got a=${params.a}, b=${params.b}.`,\n { code: \"incompatible_calibrator\" },\n );\n }\n return {\n kind: \"platt\",\n apply<U extends string>(d: Distribution<U>): Distribution<U> {\n const labels = Object.keys(d.probs);\n if (labels.length !== 2) {\n throw new ConfigError(\n `plattScaling is binary-only; got distribution with ${labels.length} labels.`,\n { code: \"incompatible_calibrator\" },\n );\n }\n const [neg, pos] = labels as [string, string];\n const probs = d.probs as Record<string, number>;\n const pPos = probs[pos] ?? 0;\n // Clamp away from {0, 1} so `Math.log(p / (1 - p))` is finite.\n const eps = 1e-9;\n const clamped = Math.min(1 - eps, Math.max(eps, pPos));\n const logit = Math.log(clamped / (1 - clamped));\n const calibratedPos = sigmoid(params.a * logit + params.b);\n return {\n probs: {\n [neg]: 1 - calibratedPos,\n [pos]: calibratedPos,\n } as Distribution<U>[\"probs\"],\n coverage: d.coverage,\n };\n },\n };\n}\n\nfunction sigmoid(x: number): number {\n return 1 / (1 + Math.exp(-x));\n}\n\nexport function isIdentityCalibrator(c: Calibrator): boolean {\n return c.kind === \"identity\";\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/errors.ts","../../src/calibration/index.ts"],"names":[],"mappings":";AAgDO,IAAM,YAAA,GAAN,cAA2B,KAAA,CAAM;AAAA,EAC7B,IAAA;AAAA,EAET,WAAA,CAAY,SAAiB,OAAA,EAA8C;AACzE,IAAA,KAAA,CAAM,OAAA,EAAS,SAAS,KAAA,KAAU,MAAA,GAAY,EAAE,KAAA,EAAO,OAAA,CAAQ,KAAA,EAAM,GAAI,MAAS,CAAA;AAClF,IAAA,IAAA,CAAK,IAAA,GAAO,cAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,SAAS,IAAA,IAAQ,aAAA;AAAA,EAC/B;AACF,CAAA;AASO,IAAM,WAAA,GAAN,cAA0B,YAAA,CAAa;AAAA,EAC5C,WAAA,CAAY,SAAiB,OAAA,EAA8C;AACzE,IAAA,KAAA,CAAM,SAAS,OAAO,CAAA;AACtB,IAAA,IAAA,CAAK,IAAA,GAAO,aAAA;AAAA,EACd;AACF,CAAA;;;AChDO,IAAM,QAAA,GAAuB;AAAA,EAClC,IAAA,EAAM,UAAA;AAAA,EACN,MAAwB,CAAA,EAAqC;AAC3D,IAAA,OAAO,CAAA;AAAA,EACT;AACF;AAaO,SAAS,mBAAmB,CAAA,EAAuB;AACxD,EAAA,IAAI,MAAA,CAAO,KAAA,CAAM,CAAC,CAAA,IAAK,KAAK,CAAA,EAAG;AAC7B,IAAA,MAAM,IAAI,WAAA,CAAY,CAAA,0CAAA,EAA6C,CAAC,CAAA,CAAA,CAAA,EAAK;AAAA,MACvE,IAAA,EAAM;AAAA,KACP,CAAA;AAAA,EACH;AACA,EAAA,MAAM,WAAW,CAAA,GAAI,CAAA;AACrB,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,aAAA;AAAA,IACN,MAAwB,CAAA,EAAqC;AAC3D,MAAA,MAAM,aAAA,GAAiB,MAAA,CAAO,OAAA,CAAQ,CAAA,CAAE,KAAK,CAAA,CAAyB,GAAA;AAAA,QACpE,CAAC,CAAC,KAAA,EAAO,IAAI,CAAA,KAAM,CAAC,KAAA,EAAO,IAAA,KAAS,CAAA,GAAI,CAAA,GAAI,IAAA,IAAQ,QAAQ;AAAA,OAC9D;AACA,MAAA,MAAM,cAAc,aAAA,CAAc,MAAA;AAAA,QAChC,CAAC,OAAA,EAAS,GAAG,UAAU,MAAM,OAAA,GAAU,UAAA;AAAA,QACvC;AAAA,OACF;AAEA,MAAA,IAAI,WAAA,KAAgB,GAAG,OAAO,CAAA;AAC9B,MAAA,MAAM,kBAAkB,MAAA,CAAO,WAAA;AAAA,QAC7B,aAAA,CAAc,GAAA,CAAI,CAAC,CAAC,KAAA,EAAO,UAAU,CAAA,KAAM,CAAC,KAAA,EAAO,UAAA,GAAa,WAAW,CAAU;AAAA,OACvF;AACA,MAAA,OAAO;AAAA,QACL,KAAA,EAAO,eAAA;AAAA,QACP,UAAU,CAAA,CAAE;AAAA,OACd;AAAA,IACF;AAAA,GACF;AACF;AAUO,SAAS,aAAa,MAAA,EAA8C;AACzE,EAAA,IACE,MAAA,CAAO,MAAM,MAAA,CAAO,CAAC,KACrB,MAAA,CAAO,KAAA,CAAM,OAAO,CAAC,CAAA,IACrB,CAAC,MAAA,CAAO,QAAA,CAAS,OAAO,CAAC,CAAA,IACzB,CAAC,MAAA,CAAO,QAAA,CAAS,MAAA,CAAO,CAAC,CAAA,EACzB;AACA,IAAA,MAAM,IAAI,WAAA;AAAA,MACR,CAAA,8DAAA,EAAiE,MAAA,CAAO,CAAC,CAAA,IAAA,EAAO,OAAO,CAAC,CAAA,CAAA,CAAA;AAAA,MACxF,EAAE,MAAM,yBAAA;AAA0B,KACpC;AAAA,EACF;AACA,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,OAAA;AAAA,IACN,MAAwB,CAAA,EAAqC;AAC3D,MAAA,MAAM,MAAA,GAAS,MAAA,CAAO,IAAA,CAAK,CAAA,CAAE,KAAK,CAAA;AAClC,MAAA,IAAI,MAAA,CAAO,WAAW,CAAA,EAAG;AACvB,QAAA,MAAM,IAAI,WAAA;AAAA,UACR,CAAA,mDAAA,EAAsD,OAAO,MAAM,CAAA,QAAA,CAAA;AAAA,UACnE,EAAE,MAAM,yBAAA;AAA0B,SACpC;AAAA,MACF;AACA,MAAA,MAAM,CAAC,GAAA,EAAK,GAAG,CAAA,GAAI,MAAA;AACnB,MAAA,MAAM,QAAQ,CAAA,CAAE,KAAA;AAChB,MAAA,MAAM,IAAA,GAAO,KAAA,CAAM,GAAG,CAAA,IAAK,CAAA;AAE3B,MAAA,MAAM,GAAA,GAAM,IAAA;AACZ,MAAA,MAAM,OAAA,GAAU,KAAK,GAAA,CAAI,CAAA,GAAI,KAAK,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,IAAI,CAAC,CAAA;AACrD,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,OAAA,IAAW,IAAI,OAAA,CAAQ,CAAA;AAC9C,MAAA,MAAM,gBAAgB,OAAA,CAAQ,MAAA,CAAO,CAAA,GAAI,KAAA,GAAQ,OAAO,CAAC,CAAA;AACzD,MAAA,OAAO;AAAA,QACL,KAAA,EAAO;AAAA,UACL,CAAC,GAAG,GAAG,CAAA,GAAI,aAAA;AAAA,UACX,CAAC,GAAG,GAAG;AAAA,SACT;AAAA,QACA,UAAU,CAAA,CAAE;AAAA,OACd;AAAA,IACF;AAAA,GACF;AACF;AAEA,SAAS,QAAQ,CAAA,EAAmB;AAClC,EAAA,OAAO,CAAA,IAAK,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,CAAC,CAAC,CAAA,CAAA;AAC7B;AAEO,SAAS,qBAAqB,CAAA,EAAwB;AAC3D,EAAA,OAAO,EAAE,IAAA,KAAS,UAAA;AACpB","file":"index.js","sourcesContent":["/**\n * Error taxonomy for domovoi.\n *\n * Four classes (`DomovoiError` base + `ProviderError`, `ConfigError`,\n * `BudgetExhaustedError`) with a stable `code` field for fine-grained\n * discrimination. All accept `{ cause }` for ES2022 chaining.\n *\n * The engine canonicalizes anything thrown from `Provider.sample` that isn't\n * already a `DomovoiError` into `ProviderError({ cause })`, so callers always\n * see a known error type. Under the default `onErrorPolicy: \"fallback\"`,\n * runtime errors become `Unknown` Verdict variants rather than throw;\n * `ConfigError` always throws.\n */\n\n/**\n * Stable error codes carried in `error.code`. Discriminate on these rather\n * than on `instanceof` of removed-historical subclasses.\n */\nexport type ErrorCode =\n // ConfigError — construction-time\n | \"decision_space_collision\"\n | \"decision_space_too_large\"\n | \"missing_provider_config\"\n | \"malformed_provider_config\"\n | \"unknown_provider_factory\"\n | \"missing_credential\"\n | \"incompatible_calibrator\"\n | \"invalid_classifier_name\"\n | \"invalid_thresholds\"\n | \"invalid_space\"\n | \"empty_providers\"\n // ProviderError — runtime\n | \"provider_network\"\n | \"provider_rate_limit\"\n | \"provider_timeout\"\n | \"provider_unauthorized\"\n | \"provider_server_error\"\n | \"provider_malformed_response\"\n | \"invalid_distribution\"\n // BudgetExhaustedError — runtime (operational: time / call-count)\n | \"per_call_timeout\"\n | \"chain_timeout\"\n | \"max_calls\"\n // BudgetExceededError — runtime (scope token budget)\n | \"tokens_exceeded\"\n // ConfigError — scope budget validation\n | \"invalid_scope_budget\";\n\nexport class DomovoiError extends Error {\n readonly code: string;\n\n constructor(message: string, options?: { code?: string; cause?: unknown }) {\n super(message, options?.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = \"DomovoiError\";\n this.code = options?.code ?? \"unspecified\";\n }\n}\n\nexport class ProviderError extends DomovoiError {\n constructor(message: string, options?: { code?: string; cause?: unknown }) {\n super(message, options);\n this.name = \"ProviderError\";\n }\n}\n\nexport class ConfigError extends DomovoiError {\n constructor(message: string, options?: { code?: string; cause?: unknown }) {\n super(message, options);\n this.name = \"ConfigError\";\n }\n}\n\nexport class BudgetExhaustedError extends DomovoiError {\n readonly attemptedProviders: readonly string[];\n readonly elapsedMs: number;\n readonly scope: \"per_call_timeout\" | \"chain_timeout\" | \"max_calls\";\n\n constructor(\n message: string,\n options: {\n scope: \"per_call_timeout\" | \"chain_timeout\" | \"max_calls\";\n attemptedProviders: readonly string[];\n elapsedMs: number;\n cause?: unknown;\n },\n ) {\n super(message, { code: options.scope, cause: options.cause });\n this.name = \"BudgetExhaustedError\";\n this.scope = options.scope;\n this.attemptedProviders = options.attemptedProviders;\n this.elapsedMs = options.elapsedMs;\n }\n}\n\n/**\n * Thrown when scope token budget is exceeded under `onExceeded: \"throw\"` mode.\n * Distinct from `BudgetExhaustedError` (operational time / call-count budgets):\n * this is the cost ceiling for a `domovoi.scope({ budget: { tokens } })` block.\n *\n * Default mode is `\"graceful\"` — classify returns\n * `Unknown { reason: { type: \"budget_exceeded\", spent, limit } }` instead.\n */\nexport class BudgetExceededError extends DomovoiError {\n readonly spent: number;\n readonly limit: number;\n\n constructor(options: { spent: number; limit: number; cause?: unknown }) {\n super(`Scope budget exceeded: ${options.spent} / ${options.limit} tokens`, {\n code: \"tokens_exceeded\",\n cause: options.cause,\n });\n this.name = \"BudgetExceededError\";\n this.spent = options.spent;\n this.limit = options.limit;\n }\n}\n\n/**\n * Wraps any non-DomovoiError thrown value in `ProviderError({ cause })`.\n * `DomovoiError` subtypes — including `ProviderError` subclasses defined by\n * external Provider implementations — pass through unchanged.\n */\nexport function canonicalizeProviderThrow(thrown: unknown): DomovoiError {\n if (thrown instanceof DomovoiError) return thrown;\n if (thrown instanceof Error) {\n return new ProviderError(thrown.message || \"Provider call failed\", {\n code: \"provider_network\",\n cause: thrown,\n });\n }\n return new ProviderError(String(thrown), {\n code: \"provider_network\",\n cause: thrown,\n });\n}\n\n/**\n * Convert an Error to the JSON-safe shape stored in\n * `Verdict.meta.providerErrors`. Cause chains are preserved recursively.\n */\nexport function serializeError(err: unknown): {\n readonly name: string;\n readonly message: string;\n readonly code?: string;\n readonly cause?: ReturnType<typeof serializeError>;\n readonly stack?: string;\n} {\n if (!(err instanceof Error)) {\n return { name: \"Error\", message: String(err) };\n }\n const code = err instanceof DomovoiError ? err.code : undefined;\n const cause = err.cause !== undefined ? serializeError(err.cause) : undefined;\n return {\n name: err.name,\n message: err.message,\n ...(code !== undefined ? { code } : {}),\n ...(cause !== undefined ? { cause } : {}),\n ...(err.stack !== undefined ? { stack: err.stack } : {}),\n };\n}\n","/**\n * Built-in calibrator factories.\n *\n * - `identity` — pass-through; the default.\n * - `temperatureScaling(T)` — `p^(1/T) / Σ p_j^(1/T)`; equivalent to\n * scaling logits before softmax.\n * - `plattScaling({ a, b })` — sigmoid scaling; binary spaces only.\n *\n * `Calibrator.apply` must be pure and stateless. The engine runs it\n * per-caller after the Distribution is loaded from the cache, so impurity\n * would leak across callers sharing a cache row.\n */\n\nimport { ConfigError } from \"../errors.js\";\nimport type { Distribution } from \"../types.js\";\n\nexport interface Calibrator {\n /** Identifier; `\"identity\"` is reserved and recognized by the engine. */\n readonly kind: string;\n apply<T extends string>(d: Distribution<T>): Distribution<T>;\n}\n\nexport const identity: Calibrator = {\n kind: \"identity\",\n apply<T extends string>(d: Distribution<T>): Distribution<T> {\n return d;\n },\n};\n\n/**\n * - `T = 1` → identity.\n * - `T > 1` → softens (less peaked).\n * - `T < 1` → sharpens (more peaked).\n *\n * Operates on probabilities (not logits) and leaves `coverage` unchanged.\n * Throws `ConfigError` on `T <= 0`.\n *\n * @example\n * calibrator: temperatureScaling(0.85) // user-fit on held-out eval set\n */\nexport function temperatureScaling(T: number): Calibrator {\n if (Number.isNaN(T) || T <= 0) {\n throw new ConfigError(`temperatureScaling(T): T must be > 0; got ${T}.`, {\n code: \"incompatible_calibrator\",\n });\n }\n const inverseT = 1 / T;\n return {\n kind: \"temperature\",\n apply<U extends string>(d: Distribution<U>): Distribution<U> {\n const scaledEntries = (Object.entries(d.probs) as [string, number][]).map(\n ([label, prob]) => [label, prob === 0 ? 0 : prob ** inverseT] as const,\n );\n const partitionFn = scaledEntries.reduce(\n (running, [, scaledProb]) => running + scaledProb,\n 0,\n );\n // Degenerate input (every prob is 0) — return as-is rather than divide by 0.\n if (partitionFn === 0) return d;\n const normalizedProbs = Object.fromEntries(\n scaledEntries.map(([label, scaledProb]) => [label, scaledProb / partitionFn] as const),\n );\n return {\n probs: normalizedProbs as Distribution<U>[\"probs\"],\n coverage: d.coverage,\n };\n },\n };\n}\n\n/**\n * Platt scaling: `sigmoid(a*z + b)` where `z = logit(p_pos)`.\n *\n * Binary-only — the second label in the distribution (in user-given order)\n * is treated as the positive class. Construction-time space-size validation\n * happens in the classifier; `apply()` re-checks at runtime as a defensive\n * guard against direct misuse.\n */\nexport function plattScaling(params: { a: number; b: number }): Calibrator {\n if (\n Number.isNaN(params.a) ||\n Number.isNaN(params.b) ||\n !Number.isFinite(params.a) ||\n !Number.isFinite(params.b)\n ) {\n throw new ConfigError(\n `plattScaling({ a, b }): a and b must be finite numbers; got a=${params.a}, b=${params.b}.`,\n { code: \"incompatible_calibrator\" },\n );\n }\n return {\n kind: \"platt\",\n apply<U extends string>(d: Distribution<U>): Distribution<U> {\n const labels = Object.keys(d.probs);\n if (labels.length !== 2) {\n throw new ConfigError(\n `plattScaling is binary-only; got distribution with ${labels.length} labels.`,\n { code: \"incompatible_calibrator\" },\n );\n }\n const [neg, pos] = labels as [string, string];\n const probs = d.probs as Record<string, number>;\n const pPos = probs[pos] ?? 0;\n // Clamp away from {0, 1} so `Math.log(p / (1 - p))` is finite.\n const eps = 1e-9;\n const clamped = Math.min(1 - eps, Math.max(eps, pPos));\n const logit = Math.log(clamped / (1 - clamped));\n const calibratedPos = sigmoid(params.a * logit + params.b);\n return {\n probs: {\n [neg]: 1 - calibratedPos,\n [pos]: calibratedPos,\n } as Distribution<U>[\"probs\"],\n coverage: d.coverage,\n };\n },\n };\n}\n\nfunction sigmoid(x: number): number {\n return 1 / (1 + Math.exp(-x));\n}\n\nexport function isIdentityCalibrator(c: Calibrator): boolean {\n return c.kind === \"identity\";\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ContextStorage<T>` — pluggable extension point for `domovoi.scope`'s
|
|
3
|
+
* ambient state. Default implementation wraps Node's
|
|
4
|
+
* `node:async_hooks.AsyncLocalStorage`, which works on Node 16+,
|
|
5
|
+
* Cloudflare Workers (with the `nodejs_compat` flag, since 2024), and
|
|
6
|
+
* Deno (native).
|
|
7
|
+
*
|
|
8
|
+
* Browser users or exotic runtimes call `configureContextStorage(custom)`
|
|
9
|
+
* at boot to swap the default. The interface mirrors `AsyncLocalStorage`
|
|
10
|
+
* minus methods domovoi doesn't need (`enterWith`, `disable`, etc.).
|
|
11
|
+
*
|
|
12
|
+
* `getContextStorage()` returns the *currently configured* storage —
|
|
13
|
+
* tests mutate it via `configureContextStorage` and reset between cases.
|
|
14
|
+
*
|
|
15
|
+
* Resolution semantics: `run` shadows nested scopes fully. There is no
|
|
16
|
+
* implicit merge — the engine's `mergeScopes()` (in `scope.ts`) builds the
|
|
17
|
+
* `ResolvedScope` value that `run` stores, so nested inheritance behavior
|
|
18
|
+
* lives in scope-merge logic, not in the context storage layer.
|
|
19
|
+
*/
|
|
20
|
+
import type { ResolvedScope } from "./scope.js";
|
|
21
|
+
export interface ContextStorage<T> {
|
|
22
|
+
/** Run `fn` with `value` as the active store. Restored on return/throw. */
|
|
23
|
+
run<R>(value: T, fn: () => R | Promise<R>): R | Promise<R>;
|
|
24
|
+
/** Read the active store, or `undefined` if no enclosing `run()`. */
|
|
25
|
+
getStore(): T | undefined;
|
|
26
|
+
}
|
|
27
|
+
export declare function getContextStorage(): ContextStorage<ResolvedScope>;
|
|
28
|
+
/**
|
|
29
|
+
* Replace the default `AsyncLocalStorage`-backed storage with a custom
|
|
30
|
+
* implementation. Call once at boot, before any `domovoi.scope(...)` runs.
|
|
31
|
+
*
|
|
32
|
+
* Use cases:
|
|
33
|
+
* - browser runtimes without `node:async_hooks`
|
|
34
|
+
* - test harnesses that need deterministic single-tenant state
|
|
35
|
+
* - custom propagation layers (cross-process, queue handlers with their
|
|
36
|
+
* own context system)
|
|
37
|
+
*/
|
|
38
|
+
export declare function configureContextStorage(custom: ContextStorage<ResolvedScope>): void;
|
|
39
|
+
/**
|
|
40
|
+
* Reset to the default `AsyncLocalStorage`-backed storage. Test-only
|
|
41
|
+
* affordance — production code should not need this.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resetContextStorage(): void;
|
|
44
|
+
//# sourceMappingURL=context-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-storage.d.ts","sourceRoot":"","sources":["../src/context-storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,2EAA2E;IAC3E,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3D,qEAAqE;IACrE,QAAQ,IAAI,CAAC,GAAG,SAAS,CAAC;CAC3B;AAID,wBAAgB,iBAAiB,IAAI,cAAc,CAAC,aAAa,CAAC,CAEjE;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,cAAc,CAAC,aAAa,CAAC,GAAG,IAAI,CAEnF;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C"}
|
package/dist/engine/decide.d.ts
CHANGED
|
@@ -11,6 +11,15 @@
|
|
|
11
11
|
* Errors during the chain become `Unknown` Verdicts under the default
|
|
12
12
|
* `onErrorPolicy: "fallback"`; under `"throw"` they propagate as
|
|
13
13
|
* `BudgetExhaustedError` / `AbortError` / `AggregateError`.
|
|
14
|
+
*
|
|
15
|
+
* v0.2 ambient context (via `domovoi.scope`):
|
|
16
|
+
* - reads `currentScope()` at entry
|
|
17
|
+
* - merges scope signal into per-call merged signal
|
|
18
|
+
* - pre-checks scope budget before each provider attempt; returns
|
|
19
|
+
* `Unknown { budget_exceeded }` (graceful) or throws
|
|
20
|
+
* `BudgetExceededError` (mode: "throw") on exhaustion
|
|
21
|
+
* - charges scope budget after each provider call (post-call estimate)
|
|
22
|
+
* - emits one OTel-shaped span per provider attempt via `scope.tracer`
|
|
14
23
|
*/
|
|
15
24
|
import type { Verdict } from "../types.js";
|
|
16
25
|
import { type DecideConfig } from "./config.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decide.d.ts","sourceRoot":"","sources":["../../src/engine/decide.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"decide.d.ts","sourceRoot":"","sources":["../../src/engine/decide.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAMH,OAAO,KAAK,EAAgD,OAAO,EAAE,MAAM,aAAa,CAAC;AAGzF,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,aAAa,CAAC;AAiBrB,wBAAsB,MAAM,CAAC,CAAC,SAAS,MAAM,EAC3C,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EACvB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAgFrB"}
|
|
@@ -8,7 +8,7 @@ import type { Distribution } from "../types.js";
|
|
|
8
8
|
import { type DecideConfig } from "./config.js";
|
|
9
9
|
import type { MetaBuilder } from "./meta.js";
|
|
10
10
|
export declare function computeProviderCacheKey<T extends string>(provider: Provider, formattedInput: string, config: DecideConfig<T>): string;
|
|
11
|
-
export declare function mergeSignals(userSignal: AbortSignal | undefined, timeoutSignal: AbortSignal): AbortSignal;
|
|
11
|
+
export declare function mergeSignals(userSignal: AbortSignal | undefined, timeoutSignal: AbortSignal, scopeSignal?: AbortSignal): AbortSignal;
|
|
12
12
|
export declare function loadDistribution<T extends string>(provider: Provider, formattedInput: string, config: DecideConfig<T>, meta: MetaBuilder, signal: AbortSignal, cacheKey: string): Promise<Distribution<T>>;
|
|
13
13
|
/**
|
|
14
14
|
* Drop the in-flight slot for a key after a failed sample so a concurrent
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"distribution.d.ts","sourceRoot":"","sources":["../../src/engine/distribution.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAA+B,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAS7C,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,MAAM,EACtD,QAAQ,EAAE,QAAQ,EAClB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GACtB,MAAM,CAWR;AAED,wBAAgB,YAAY,CAC1B,UAAU,EAAE,WAAW,GAAG,SAAS,EACnC,aAAa,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"distribution.d.ts","sourceRoot":"","sources":["../../src/engine/distribution.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAA+B,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAS7C,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,MAAM,EACtD,QAAQ,EAAE,QAAQ,EAClB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GACtB,MAAM,CAWR;AAED,wBAAgB,YAAY,CAC1B,UAAU,EAAE,WAAW,GAAG,SAAS,EACnC,aAAa,EAAE,WAAW,EAC1B,WAAW,CAAC,EAAE,WAAW,GACxB,WAAW,CAKb;AAED,wBAAsB,gBAAgB,CAAC,CAAC,SAAS,MAAM,EACrD,QAAQ,EAAE,QAAQ,EAClB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EACvB,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAa1B;AAmBD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAErD"}
|
|
@@ -3,10 +3,17 @@
|
|
|
3
3
|
* chain-budget exhaustion, pre/mid-loop cancellation, and chain-exhausted
|
|
4
4
|
* (every-provider-errored vs last-provider-uncertain).
|
|
5
5
|
*/
|
|
6
|
+
import type { BudgetMode } from "../budget-tracker.js";
|
|
6
7
|
import type { Distribution, Unknown, Verdict } from "../types.js";
|
|
7
8
|
import type { DecideConfig } from "./config.js";
|
|
8
9
|
import { type MetaBuilder } from "./meta.js";
|
|
9
10
|
export declare function checkChainBudget<T extends string>(meta: MetaBuilder, chainStartMs: number, chainTimeoutMs: number, config: DecideConfig<T>): Unknown<T> | undefined;
|
|
10
11
|
export declare function makeCancelledFromMeta<T extends string>(meta: MetaBuilder, reason: string): Unknown<T>;
|
|
12
|
+
/**
|
|
13
|
+
* Build the terminal verdict for scope-budget exhaustion. Under
|
|
14
|
+
* `BudgetMode.graceful` returns `Unknown { budget_exceeded }`; under
|
|
15
|
+
* `"throw"` mode throws `BudgetExceededError` for the caller to handle.
|
|
16
|
+
*/
|
|
17
|
+
export declare function makeBudgetExceededVerdict<T extends string>(meta: MetaBuilder, spent: number, limit: number, mode: BudgetMode): Unknown<T>;
|
|
11
18
|
export declare function finalizeChainExhausted<T extends string>(meta: MetaBuilder, lastCalibrated: Distribution<T> | undefined, attempts: number, config: DecideConfig<T>): Verdict<T>;
|
|
12
19
|
//# sourceMappingURL=finalize.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"finalize.d.ts","sourceRoot":"","sources":["../../src/engine/finalize.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"finalize.d.ts","sourceRoot":"","sources":["../../src/engine/finalize.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAElE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAElE,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,EAC/C,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GACtB,OAAO,CAAC,CAAC,CAAC,GAAG,SAAS,CAUxB;AAED,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,MAAM,EACpD,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,CAAC,CAAC,CAMZ;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,CAAC,SAAS,MAAM,EACxD,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,CAAC,CAAC,CASZ;AAED,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,MAAM,EACrD,IAAI,EAAE,WAAW,EACjB,cAAc,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS,EAC3C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GACtB,OAAO,CAAC,CAAC,CAAC,CAqCZ"}
|
package/dist/errors.d.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* Stable error codes carried in `error.code`. Discriminate on these rather
|
|
16
16
|
* than on `instanceof` of removed-historical subclasses.
|
|
17
17
|
*/
|
|
18
|
-
export type ErrorCode = "decision_space_collision" | "decision_space_too_large" | "missing_provider_config" | "malformed_provider_config" | "unknown_provider_factory" | "missing_credential" | "incompatible_calibrator" | "invalid_classifier_name" | "invalid_thresholds" | "invalid_space" | "empty_providers" | "provider_network" | "provider_rate_limit" | "provider_timeout" | "provider_unauthorized" | "provider_server_error" | "provider_malformed_response" | "invalid_distribution" | "per_call_timeout" | "chain_timeout" | "max_calls";
|
|
18
|
+
export type ErrorCode = "decision_space_collision" | "decision_space_too_large" | "missing_provider_config" | "malformed_provider_config" | "unknown_provider_factory" | "missing_credential" | "incompatible_calibrator" | "invalid_classifier_name" | "invalid_thresholds" | "invalid_space" | "empty_providers" | "provider_network" | "provider_rate_limit" | "provider_timeout" | "provider_unauthorized" | "provider_server_error" | "provider_malformed_response" | "invalid_distribution" | "per_call_timeout" | "chain_timeout" | "max_calls" | "tokens_exceeded" | "invalid_scope_budget";
|
|
19
19
|
export declare class DomovoiError extends Error {
|
|
20
20
|
readonly code: string;
|
|
21
21
|
constructor(message: string, options?: {
|
|
@@ -46,6 +46,23 @@ export declare class BudgetExhaustedError extends DomovoiError {
|
|
|
46
46
|
cause?: unknown;
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Thrown when scope token budget is exceeded under `onExceeded: "throw"` mode.
|
|
51
|
+
* Distinct from `BudgetExhaustedError` (operational time / call-count budgets):
|
|
52
|
+
* this is the cost ceiling for a `domovoi.scope({ budget: { tokens } })` block.
|
|
53
|
+
*
|
|
54
|
+
* Default mode is `"graceful"` — classify returns
|
|
55
|
+
* `Unknown { reason: { type: "budget_exceeded", spent, limit } }` instead.
|
|
56
|
+
*/
|
|
57
|
+
export declare class BudgetExceededError extends DomovoiError {
|
|
58
|
+
readonly spent: number;
|
|
59
|
+
readonly limit: number;
|
|
60
|
+
constructor(options: {
|
|
61
|
+
spent: number;
|
|
62
|
+
limit: number;
|
|
63
|
+
cause?: unknown;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
49
66
|
/**
|
|
50
67
|
* Wraps any non-DomovoiError thrown value in `ProviderError({ cause })`.
|
|
51
68
|
* `DomovoiError` subtypes — including `ProviderError` subclasses defined by
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;GAGG;AACH,MAAM,MAAM,SAAS,GAEjB,0BAA0B,GAC1B,0BAA0B,GAC1B,yBAAyB,GACzB,2BAA2B,GAC3B,0BAA0B,GAC1B,oBAAoB,GACpB,yBAAyB,GACzB,yBAAyB,GACzB,oBAAoB,GACpB,eAAe,GACf,iBAAiB,GAEjB,kBAAkB,GAClB,qBAAqB,GACrB,kBAAkB,GAClB,uBAAuB,GACvB,uBAAuB,GACvB,6BAA6B,GAC7B,sBAAsB,GAEtB,kBAAkB,GAClB,eAAe,GACf,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;GAGG;AACH,MAAM,MAAM,SAAS,GAEjB,0BAA0B,GAC1B,0BAA0B,GAC1B,yBAAyB,GACzB,2BAA2B,GAC3B,0BAA0B,GAC1B,oBAAoB,GACpB,yBAAyB,GACzB,yBAAyB,GACzB,oBAAoB,GACpB,eAAe,GACf,iBAAiB,GAEjB,kBAAkB,GAClB,qBAAqB,GACrB,kBAAkB,GAClB,uBAAuB,GACvB,uBAAuB,GACvB,6BAA6B,GAC7B,sBAAsB,GAEtB,kBAAkB,GAClB,eAAe,GACf,WAAW,GAEX,iBAAiB,GAEjB,sBAAsB,CAAC;AAE3B,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAK1E;AAED,qBAAa,aAAc,SAAQ,YAAY;gBACjC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAI1E;AAED,qBAAa,WAAY,SAAQ,YAAY;gBAC/B,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAI1E;AAED,qBAAa,oBAAqB,SAAQ,YAAY;IACpD,QAAQ,CAAC,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,kBAAkB,GAAG,eAAe,GAAG,WAAW,CAAC;gBAGjE,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;QACP,KAAK,EAAE,kBAAkB,GAAG,eAAe,GAAG,WAAW,CAAC;QAC1D,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;QACtC,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB;CAQJ;AAED;;;;;;;GAOG;AACH,qBAAa,mBAAoB,SAAQ,YAAY;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBAEX,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CASvE;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,OAAO,GAAG,YAAY,CAYvE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;IACnD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB,CAaA"}
|