@full-self-browsing/lattice 0.0.0-bootstrap.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/dist/index.js ADDED
@@ -0,0 +1,3200 @@
1
+ import { C as createHookPipeline, S as BAND, T as toArtifactRef, _ as PAYLOAD_TYPE, a as formatToolsForProvider, b as decodeEnvelope, c as importMcpTools, d as validateOutputMap, f as createNoopSurvivabilityAdapter, g as createReceipt, h as createCheckpointHook, i as createNoopAgentHost, l as runTool, m as STEP_TRANSITION_EVENT_NAME, o as toolSchemaToJsonSchema, p as DEFAULT_CHECKPOINT_BAND, r as AgentDeniedError, s as defineTool, t as runAgent, u as toolArtifactRef, v as base64Encode, w as artifact, x as canonicalizeReceiptBody, y as buildPae } from "./runtime-CfZ-sGGk.js";
2
+ import canonicalize from "canonicalize";
3
+ import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ //#region src/contract/contract.ts
7
+ /**
8
+ * Factory for `CapabilityContract` values.
9
+ *
10
+ * Mirrors the `output()` and adapter factory style — exact-optional safe
11
+ * (does not emit `field: undefined` properties under `exactOptionalPropertyTypes`).
12
+ * Returns a frozen value with frozen nested objects so downstream code can
13
+ * rely on structural immutability when canonicalizing in Phase 9.
14
+ */
15
+ function contract(input = {}) {
16
+ return Object.freeze({
17
+ kind: "capability-contract",
18
+ ...input.budget !== void 0 ? { budget: Object.freeze({ ...input.budget }) } : {},
19
+ ...input.invariants !== void 0 ? { invariants: Object.freeze(input.invariants.map((inv) => Object.freeze({ ...inv }))) } : {},
20
+ ...input.qualityFloor !== void 0 ? { qualityFloor: Object.freeze({ ...input.qualityFloor }) } : {},
21
+ ...input.requiredModalities !== void 0 ? { requiredModalities: Object.freeze([...input.requiredModalities]) } : {},
22
+ ...input.requiredPrivacy !== void 0 ? { requiredPrivacy: input.requiredPrivacy } : {}
23
+ });
24
+ }
25
+ //#endregion
26
+ //#region src/contract/invariants.ts
27
+ let counter = 0;
28
+ function nextId(kind, options) {
29
+ counter += 1;
30
+ return options?.id ?? `${kind}-${counter}`;
31
+ }
32
+ /**
33
+ * Fluent builder for tripwire invariants.
34
+ *
35
+ * Each helper returns a frozen `InvariantDeclaration` with an auto-generated
36
+ * id of the form `${kind}-${counter}`. Callers may override the id via the
37
+ * second-positional `options.id` arg.
38
+ *
39
+ * The counter is monotonic across kinds — calling `inv.mustCite("a")` then
40
+ * `inv.fieldFromTable("x", ["y"])` yields ids `must-cite-1` then
41
+ * `field-from-table-2`. This keeps ids globally unique within a process.
42
+ *
43
+ * Note on `inv.matches`: the caller supplies the StandardSchema validator,
44
+ * and the tripwire evaluator trusts whatever `~standard.validate` returns.
45
+ * This is by design — `matches` is the caller-driven escape hatch (see
46
+ * T-08-05 in the 08-01-PLAN threat register).
47
+ */
48
+ const inv = {
49
+ mustCite(artifactName, options) {
50
+ return Object.freeze({
51
+ id: nextId("must-cite", options),
52
+ kind: "must-cite",
53
+ artifactName
54
+ });
55
+ },
56
+ fieldFromTable(path, allowedValues, options) {
57
+ return Object.freeze({
58
+ id: nextId("field-from-table", options),
59
+ kind: "field-from-table",
60
+ path,
61
+ allowedValues: Object.freeze([...allowedValues])
62
+ });
63
+ },
64
+ noPII(path, options) {
65
+ return Object.freeze({
66
+ id: nextId("no-pii", options),
67
+ kind: "no-pii",
68
+ path
69
+ });
70
+ },
71
+ matches(path, schema, options) {
72
+ return Object.freeze({
73
+ id: nextId("matches", options),
74
+ kind: "matches",
75
+ path,
76
+ schema
77
+ });
78
+ },
79
+ __resetCounterForTests() {
80
+ counter = 0;
81
+ }
82
+ };
83
+ //#endregion
84
+ //#region src/contract/pii-detectors.ts
85
+ /**
86
+ * Luhn check digit validator.
87
+ *
88
+ * Strips non-digit characters from `digits`, requires the resulting length
89
+ * to be 13-19 (ISO/IEC 7812 PAN range), then walks right-to-left doubling
90
+ * every second digit and summing. Returns true when the sum is a multiple
91
+ * of 10.
92
+ */
93
+ function luhn(digits) {
94
+ const cleaned = digits.replace(/\D/g, "");
95
+ if (cleaned.length < 13 || cleaned.length > 19) return false;
96
+ let sum = 0;
97
+ let shouldDouble = false;
98
+ for (let i = cleaned.length - 1; i >= 0; i -= 1) {
99
+ const code = cleaned.charCodeAt(i);
100
+ if (code < 48 || code > 57) return false;
101
+ let digit = code - 48;
102
+ if (shouldDouble) {
103
+ digit *= 2;
104
+ if (digit > 9) digit -= 9;
105
+ }
106
+ sum += digit;
107
+ shouldDouble = !shouldDouble;
108
+ }
109
+ return sum % 10 === 0;
110
+ }
111
+ function execFirst(regex, input) {
112
+ const match = regex.exec(input);
113
+ return match ? match[0] : void 0;
114
+ }
115
+ /**
116
+ * Default PII detectors used by `evaluateTripwires` for `no-pii` invariants.
117
+ *
118
+ * Order is deterministic: email, us-ssn, credit-card, us-phone. Callers who
119
+ * need a different set can pass their own list to `evaluateTripwires`.
120
+ */
121
+ const defaultPiiDetectors = Object.freeze([
122
+ {
123
+ name: "email",
124
+ detect(input) {
125
+ const substring = execFirst(/[\w.+-]+@[\w-]+\.[\w.-]+/, input);
126
+ return substring !== void 0 ? {
127
+ matched: true,
128
+ substring
129
+ } : { matched: false };
130
+ }
131
+ },
132
+ {
133
+ name: "us-ssn",
134
+ detect(input) {
135
+ const substring = execFirst(/\b\d{3}-\d{2}-\d{4}\b/, input);
136
+ return substring !== void 0 ? {
137
+ matched: true,
138
+ substring
139
+ } : { matched: false };
140
+ }
141
+ },
142
+ {
143
+ name: "credit-card",
144
+ detect(input) {
145
+ const candidate = execFirst(/\b(?:\d[ -]?){13,19}\b/, input);
146
+ if (candidate === void 0) return { matched: false };
147
+ const trimmed = candidate.replace(/[ -]+$/, "");
148
+ if (!luhn(trimmed)) return { matched: false };
149
+ return {
150
+ matched: true,
151
+ substring: trimmed
152
+ };
153
+ }
154
+ },
155
+ {
156
+ name: "us-phone",
157
+ detect(input) {
158
+ const substring = execFirst(/\b\d{3}-\d{3}-\d{4}\b|\(\d{3}\)\s?\d{3}-\d{4}/, input);
159
+ return substring !== void 0 ? {
160
+ matched: true,
161
+ substring
162
+ } : { matched: false };
163
+ }
164
+ }
165
+ ]);
166
+ //#endregion
167
+ //#region src/routing/catalog.ts
168
+ const DEFAULT_CATALOG_VERSION = "lattice:catalog:v1";
169
+ function createCapabilityCatalog(providers) {
170
+ return {
171
+ version: DEFAULT_CATALOG_VERSION,
172
+ models: providers.flatMap((provider) => {
173
+ if (provider.kind === "provider-adapter" && provider.capabilities !== void 0) return provider.capabilities;
174
+ return [defaultCapabilityForProvider(provider.id)];
175
+ })
176
+ };
177
+ }
178
+ function defaultCapabilityForProvider(providerId) {
179
+ return {
180
+ providerId,
181
+ modelId: `${providerId}:default`,
182
+ inputModalities: [
183
+ "text",
184
+ "json",
185
+ "image",
186
+ "audio",
187
+ "document",
188
+ "file",
189
+ "url",
190
+ "tool"
191
+ ],
192
+ outputModalities: ["text", "json"],
193
+ fileTransport: [
194
+ "inline",
195
+ "json",
196
+ "url",
197
+ "base64",
198
+ "extracted-text",
199
+ "transcript"
200
+ ],
201
+ contextWindow: 16e3,
202
+ structuredOutput: true,
203
+ toolUse: false,
204
+ streaming: false,
205
+ pricing: {
206
+ inputCostPer1M: 0,
207
+ outputCostPer1M: 0,
208
+ inputPer1kTokens: 0,
209
+ outputPer1kTokens: 0
210
+ },
211
+ latency: "interactive",
212
+ dataPolicy: {
213
+ privacy: ["standard", "sensitive"],
214
+ uploadRetention: "none",
215
+ supportsNoLogging: true,
216
+ supportsNoTraining: true
217
+ },
218
+ available: true
219
+ };
220
+ }
221
+ /**
222
+ * Resolve the effective per-1k token pricing for a capability.
223
+ *
224
+ * Prefers the explicit `inputPer1kTokens` / `outputPer1kTokens` fields and
225
+ * falls back to dividing the legacy per-1M fields by 1000 when only those
226
+ * are present. Returns `undefined` per side when neither shape supplies a
227
+ * value, so callers can distinguish "free / zero" (`0`) from "unknown"
228
+ * (`undefined`) — Phase 7 cost normalization treats unknown pricing as
229
+ * `usage.costUsd === null`, not `0`.
230
+ */
231
+ function effectivePer1kPricing(pricing) {
232
+ if (pricing === void 0) return {
233
+ inputPer1kTokens: void 0,
234
+ outputPer1kTokens: void 0
235
+ };
236
+ return {
237
+ inputPer1kTokens: pricing.inputPer1kTokens ?? (pricing.inputCostPer1M !== void 0 ? pricing.inputCostPer1M / 1e3 : void 0),
238
+ outputPer1kTokens: pricing.outputPer1kTokens ?? (pricing.outputCostPer1M !== void 0 ? pricing.outputCostPer1M / 1e3 : void 0)
239
+ };
240
+ }
241
+ //#endregion
242
+ //#region src/contract/preflight.ts
243
+ /**
244
+ * Pure cost estimator. Returns `null` when pricing is unknown (so downstream
245
+ * gates can distinguish "free / zero" from "unmeasured" per the Phase 7
246
+ * cost-normalization decision). Uses static catalog metadata only — no probes,
247
+ * no external pricing APIs.
248
+ */
249
+ function estimateRouteCost(input) {
250
+ const { inputPer1kTokens, outputPer1kTokens } = effectivePer1kPricing(input.capability.pricing);
251
+ if (inputPer1kTokens === void 0 && outputPer1kTokens === void 0) return null;
252
+ return (inputPer1kTokens ?? 0) * input.estimatedInputTokens / 1e3 + (outputPer1kTokens ?? 0) * input.estimatedOutputTokens / 1e3;
253
+ }
254
+ /**
255
+ * Pure pre-flight evaluator. Phase 9 receipts will reuse this for deterministic
256
+ * verdict reconstruction.
257
+ *
258
+ * Token estimation: Phase 7 does NOT define a separate token estimator. Output-
259
+ * token projection is the canonical responsibility of the router's existing
260
+ * `estimateRoute()` helper (in `routing/router.ts`), which already produces an
261
+ * `estimatedOutputTokens` value. The router passes that same value into this
262
+ * evaluator via `EvaluateContractInput.estimatedOutputTokens`, so preflight
263
+ * and the router always agree on the projected output size. Phase 9 receipts
264
+ * will pin the router's estimate as the deterministic input — intentionally
265
+ * one source of truth.
266
+ *
267
+ * Reject taxonomy (Phase 7 emits three of four codes):
268
+ * - `contract-budget-exceeded` (CONTRACT-04 + COST-03)
269
+ * - `contract-modality-missing` (CONTRACT-06)
270
+ * - `contract-privacy-mismatch` (CONTRACT-06)
271
+ * - `contract-quality-floor` (reserved for Phase 12 `lattice eval`; NEVER emitted here)
272
+ */
273
+ function evaluateContractAgainstRoute(contract, input) {
274
+ if (contract === void 0) return {
275
+ ok: true,
276
+ reasons: []
277
+ };
278
+ const reasons = [];
279
+ if (contract.budget?.maxCostUsd !== void 0) {
280
+ const estimatedCost = estimateRouteCost({
281
+ capability: input.capability,
282
+ estimatedInputTokens: input.estimatedInputTokens,
283
+ estimatedOutputTokens: input.estimatedOutputTokens
284
+ });
285
+ if (estimatedCost === null) reasons.push({
286
+ code: "contract-budget-exceeded",
287
+ message: `${input.capability.modelId} pricing unknown; contract budget declared (maxCostUsd=${contract.budget.maxCostUsd}).`
288
+ });
289
+ else if (estimatedCost > contract.budget.maxCostUsd) reasons.push({
290
+ code: "contract-budget-exceeded",
291
+ message: `${input.capability.modelId} estimated ${estimatedCost.toFixed(6)} exceeds contract budget ${contract.budget.maxCostUsd}.`
292
+ });
293
+ }
294
+ if (contract.requiredModalities !== void 0) {
295
+ for (const modality of contract.requiredModalities) if (!input.capability.inputModalities.includes(modality) && !input.capability.outputModalities.includes(modality)) reasons.push({
296
+ code: "contract-modality-missing",
297
+ message: `${input.capability.modelId} does not support required modality ${modality}.`
298
+ });
299
+ }
300
+ if (contract.requiredPrivacy !== void 0) {
301
+ if (!input.capability.dataPolicy.privacy.includes(contract.requiredPrivacy)) reasons.push({
302
+ code: "contract-privacy-mismatch",
303
+ message: `${input.capability.modelId} does not satisfy contract privacy ${contract.requiredPrivacy}.`
304
+ });
305
+ }
306
+ return {
307
+ ok: reasons.length === 0,
308
+ reasons
309
+ };
310
+ }
311
+ //#endregion
312
+ //#region src/contract/tripwire.ts
313
+ /**
314
+ * Pure tripwire evaluator.
315
+ *
316
+ * No I/O, no Date.now, no random — same `(output, invariants)` always
317
+ * returns the same `TripwireResult`. Phase 9 receipts can reconstruct the
318
+ * verdict deterministically (T-08-04).
319
+ *
320
+ * Evaluates invariants in declaration order; the FIRST failing invariant
321
+ * aborts and returns its evidence. Subsequent invariants are not evaluated.
322
+ *
323
+ * @param output The provider output to inspect.
324
+ * @param invariants Invariants to evaluate, in declaration order.
325
+ * @param detectors PII detectors used for `no-pii` invariants. Defaults
326
+ * to `defaultPiiDetectors`. Callers can pass a custom
327
+ * list to override.
328
+ */
329
+ async function evaluateTripwires(output, invariants, detectors = defaultPiiDetectors) {
330
+ for (const declaration of invariants) {
331
+ const result = await evaluateOne(output, declaration, detectors);
332
+ if (!result.ok) return result;
333
+ }
334
+ return { ok: true };
335
+ }
336
+ async function evaluateOne(output, declaration, detectors) {
337
+ switch (declaration.kind) {
338
+ case "must-cite": return evaluateMustCite(output, declaration);
339
+ case "field-from-table": return evaluateFieldFromTable(output, declaration);
340
+ case "no-pii": return evaluateNoPii(output, declaration, detectors);
341
+ case "matches": return evaluateMatches(output, declaration);
342
+ default: throw new Error(`Unknown invariant kind: ${JSON.stringify(declaration)}`);
343
+ }
344
+ }
345
+ function evaluateMustCite(output, decl) {
346
+ const located = locateCitations(output);
347
+ const cites = located?.value ?? [];
348
+ const path = located?.path ?? "citations";
349
+ if (cites.some((entry) => {
350
+ if (typeof entry === "string") return entry === decl.artifactName;
351
+ if (typeof entry === "object" && entry !== null && "source" in entry) return entry.source === decl.artifactName;
352
+ return false;
353
+ })) return { ok: true };
354
+ return {
355
+ ok: false,
356
+ evidence: {
357
+ invariantId: decl.id,
358
+ kind: "must-cite",
359
+ path,
360
+ observed: cites,
361
+ message: `must-cite: no citation found for "${decl.artifactName}".`
362
+ }
363
+ };
364
+ }
365
+ /**
366
+ * Locate the citations payload in `output`. Searches top-level for a
367
+ * `citations` or `evidence` key holding an array. Per 08-CONTEXT.md:
368
+ * "Path defaults to evidence if the output has a citations field; the
369
+ * runtime locates the citations payload in the output."
370
+ *
371
+ * Returns `undefined` when neither field is an array.
372
+ */
373
+ function locateCitations(output) {
374
+ if (typeof output !== "object" || output === null) return void 0;
375
+ const record = output;
376
+ for (const key of ["citations", "evidence"]) {
377
+ const value = record[key];
378
+ if (Array.isArray(value)) return {
379
+ value,
380
+ path: key
381
+ };
382
+ }
383
+ }
384
+ function evaluateFieldFromTable(output, decl) {
385
+ const value = resolvePath(output, decl.path);
386
+ if (typeof value === "string" && decl.allowedValues.includes(value)) return { ok: true };
387
+ return {
388
+ ok: false,
389
+ evidence: {
390
+ invariantId: decl.id,
391
+ kind: "field-from-table",
392
+ path: decl.path,
393
+ observed: value,
394
+ message: `field-from-table: value at "${decl.path}" not in allowedValues.`
395
+ }
396
+ };
397
+ }
398
+ function evaluateNoPii(output, decl, detectors) {
399
+ const value = resolvePath(output, decl.path);
400
+ if (typeof value !== "string") return { ok: true };
401
+ for (const detector of detectors) {
402
+ const result = detector.detect(value);
403
+ if (result.matched) return {
404
+ ok: false,
405
+ evidence: {
406
+ invariantId: decl.id,
407
+ kind: "no-pii",
408
+ path: decl.path,
409
+ observed: {
410
+ detector: detector.name,
411
+ substring: result.substring
412
+ },
413
+ message: `no-pii: detector "${detector.name}" flagged content at "${decl.path}".`
414
+ }
415
+ };
416
+ }
417
+ return { ok: true };
418
+ }
419
+ async function evaluateMatches(output, decl) {
420
+ const value = resolvePath(output, decl.path);
421
+ const validateResult = decl.schema["~standard"].validate(value);
422
+ const validation = validateResult instanceof Promise ? await validateResult : validateResult;
423
+ if ("issues" in validation && validation.issues !== void 0) {
424
+ const firstIssue = validation.issues[0];
425
+ return {
426
+ ok: false,
427
+ evidence: {
428
+ invariantId: decl.id,
429
+ kind: "matches",
430
+ path: decl.path,
431
+ observed: value,
432
+ message: firstIssue?.message ?? `matches: schema validation failed at "${decl.path}".`
433
+ }
434
+ };
435
+ }
436
+ return { ok: true };
437
+ }
438
+ /**
439
+ * Resolve a dotted/bracketed path expression against a value.
440
+ *
441
+ * Supports three segment forms:
442
+ * - dotted key: `a.b.c`
443
+ * - bracket index: `a[0].b`
444
+ * - wildcard: `a[*].b` (materializes the array of resolutions)
445
+ *
446
+ * Returns `undefined` for missing paths (does not throw).
447
+ *
448
+ * NOTE (T-08-03): `[*]` materializes the array; deeply nested wildcard
449
+ * chains could allocate O(N^k). Accepted for v1.1 — provider responses
450
+ * are bounded by output token caps.
451
+ */
452
+ function resolvePath(value, path) {
453
+ if (path === "") return value;
454
+ return walk(value, tokenize(path), 0);
455
+ }
456
+ function tokenize(path) {
457
+ const tokens = [];
458
+ let i = 0;
459
+ let buffer = "";
460
+ const flushKey = () => {
461
+ if (buffer.length > 0) {
462
+ tokens.push({
463
+ type: "key",
464
+ name: buffer
465
+ });
466
+ buffer = "";
467
+ }
468
+ };
469
+ while (i < path.length) {
470
+ const ch = path[i];
471
+ if (ch === ".") {
472
+ flushKey();
473
+ i += 1;
474
+ continue;
475
+ }
476
+ if (ch === "[") {
477
+ flushKey();
478
+ const end = path.indexOf("]", i + 1);
479
+ if (end === -1) {
480
+ buffer = path.slice(i);
481
+ i = path.length;
482
+ continue;
483
+ }
484
+ const inner = path.slice(i + 1, end);
485
+ if (inner === "*") tokens.push({ type: "wildcard" });
486
+ else {
487
+ const idx = Number(inner);
488
+ if (Number.isInteger(idx) && idx >= 0) tokens.push({
489
+ type: "index",
490
+ index: idx
491
+ });
492
+ else tokens.push({
493
+ type: "key",
494
+ name: inner
495
+ });
496
+ }
497
+ i = end + 1;
498
+ continue;
499
+ }
500
+ buffer += ch;
501
+ i += 1;
502
+ }
503
+ flushKey();
504
+ return tokens;
505
+ }
506
+ function walk(value, tokens, cursor) {
507
+ if (cursor >= tokens.length) return value;
508
+ if (value === void 0 || value === null) return void 0;
509
+ const token = tokens[cursor];
510
+ if (token.type === "key") {
511
+ if (typeof value !== "object") return void 0;
512
+ const next = value[token.name];
513
+ return walk(next, tokens, cursor + 1);
514
+ }
515
+ if (token.type === "index") {
516
+ if (!Array.isArray(value)) return void 0;
517
+ return walk(value[token.index], tokens, cursor + 1);
518
+ }
519
+ if (!Array.isArray(value)) return void 0;
520
+ return value.map((entry) => walk(entry, tokens, cursor + 1));
521
+ }
522
+ //#endregion
523
+ //#region src/outputs/contracts.ts
524
+ const output = {
525
+ citations() {
526
+ return { kind: "citations" };
527
+ },
528
+ artifacts(options = {}) {
529
+ return {
530
+ kind: "artifacts",
531
+ ...options
532
+ };
533
+ }
534
+ };
535
+ //#endregion
536
+ //#region src/receipts/keyset.ts
537
+ /**
538
+ * In-memory KeySet factory.
539
+ *
540
+ * Verification flow (plan 09-03):
541
+ * - keySet.lookup(kid) returns undefined → VerifyError {kind: "key-not-found"}
542
+ * - entry.state === "revoked" → VerifyError {kind: "key-revoked"}
543
+ * - entry.state === "retired" → VerifyOk + keyState: "retired" (caller may warn)
544
+ * - entry.state === "active" → VerifyOk + keyState: "active"
545
+ *
546
+ * Duplicate kids: last write wins (deterministic — callers control entry order).
547
+ * Empty entries array is legal — every lookup returns undefined.
548
+ * Returned KeySet exposes only `lookup` — no enumeration.
549
+ *
550
+ * See 09-CONTEXT.md "Key Management (UNRETROFITTABLE)".
551
+ */
552
+ function createMemoryKeySet(entries) {
553
+ const byKid = /* @__PURE__ */ new Map();
554
+ for (const entry of entries) byKid.set(entry.kid, entry);
555
+ return { lookup(kid) {
556
+ return byKid.get(kid);
557
+ } };
558
+ }
559
+ //#endregion
560
+ //#region src/receipts/sign.ts
561
+ const ALG = "Ed25519";
562
+ /**
563
+ * Copy a Uint8Array into a fresh ArrayBuffer. WebCrypto's BufferSource type
564
+ * (under exactOptionalPropertyTypes + strict TS) rejects `Uint8Array<ArrayBufferLike>`
565
+ * because the underlying buffer could be a SharedArrayBuffer. Matches the
566
+ * pattern used in storage/fingerprint.ts.
567
+ */
568
+ function toArrayBuffer$1(bytes) {
569
+ const copy = new Uint8Array(bytes.byteLength);
570
+ copy.set(bytes);
571
+ return copy.buffer;
572
+ }
573
+ async function importEd25519PrivateKey(jwk) {
574
+ return crypto.subtle.importKey("jwk", jwk, ALG, true, ["sign"]);
575
+ }
576
+ async function importEd25519PublicKey(jwk) {
577
+ return crypto.subtle.importKey("jwk", jwk, ALG, true, ["verify"]);
578
+ }
579
+ async function generateEd25519KeyPairJwk() {
580
+ const pair = await crypto.subtle.generateKey(ALG, true, ["sign", "verify"]);
581
+ const [privateKeyJwk, publicKeyJwk] = await Promise.all([crypto.subtle.exportKey("jwk", pair.privateKey), crypto.subtle.exportKey("jwk", pair.publicKey)]);
582
+ return {
583
+ privateKeyJwk,
584
+ publicKeyJwk
585
+ };
586
+ }
587
+ async function verifyEd25519Signature(publicKeyJwk, message, signature) {
588
+ let key;
589
+ try {
590
+ key = await importEd25519PublicKey(publicKeyJwk);
591
+ } catch {
592
+ return false;
593
+ }
594
+ try {
595
+ return await crypto.subtle.verify(ALG, key, toArrayBuffer$1(signature), toArrayBuffer$1(message));
596
+ } catch {
597
+ return false;
598
+ }
599
+ }
600
+ function createInMemorySigner(privateKeyJwk, options) {
601
+ let cachedKey;
602
+ const ensureKey = async () => {
603
+ if (cachedKey === void 0) cachedKey = await importEd25519PrivateKey(privateKeyJwk);
604
+ return cachedKey;
605
+ };
606
+ return {
607
+ kid: options.kid,
608
+ publicKeyJwk: options.publicKeyJwk,
609
+ async sign(bytes) {
610
+ const key = await ensureKey();
611
+ const sig = await crypto.subtle.sign(ALG, key, toArrayBuffer$1(bytes));
612
+ return new Uint8Array(sig);
613
+ }
614
+ };
615
+ }
616
+ //#endregion
617
+ //#region src/receipts/verify.ts
618
+ function fail$1(kind, message) {
619
+ return {
620
+ ok: false,
621
+ error: {
622
+ kind,
623
+ message
624
+ }
625
+ };
626
+ }
627
+ function bytesEqual(a, b) {
628
+ if (a.byteLength !== b.byteLength) return false;
629
+ for (let i = 0; i < a.byteLength; i += 1) if (a[i] !== b[i]) return false;
630
+ return true;
631
+ }
632
+ /**
633
+ * Receipt body shape check. We trust the JSON parse — but we re-validate
634
+ * that the required fields exist with the right primitive types before
635
+ * canonicalizing again. Anything off -> version-mismatch (the body is
636
+ * structurally NOT a v1 receipt, even if it parses as JSON).
637
+ */
638
+ function asReceiptBody(value) {
639
+ if (typeof value !== "object" || value === null) return void 0;
640
+ const v = value;
641
+ if (v.version !== void 0 && v.version !== "lattice-receipt/v1" && v.version !== "lattice-receipt/v1.1") return;
642
+ if (typeof v.receiptId !== "string") return void 0;
643
+ if (typeof v.runId !== "string") return void 0;
644
+ if (typeof v.issuedAt !== "string") return void 0;
645
+ if (typeof v.kid !== "string") return void 0;
646
+ if (typeof v.model !== "object" || v.model === null) return void 0;
647
+ if (typeof v.route !== "object" || v.route === null) return void 0;
648
+ if (typeof v.usage !== "object" || v.usage === null) return void 0;
649
+ if (typeof v.contractVerdict !== "string") return void 0;
650
+ if (!Array.isArray(v.inputHashes)) return void 0;
651
+ if (typeof v.redactionPolicyId !== "string") return void 0;
652
+ if (!Array.isArray(v.redactions)) return void 0;
653
+ return v;
654
+ }
655
+ /**
656
+ * Pure receipt verifier.
657
+ *
658
+ * Returns a typed VerifyResult — never throws across the verification
659
+ * boundary (PITFALLS.md security: "Verifier panics on malformed receipts
660
+ * -> DoS via crafted input"). All parsing failures become typed errors.
661
+ *
662
+ * Decision tree (first match wins):
663
+ * 1. decodeEnvelope throws OR signatures[] empty -> envelope-malformed
664
+ * 2. payload bytes are not valid JSON -> envelope-malformed
665
+ * 3. body shape check fails OR version unknown literal -> version-mismatch
666
+ * 4. body.version === undefined OR "lattice-receipt/v1"-> schema-version-too-low (CRYPTO-01)
667
+ * 5. keySet.lookup(keyid) === undefined -> key-not-found
668
+ * 6. entry.state === "revoked" -> key-revoked
669
+ * 7. re-canonicalized body != signed payloadBytes -> canonicalization-mismatch
670
+ * 8. Ed25519 verification of PAE fails -> signature-invalid
671
+ * 9. body.kid !== entry.kid (defense in depth) -> signature-invalid
672
+ * 10. otherwise -> ok + keyState
673
+ */
674
+ async function verifyReceipt(envelope, keySet) {
675
+ let decoded;
676
+ try {
677
+ decoded = decodeEnvelope(envelope);
678
+ } catch (error) {
679
+ return fail$1("envelope-malformed", error instanceof Error ? error.message : String(error));
680
+ }
681
+ if (decoded.signatures.length === 0) return fail$1("envelope-malformed", "envelope has no signatures");
682
+ let parsed;
683
+ try {
684
+ parsed = JSON.parse(new TextDecoder().decode(decoded.payloadBytes));
685
+ } catch (error) {
686
+ return fail$1("envelope-malformed", `payload is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
687
+ }
688
+ const body = asReceiptBody(parsed);
689
+ if (body === void 0) return fail$1("version-mismatch", "receipt body is not a lattice-receipt/v1 shape");
690
+ if (body.version === void 0 || body.version === "lattice-receipt/v1") return fail$1("schema-version-too-low", "Receipt body.version must be 'lattice-receipt/v1.1' — v1 receipts are not accepted (CRYPTO-01).");
691
+ const firstSig = decoded.signatures[0];
692
+ const entry = keySet.lookup(firstSig.keyid);
693
+ if (entry === void 0) return fail$1("key-not-found", `keySet has no entry for kid "${firstSig.keyid}"`);
694
+ if (entry.state === "revoked") return fail$1("key-revoked", `key "${entry.kid}" is revoked`);
695
+ if (!bytesEqual(canonicalizeReceiptBody(body), decoded.payloadBytes)) return fail$1("canonicalization-mismatch", "re-canonicalized body does not match signed payload bytes");
696
+ const pae = buildPae(PAYLOAD_TYPE, base64Encode(decoded.payloadBytes));
697
+ if (!await verifyEd25519Signature(entry.publicKeyJwk, pae, firstSig.sig)) return fail$1("signature-invalid", "Ed25519 signature does not verify");
698
+ if (body.kid !== entry.kid) return fail$1("signature-invalid", `body.kid "${body.kid}" does not match envelope keyid "${entry.kid}"`);
699
+ return {
700
+ ok: true,
701
+ body,
702
+ keyState: entry.state
703
+ };
704
+ }
705
+ //#endregion
706
+ //#region src/results/errors.ts
707
+ /**
708
+ * Returns `true` for run errors that MUST NOT be retried by the fallback
709
+ * chain. Phase 8 covers two kinds:
710
+ *
711
+ * - `tripwire-violated` — the contract's invariants rejected the output;
712
+ * a different provider will not change the verdict, so retry burns
713
+ * budget for no gain (T-08-06 in 08-02-PLAN threat register).
714
+ * - `no-contract-match` — no route satisfies the contract at all; the
715
+ * run never executed and no retry will help.
716
+ *
717
+ * All other error kinds return `false` and remain eligible for fallback.
718
+ * The predicate is exported so Phase 12's eval gate and any user-side
719
+ * retry wrappers can share one source of truth.
720
+ */
721
+ function isTerminal(error) {
722
+ return error.kind === "tripwire-violated" || error.kind === "no-contract-match";
723
+ }
724
+ //#endregion
725
+ //#region src/providers/adapters.ts
726
+ function createOpenAICompatibleProvider(options) {
727
+ const id = options.id ?? "openai-compatible";
728
+ const fetchImpl = options.fetch ?? fetch;
729
+ return {
730
+ id,
731
+ kind: "provider-adapter",
732
+ capabilities: [{
733
+ ...defaultCapabilityForProvider(id),
734
+ modelId: options.model,
735
+ fileTransport: [
736
+ "inline",
737
+ "json",
738
+ "url",
739
+ "base64",
740
+ "extracted-text",
741
+ "transcript"
742
+ ]
743
+ }],
744
+ async execute(request) {
745
+ const init = {
746
+ method: "POST",
747
+ headers: {
748
+ "content-type": "application/json",
749
+ ...options.apiKey !== void 0 ? { authorization: `Bearer ${options.apiKey}` } : {}
750
+ },
751
+ body: JSON.stringify({
752
+ model: options.model,
753
+ messages: [{
754
+ role: "user",
755
+ content: [
756
+ {
757
+ type: "text",
758
+ text: request.task
759
+ },
760
+ {
761
+ type: "text",
762
+ text: JSON.stringify({ contextPack: request.contextPack === void 0 ? void 0 : {
763
+ id: request.contextPack.id,
764
+ tokenBudget: request.contextPack.tokenBudget,
765
+ estimatedTokens: request.contextPack.estimatedTokens,
766
+ included: request.contextPack.included,
767
+ summarized: request.contextPack.summarized,
768
+ archived: request.contextPack.archived,
769
+ omitted: request.contextPack.omitted,
770
+ warnings: request.contextPack.warnings
771
+ } })
772
+ },
773
+ ...request.artifacts.map((inputArtifact) => ({
774
+ type: "text",
775
+ text: JSON.stringify({
776
+ artifactId: inputArtifact.id,
777
+ kind: inputArtifact.kind,
778
+ mediaType: inputArtifact.mediaType,
779
+ privacy: inputArtifact.privacy,
780
+ transport: request.providerPackaging?.artifacts.find((item) => item.artifactId === inputArtifact.id)?.transport ?? request.plan?.providerPackaging?.artifacts.find((item) => item.artifactId === inputArtifact.id)?.transport,
781
+ value: typeof inputArtifact.value === "string" && inputArtifact.kind !== "url" ? inputArtifact.value : void 0,
782
+ url: inputArtifact.kind === "url" && typeof inputArtifact.value === "string" ? inputArtifact.value : void 0
783
+ })
784
+ }))
785
+ ]
786
+ }]
787
+ }),
788
+ ...request.signal !== void 0 ? { signal: request.signal } : {}
789
+ };
790
+ const response = await fetchImpl(`${options.baseUrl.replace(/\/$/u, "")}/chat/completions`, init);
791
+ if (!response.ok) throw new Error(`OpenAI-compatible provider failed with ${response.status}.`);
792
+ const body = await response.json();
793
+ const text = String(body.choices?.[0]?.message?.content ?? "");
794
+ const usage = normalizeUsage(body.usage);
795
+ const normalizedUsage = normalizeUsageToRunUsage(body.usage, options.pricing);
796
+ return {
797
+ rawOutputs: Object.fromEntries(request.outputs.map((name) => [name, text])),
798
+ ...usage !== void 0 ? { usage } : {},
799
+ normalizedUsage,
800
+ rawResponse: body
801
+ };
802
+ }
803
+ };
804
+ }
805
+ /**
806
+ * Phase 7 normalization: maps raw provider usage payloads (OpenAI's
807
+ * `prompt_tokens`/`completion_tokens`, the Responses API's
808
+ * `input_tokens`/`output_tokens`, or camelCase variants) to the shared
809
+ * `Usage` shape. When `pricing` is supplied, `costUsd` is computed from
810
+ * the normalized token counts. Otherwise `costUsd` is `null` so consumers
811
+ * can distinguish "unmeasured" from "zero".
812
+ */
813
+ function normalizeUsageToRunUsage(rawUsage, pricing) {
814
+ let promptTokens = 0;
815
+ let completionTokens = 0;
816
+ if (typeof rawUsage === "object" && rawUsage !== null) {
817
+ const record = rawUsage;
818
+ promptTokens = numberField$2(record, "prompt_tokens") ?? numberField$2(record, "input_tokens") ?? numberField$2(record, "inputTokens") ?? 0;
819
+ completionTokens = numberField$2(record, "completion_tokens") ?? numberField$2(record, "output_tokens") ?? numberField$2(record, "outputTokens") ?? 0;
820
+ }
821
+ let costUsd = null;
822
+ if (pricing !== void 0 && (pricing.inputPer1kTokens !== void 0 || pricing.outputPer1kTokens !== void 0)) costUsd = (pricing.inputPer1kTokens ?? 0) * promptTokens / 1e3 + (pricing.outputPer1kTokens ?? 0) * completionTokens / 1e3;
823
+ return {
824
+ promptTokens,
825
+ completionTokens,
826
+ costUsd
827
+ };
828
+ }
829
+ function normalizeUsage(usage) {
830
+ if (typeof usage !== "object" || usage === null) return;
831
+ const record = usage;
832
+ const inputTokens = numberField$2(record, "prompt_tokens") ?? numberField$2(record, "input_tokens");
833
+ const outputTokens = numberField$2(record, "completion_tokens") ?? numberField$2(record, "output_tokens");
834
+ const totalTokens = numberField$2(record, "total_tokens");
835
+ return {
836
+ ...inputTokens !== void 0 ? { inputTokens } : {},
837
+ ...outputTokens !== void 0 ? { outputTokens } : {},
838
+ ...totalTokens !== void 0 ? { totalTokens } : {}
839
+ };
840
+ }
841
+ function numberField$2(record, key) {
842
+ const value = record[key];
843
+ return typeof value === "number" ? value : void 0;
844
+ }
845
+ function createOpenAIProvider(options) {
846
+ return createOpenAICompatibleProvider({
847
+ ...options,
848
+ id: options.id ?? "openai",
849
+ baseUrl: options.baseUrl
850
+ });
851
+ }
852
+ function createAISdkProvider(options) {
853
+ const id = options.id ?? "ai-sdk";
854
+ return {
855
+ id,
856
+ kind: "provider-adapter",
857
+ capabilities: [{
858
+ ...defaultCapabilityForProvider(id),
859
+ modelId: options.model,
860
+ toolUse: true,
861
+ streaming: true
862
+ }],
863
+ execute: async (request) => {
864
+ const response = await options.generate({
865
+ task: request.task,
866
+ outputNames: request.outputs
867
+ });
868
+ const normalizedUsage = {
869
+ promptTokens: response.usage?.inputTokens ?? 0,
870
+ completionTokens: response.usage?.outputTokens ?? 0,
871
+ costUsd: null
872
+ };
873
+ return {
874
+ ...response,
875
+ normalizedUsage
876
+ };
877
+ }
878
+ };
879
+ }
880
+ //#endregion
881
+ //#region src/providers/anthropic.ts
882
+ const DEFAULT_BASE_URL$1 = "https://api.anthropic.com";
883
+ const DEFAULT_ANTHROPIC_VERSION = "2023-06-01";
884
+ const DEFAULT_MAX_TOKENS = 2e3;
885
+ function createAnthropicProvider(options) {
886
+ const id = options.id ?? "anthropic";
887
+ const fetchImpl = options.fetch ?? fetch;
888
+ const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL$1).replace(/\/$/u, "");
889
+ const anthropicVersion = options.anthropicVersion ?? DEFAULT_ANTHROPIC_VERSION;
890
+ return {
891
+ id,
892
+ kind: "provider-adapter",
893
+ capabilities: [{
894
+ ...defaultCapabilityForProvider(id),
895
+ modelId: options.model,
896
+ fileTransport: [
897
+ "inline",
898
+ "json",
899
+ "url",
900
+ "base64",
901
+ "extracted-text",
902
+ "transcript"
903
+ ]
904
+ }],
905
+ async execute(request) {
906
+ const init = {
907
+ method: "POST",
908
+ headers: {
909
+ "content-type": "application/json",
910
+ "x-api-key": options.apiKey,
911
+ "anthropic-version": anthropicVersion
912
+ },
913
+ body: JSON.stringify({
914
+ model: options.model,
915
+ system: "",
916
+ messages: [{
917
+ role: "user",
918
+ content: request.task
919
+ }],
920
+ max_tokens: DEFAULT_MAX_TOKENS
921
+ }),
922
+ ...request.signal !== void 0 ? { signal: request.signal } : {}
923
+ };
924
+ const response = await fetchImpl(`${baseUrl}/v1/messages`, init);
925
+ if (!response.ok) throw new Error(`Anthropic provider failed with ${response.status}.`);
926
+ const body = await response.json();
927
+ const text = String(body.content?.[0]?.text ?? "");
928
+ const usage = normalizeAnthropicUsage(body.usage);
929
+ const normalizedUsage = normalizeAnthropicUsageToRunUsage(body.usage, options.pricing);
930
+ return {
931
+ rawOutputs: Object.fromEntries(request.outputs.map((name) => [name, text])),
932
+ ...usage !== void 0 ? { usage } : {},
933
+ normalizedUsage,
934
+ rawResponse: body
935
+ };
936
+ }
937
+ };
938
+ }
939
+ /**
940
+ * Anthropic uses `input_tokens` / `output_tokens` (not OpenAI's
941
+ * `prompt_tokens` / `completion_tokens`). This helper maps to Lattice's
942
+ * `Usage` shape and applies pricing when supplied (Phase 7 pattern).
943
+ */
944
+ function normalizeAnthropicUsageToRunUsage(rawUsage, pricing) {
945
+ let promptTokens = 0;
946
+ let completionTokens = 0;
947
+ if (typeof rawUsage === "object" && rawUsage !== null) {
948
+ const record = rawUsage;
949
+ promptTokens = numberField$1(record, "input_tokens") ?? numberField$1(record, "inputTokens") ?? 0;
950
+ completionTokens = numberField$1(record, "output_tokens") ?? numberField$1(record, "outputTokens") ?? 0;
951
+ }
952
+ let costUsd = null;
953
+ if (pricing !== void 0 && (pricing.inputPer1kTokens !== void 0 || pricing.outputPer1kTokens !== void 0)) costUsd = (pricing.inputPer1kTokens ?? 0) * promptTokens / 1e3 + (pricing.outputPer1kTokens ?? 0) * completionTokens / 1e3;
954
+ return {
955
+ promptTokens,
956
+ completionTokens,
957
+ costUsd
958
+ };
959
+ }
960
+ function normalizeAnthropicUsage(usage) {
961
+ if (typeof usage !== "object" || usage === null) return;
962
+ const record = usage;
963
+ const inputTokens = numberField$1(record, "input_tokens");
964
+ const outputTokens = numberField$1(record, "output_tokens");
965
+ const totalTokens = inputTokens !== void 0 && outputTokens !== void 0 ? inputTokens + outputTokens : void 0;
966
+ return {
967
+ ...inputTokens !== void 0 ? { inputTokens } : {},
968
+ ...outputTokens !== void 0 ? { outputTokens } : {},
969
+ ...totalTokens !== void 0 ? { totalTokens } : {}
970
+ };
971
+ }
972
+ function numberField$1(record, key) {
973
+ const value = record[key];
974
+ return typeof value === "number" ? value : void 0;
975
+ }
976
+ //#endregion
977
+ //#region src/providers/fake.ts
978
+ const DEFAULT_FAKE_USAGE = {
979
+ promptTokens: 0,
980
+ completionTokens: 0,
981
+ costUsd: null
982
+ };
983
+ function createFakeProvider(options = {}) {
984
+ const id = options.id ?? "fake";
985
+ const modelId = options.modelId ?? `${id}:deterministic`;
986
+ const defaultCapability = {
987
+ ...defaultCapabilityForProvider(id),
988
+ modelId,
989
+ inputModalities: [
990
+ "text",
991
+ "json",
992
+ "image",
993
+ "audio",
994
+ "document",
995
+ "file",
996
+ "url",
997
+ "tool"
998
+ ],
999
+ outputModalities: ["text", "json"],
1000
+ toolUse: true
1001
+ };
1002
+ return {
1003
+ id,
1004
+ kind: "provider-adapter",
1005
+ capabilities: options.capabilities ?? [defaultCapability],
1006
+ async execute(request) {
1007
+ const baseResponse = typeof options.response === "function" ? await options.response(request) : options.response;
1008
+ if (baseResponse !== void 0) return baseResponse.normalizedUsage !== void 0 ? baseResponse : {
1009
+ ...baseResponse,
1010
+ normalizedUsage: { ...DEFAULT_FAKE_USAGE }
1011
+ };
1012
+ return {
1013
+ rawOutputs: Object.fromEntries(request.outputs.map((name) => [name, defaultOutputForName(name)])),
1014
+ ...options.artifacts !== void 0 ? { artifactRefs: options.artifacts } : {},
1015
+ normalizedUsage: { ...DEFAULT_FAKE_USAGE }
1016
+ };
1017
+ }
1018
+ };
1019
+ }
1020
+ function defaultOutputForName(name) {
1021
+ if (/action|json|data|decision/u.test(name)) return {
1022
+ kind: "clarify",
1023
+ reason: "fake provider default structured response"
1024
+ };
1025
+ if (/citations|evidence/u.test(name)) return [];
1026
+ if (/generated|artifacts/u.test(name)) return [];
1027
+ return `Fake response for ${name}.`;
1028
+ }
1029
+ //#endregion
1030
+ //#region src/providers/gemini.ts
1031
+ const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com";
1032
+ const DEFAULT_MAX_OUTPUT_TOKENS = 2e3;
1033
+ const DEFAULT_TEMPERATURE = .7;
1034
+ const DEFAULT_TOP_P = .9;
1035
+ /**
1036
+ * 4 HARM_CATEGORY entries at BLOCK_NONE (FSB convention mirrored from
1037
+ * `extension/ai/universal-provider.js:255-272`). If Google restricts
1038
+ * BLOCK_NONE in the future, that is a re-spec concern, not a Phase 4
1039
+ * design defect (CONTEXT.md Specific Ideas note).
1040
+ */
1041
+ const SAFETY_SETTINGS = [
1042
+ {
1043
+ category: "HARM_CATEGORY_HARASSMENT",
1044
+ threshold: "BLOCK_NONE"
1045
+ },
1046
+ {
1047
+ category: "HARM_CATEGORY_HATE_SPEECH",
1048
+ threshold: "BLOCK_NONE"
1049
+ },
1050
+ {
1051
+ category: "HARM_CATEGORY_SEXUALLY_EXPLICIT",
1052
+ threshold: "BLOCK_NONE"
1053
+ },
1054
+ {
1055
+ category: "HARM_CATEGORY_DANGEROUS_CONTENT",
1056
+ threshold: "BLOCK_NONE"
1057
+ }
1058
+ ];
1059
+ function createGeminiProvider(options) {
1060
+ const id = options.id ?? "gemini";
1061
+ const fetchImpl = options.fetch ?? fetch;
1062
+ const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/u, "");
1063
+ return {
1064
+ id,
1065
+ kind: "provider-adapter",
1066
+ capabilities: [{
1067
+ ...defaultCapabilityForProvider(id),
1068
+ modelId: options.model,
1069
+ fileTransport: [
1070
+ "inline",
1071
+ "json",
1072
+ "url",
1073
+ "base64",
1074
+ "extracted-text",
1075
+ "transcript"
1076
+ ]
1077
+ }],
1078
+ async execute(request) {
1079
+ const init = {
1080
+ method: "POST",
1081
+ headers: { "content-type": "application/json" },
1082
+ body: JSON.stringify({
1083
+ contents: [{
1084
+ role: "user",
1085
+ parts: [{ text: request.task }]
1086
+ }],
1087
+ generationConfig: {
1088
+ temperature: DEFAULT_TEMPERATURE,
1089
+ topP: DEFAULT_TOP_P,
1090
+ maxOutputTokens: DEFAULT_MAX_OUTPUT_TOKENS
1091
+ },
1092
+ safetySettings: SAFETY_SETTINGS
1093
+ }),
1094
+ ...request.signal !== void 0 ? { signal: request.signal } : {}
1095
+ };
1096
+ const response = await fetchImpl(`${baseUrl}/v1beta/models/${encodeURIComponent(options.model)}:generateContent?key=${encodeURIComponent(options.apiKey)}`, init);
1097
+ if (!response.ok) throw new Error(`Gemini provider failed with ${response.status}.`);
1098
+ const body = await response.json();
1099
+ if (!Array.isArray(body.candidates) || body.candidates.length === 0) throw new Error("Gemini provider returned no candidates.");
1100
+ const text = String(body.candidates[0]?.content?.parts?.[0]?.text ?? "");
1101
+ const usage = normalizeGeminiUsage(body.usageMetadata);
1102
+ const normalizedUsage = normalizeGeminiUsageToRunUsage(body.usageMetadata, options.pricing);
1103
+ return {
1104
+ rawOutputs: Object.fromEntries(request.outputs.map((name) => [name, text])),
1105
+ ...usage !== void 0 ? { usage } : {},
1106
+ normalizedUsage,
1107
+ rawResponse: body
1108
+ };
1109
+ }
1110
+ };
1111
+ }
1112
+ /**
1113
+ * Gemini uses `usageMetadata.promptTokenCount` / `candidatesTokenCount` /
1114
+ * `totalTokenCount` (NOT OpenAI's `prompt_tokens` / `completion_tokens`).
1115
+ * This helper maps to Lattice's `Usage` shape and applies pricing when supplied.
1116
+ */
1117
+ function normalizeGeminiUsageToRunUsage(rawUsage, pricing) {
1118
+ let promptTokens = 0;
1119
+ let completionTokens = 0;
1120
+ if (typeof rawUsage === "object" && rawUsage !== null) {
1121
+ const record = rawUsage;
1122
+ promptTokens = numberField(record, "promptTokenCount") ?? 0;
1123
+ completionTokens = numberField(record, "candidatesTokenCount") ?? 0;
1124
+ }
1125
+ let costUsd = null;
1126
+ if (pricing !== void 0 && (pricing.inputPer1kTokens !== void 0 || pricing.outputPer1kTokens !== void 0)) costUsd = (pricing.inputPer1kTokens ?? 0) * promptTokens / 1e3 + (pricing.outputPer1kTokens ?? 0) * completionTokens / 1e3;
1127
+ return {
1128
+ promptTokens,
1129
+ completionTokens,
1130
+ costUsd
1131
+ };
1132
+ }
1133
+ function normalizeGeminiUsage(usage) {
1134
+ if (typeof usage !== "object" || usage === null) return;
1135
+ const record = usage;
1136
+ const inputTokens = numberField(record, "promptTokenCount");
1137
+ const outputTokens = numberField(record, "candidatesTokenCount");
1138
+ const totalTokens = numberField(record, "totalTokenCount");
1139
+ return {
1140
+ ...inputTokens !== void 0 ? { inputTokens } : {},
1141
+ ...outputTokens !== void 0 ? { outputTokens } : {},
1142
+ ...totalTokens !== void 0 ? { totalTokens } : {}
1143
+ };
1144
+ }
1145
+ function numberField(record, key) {
1146
+ const value = record[key];
1147
+ return typeof value === "number" ? value : void 0;
1148
+ }
1149
+ //#endregion
1150
+ //#region src/providers/lm-studio.ts
1151
+ const DEFAULT_LM_STUDIO_BASE_URL = "http://localhost:1234/v1";
1152
+ function createLmStudioProvider(options) {
1153
+ return createOpenAICompatibleProvider({
1154
+ ...options,
1155
+ id: options.id ?? "lm-studio",
1156
+ baseUrl: options.baseUrl ?? DEFAULT_LM_STUDIO_BASE_URL
1157
+ });
1158
+ }
1159
+ //#endregion
1160
+ //#region src/providers/openrouter.ts
1161
+ const DEFAULT_OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
1162
+ function createOpenRouterProvider(options) {
1163
+ return createOpenAICompatibleProvider({
1164
+ ...options,
1165
+ id: options.id ?? "openrouter",
1166
+ baseUrl: options.baseUrl ?? DEFAULT_OPENROUTER_BASE_URL
1167
+ });
1168
+ }
1169
+ //#endregion
1170
+ //#region src/providers/xai.ts
1171
+ const DEFAULT_XAI_BASE_URL = "https://api.x.ai/v1";
1172
+ function createXaiProvider(options) {
1173
+ const inner = createOpenAICompatibleProvider({
1174
+ ...options,
1175
+ id: options.id ?? "xai",
1176
+ baseUrl: options.baseUrl ?? DEFAULT_XAI_BASE_URL
1177
+ });
1178
+ const innerExecute = inner.execute;
1179
+ if (innerExecute === void 0) return inner;
1180
+ return {
1181
+ ...inner,
1182
+ async execute(request) {
1183
+ const response = await innerExecute(request);
1184
+ const reasoningTokens = response.rawResponse?.usage?.completion_tokens_details?.reasoning_tokens;
1185
+ if (typeof reasoningTokens === "number" && response.usage !== void 0) {
1186
+ const inputTokens = response.usage.inputTokens ?? 0;
1187
+ const outputTokens = response.usage.outputTokens ?? 0;
1188
+ return {
1189
+ ...response,
1190
+ usage: {
1191
+ ...response.usage,
1192
+ totalTokens: inputTokens + outputTokens + reasoningTokens
1193
+ }
1194
+ };
1195
+ }
1196
+ return response;
1197
+ }
1198
+ };
1199
+ }
1200
+ //#endregion
1201
+ //#region src/plan/plan.ts
1202
+ function createExecutionPlan(input) {
1203
+ const selected = input.route.selected;
1204
+ const status = selected === void 0 ? "no-route" : "planned";
1205
+ const contextWarnings = input.context?.warnings ?? [];
1206
+ const packagingWarnings = input.providerPackaging?.warnings ?? [];
1207
+ const warnings = [
1208
+ ...input.warnings ?? [],
1209
+ ...contextWarnings,
1210
+ ...packagingWarnings,
1211
+ ...input.route.noRouteReasons.map((reason) => reason.message)
1212
+ ];
1213
+ return {
1214
+ id: createPlanId(),
1215
+ kind: "execution-plan",
1216
+ version: 1,
1217
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1218
+ status,
1219
+ task: input.task,
1220
+ outputNames: Object.keys(input.outputs),
1221
+ artifactRefs: input.artifacts,
1222
+ route: input.route,
1223
+ stages: createDefaultStages(status, input.artifacts, warnings),
1224
+ ...input.context !== void 0 ? { context: input.context } : {},
1225
+ ...input.providerPackaging !== void 0 ? { providerPackaging: input.providerPackaging } : {},
1226
+ attempts: selected === void 0 ? [] : [{
1227
+ providerId: selected.providerId,
1228
+ modelId: selected.modelId,
1229
+ status: "pending"
1230
+ }],
1231
+ warnings,
1232
+ ...input.metadata !== void 0 ? { metadata: input.metadata } : {}
1233
+ };
1234
+ }
1235
+ function withPlanStatus(plan, status, updates = {}) {
1236
+ return {
1237
+ ...plan,
1238
+ status,
1239
+ ...updates.stages !== void 0 ? { stages: updates.stages } : {},
1240
+ ...updates.attempts !== void 0 ? { attempts: updates.attempts } : {},
1241
+ ...updates.warnings !== void 0 ? { warnings: updates.warnings } : {}
1242
+ };
1243
+ }
1244
+ function markStage(stages, kind, status, metadata) {
1245
+ return stages.map((stage) => stage.kind === kind ? {
1246
+ ...stage,
1247
+ status,
1248
+ ...metadata !== void 0 ? { metadata: {
1249
+ ...stage.metadata,
1250
+ ...metadata
1251
+ } } : {}
1252
+ } : stage);
1253
+ }
1254
+ function createDefaultStages(status, artifacts, warnings) {
1255
+ const skipped = status === "no-route";
1256
+ const artifactIds = artifacts.map((artifact) => artifact.id);
1257
+ return [
1258
+ {
1259
+ id: "stage:analysis",
1260
+ kind: "analysis",
1261
+ status: "completed",
1262
+ inputArtifacts: artifactIds,
1263
+ warnings: []
1264
+ },
1265
+ {
1266
+ id: "stage:transforms",
1267
+ kind: "transforms",
1268
+ status: "pending",
1269
+ inputArtifacts: artifactIds,
1270
+ warnings: []
1271
+ },
1272
+ {
1273
+ id: "stage:context-packing",
1274
+ kind: "context-packing",
1275
+ status: "completed",
1276
+ inputArtifacts: artifactIds,
1277
+ warnings: []
1278
+ },
1279
+ {
1280
+ id: "stage:provider-packaging",
1281
+ kind: "provider-packaging",
1282
+ status: skipped ? "skipped" : "completed",
1283
+ inputArtifacts: artifactIds,
1284
+ warnings
1285
+ },
1286
+ {
1287
+ id: "stage:tool-execution",
1288
+ kind: "tool-execution",
1289
+ status: "pending",
1290
+ warnings: []
1291
+ },
1292
+ {
1293
+ id: "stage:execution",
1294
+ kind: "execution",
1295
+ status: skipped ? "skipped" : "pending",
1296
+ warnings: skipped ? warnings : []
1297
+ },
1298
+ {
1299
+ id: "stage:validation",
1300
+ kind: "validation",
1301
+ status: skipped ? "skipped" : "pending",
1302
+ warnings: []
1303
+ },
1304
+ {
1305
+ id: "stage:tripwire",
1306
+ kind: "tripwire",
1307
+ status: skipped ? "skipped" : "pending",
1308
+ warnings: []
1309
+ },
1310
+ {
1311
+ id: "stage:persistence",
1312
+ kind: "persistence",
1313
+ status: "pending",
1314
+ warnings: []
1315
+ }
1316
+ ];
1317
+ }
1318
+ function createPlanId() {
1319
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return `plan:${crypto.randomUUID()}`;
1320
+ return `plan:${Date.now()}:${Math.random().toString(16).slice(2)}`;
1321
+ }
1322
+ //#endregion
1323
+ //#region src/version.ts
1324
+ const latticeVersion = "0.0.0";
1325
+ //#endregion
1326
+ //#region src/replay/materialize.ts
1327
+ function asMaterializationError(value) {
1328
+ return typeof value === "object" && value !== null && typeof value.kind === "string" && typeof value.message === "string";
1329
+ }
1330
+ /** Throwable shape — `instanceof Error` is not required for typed unions, so
1331
+ * the function just throws a plain object literal that matches the
1332
+ * `MaterializationError` shape. Callers pattern-match on `err.kind`. */
1333
+ function fail(kind, message) {
1334
+ return {
1335
+ kind,
1336
+ message
1337
+ };
1338
+ }
1339
+ /**
1340
+ * Pure async function that reconstructs a `ReplayEnvelope` from a receipt.
1341
+ *
1342
+ * Verify-FIRST ordering: `verifyReceipt` runs before `artifactLoader` is
1343
+ * touched. Tampered receipts MUST NOT cause loader side effects.
1344
+ */
1345
+ async function materializeReplayEnvelope(receipt, options) {
1346
+ const verifyResult = await verifyReceipt(receipt, options.keySet);
1347
+ if (!verifyResult.ok) throw fail(verifyResult.error.kind === "envelope-malformed" ? "envelope-malformed" : "verify-failed", verifyResult.error.message);
1348
+ const body = verifyResult.body;
1349
+ const loadedInputs = [];
1350
+ for (const hash of body.inputHashes) {
1351
+ if (hash === "") continue;
1352
+ try {
1353
+ const input = await options.artifactLoader(hash);
1354
+ loadedInputs.push(input);
1355
+ } catch (error) {
1356
+ throw fail("artifact-load-failed", error instanceof Error ? error.message : asMaterializationError(error) ? error.message : String(error));
1357
+ }
1358
+ }
1359
+ const artifactRefs = loadedInputs.map(toArtifactRef);
1360
+ const outputsMap = options.outputs !== void 0 ? Object.fromEntries(Object.keys(options.outputs).map((k) => [k, "text"])) : {};
1361
+ const plan = createExecutionPlan({
1362
+ task: options.task ?? "",
1363
+ artifacts: artifactRefs,
1364
+ outputs: outputsMap,
1365
+ route: {
1366
+ catalogVersion: "materialized",
1367
+ selected: {
1368
+ providerId: body.route.providerId,
1369
+ modelId: body.route.capabilityId,
1370
+ score: 0,
1371
+ estimates: {
1372
+ inputTokens: 0,
1373
+ outputTokens: 0
1374
+ },
1375
+ inputModalities: [],
1376
+ outputModalities: [],
1377
+ fileTransport: []
1378
+ },
1379
+ candidates: [],
1380
+ rejected: [],
1381
+ fallbackChain: [],
1382
+ noRouteReasons: []
1383
+ },
1384
+ warnings: [],
1385
+ metadata: {
1386
+ materialized: true,
1387
+ receiptId: body.receiptId,
1388
+ runId: body.runId,
1389
+ contractVerdict: body.contractVerdict,
1390
+ ...options.policy !== void 0 ? { policy: { ...options.policy } } : {}
1391
+ }
1392
+ });
1393
+ const usage = {
1394
+ inputTokens: body.usage.promptTokens,
1395
+ outputTokens: body.usage.completionTokens,
1396
+ ...body.usage.costUsd !== null ? { costUsd: Number(body.usage.costUsd) } : {}
1397
+ };
1398
+ return {
1399
+ kind: "replay-envelope",
1400
+ version: 1,
1401
+ runtimeVersion: latticeVersion,
1402
+ catalogVersion: "materialized",
1403
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1404
+ plan,
1405
+ artifacts: artifactRefs,
1406
+ ...options.outputs !== void 0 ? { outputs: options.outputs } : {},
1407
+ warnings: [],
1408
+ errors: [],
1409
+ usage,
1410
+ events: [],
1411
+ receipt,
1412
+ ...options.contract !== void 0 ? { contract: options.contract } : {}
1413
+ };
1414
+ }
1415
+ //#endregion
1416
+ //#region src/replay/replay.ts
1417
+ function createReplayEnvelope(result) {
1418
+ if (result.plan.kind !== "execution-plan") throw new Error("Replay envelopes require an execution plan.");
1419
+ const usage = result.plan.attempts.at(-1)?.usage;
1420
+ return {
1421
+ kind: "replay-envelope",
1422
+ version: 1,
1423
+ runtimeVersion: latticeVersion,
1424
+ catalogVersion: result.plan.route.catalogVersion,
1425
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1426
+ plan: redactPlan(result.plan),
1427
+ artifacts: result.ok ? result.artifacts : result.plan.artifactRefs,
1428
+ ...result.ok ? { outputs: result.outputs } : {},
1429
+ warnings: result.plan.warnings,
1430
+ errors: result.ok ? [] : [result.error.message],
1431
+ ...usage !== void 0 ? { usage } : {},
1432
+ events: result.events ?? []
1433
+ };
1434
+ }
1435
+ async function replayOffline(envelope) {
1436
+ const replayedUsage = envelopeUsage(envelope);
1437
+ if (envelope.outputs === void 0) return {
1438
+ ok: false,
1439
+ error: {
1440
+ kind: "execution_unavailable",
1441
+ message: "Replay envelope does not contain successful outputs."
1442
+ },
1443
+ usage: replayedUsage,
1444
+ plan: envelope.plan,
1445
+ events: envelope.events
1446
+ };
1447
+ return {
1448
+ ok: true,
1449
+ outputs: envelope.outputs,
1450
+ artifacts: envelope.artifacts,
1451
+ usage: replayedUsage,
1452
+ plan: envelope.plan,
1453
+ events: envelope.events
1454
+ };
1455
+ }
1456
+ function envelopeUsage(envelope) {
1457
+ if (envelope.usage === void 0) return {
1458
+ promptTokens: 0,
1459
+ completionTokens: 0,
1460
+ costUsd: null
1461
+ };
1462
+ return {
1463
+ promptTokens: envelope.usage.inputTokens ?? 0,
1464
+ completionTokens: envelope.usage.outputTokens ?? 0,
1465
+ costUsd: envelope.usage.costUsd ?? null
1466
+ };
1467
+ }
1468
+ async function rerunLive(ai, envelope, intent) {
1469
+ const result = await ai.run(intent);
1470
+ if (result.plan.kind === "execution-plan") return {
1471
+ ...result,
1472
+ plan: {
1473
+ ...result.plan,
1474
+ warnings: [...result.plan.warnings, `Live rerun of ${envelope.plan.id}: provider behavior, model versions, cost, and latency may differ.`]
1475
+ }
1476
+ };
1477
+ return result;
1478
+ }
1479
+ function redactReplayEnvelope(envelope) {
1480
+ return {
1481
+ ...envelope,
1482
+ plan: redactPlan(envelope.plan),
1483
+ artifacts: envelope.artifacts.map(redactArtifactRef),
1484
+ events: envelope.events.map((event) => {
1485
+ const metadata = redactRecord(event.metadata);
1486
+ return {
1487
+ ...event,
1488
+ ...metadata !== void 0 ? { metadata } : {}
1489
+ };
1490
+ })
1491
+ };
1492
+ }
1493
+ function redactPlan(plan) {
1494
+ return {
1495
+ ...plan,
1496
+ task: redactText(plan.task),
1497
+ artifactRefs: plan.artifactRefs.map(redactArtifactRef),
1498
+ ...plan.providerPackaging !== void 0 ? { providerPackaging: {
1499
+ ...plan.providerPackaging,
1500
+ artifacts: plan.providerPackaging.artifacts.map((item) => ({
1501
+ ...item,
1502
+ warnings: item.warnings.map(redactText)
1503
+ })),
1504
+ warnings: plan.providerPackaging.warnings.map(redactText)
1505
+ } } : {},
1506
+ warnings: plan.warnings.map(redactText)
1507
+ };
1508
+ }
1509
+ function redactArtifactRef(ref) {
1510
+ const redactedMetadata = redactRecord(ref.metadata);
1511
+ return {
1512
+ ...ref,
1513
+ ...redactedMetadata !== void 0 ? { metadata: redactedMetadata } : {},
1514
+ ...ref.source === "url" ? { metadata: {
1515
+ ...redactedMetadata,
1516
+ redactedSource: "url"
1517
+ } } : {}
1518
+ };
1519
+ }
1520
+ function redactRecord(record) {
1521
+ if (record === void 0) return;
1522
+ return Object.fromEntries(Object.entries(record).map(([key, value]) => [key, shouldRedactKey(key) ? "[redacted]" : redactValue(value)]));
1523
+ }
1524
+ function redactValue(value) {
1525
+ if (typeof value === "string") return redactText(value);
1526
+ if (Array.isArray(value)) return value.map(redactValue);
1527
+ if (typeof value === "object" && value !== null) return redactRecord(value);
1528
+ return value;
1529
+ }
1530
+ function redactText(value) {
1531
+ return value.replace(/Bearer\s+[A-Za-z0-9._-]+/gu, "Bearer [redacted]").replace(/https?:\/\/[^\s)]+/gu, "[redacted-url]").replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/gu, "[redacted-email]");
1532
+ }
1533
+ function shouldRedactKey(key) {
1534
+ return /api.?key|authorization|token|secret|password|credential|signed.?url|raw|body|transcript/iu.test(key);
1535
+ }
1536
+ //#endregion
1537
+ //#region src/agent/infra/cost-tracker.ts
1538
+ const WARNING_THRESHOLD = .8;
1539
+ function createCostTracker() {
1540
+ let promptTokens = 0;
1541
+ let completionTokens = 0;
1542
+ let costUsd = null;
1543
+ return {
1544
+ kind: "cost-tracker",
1545
+ recordIteration(usage) {
1546
+ promptTokens += usage.promptTokens;
1547
+ completionTokens += usage.completionTokens;
1548
+ if (usage.costUsd !== null) costUsd = (costUsd ?? 0) + usage.costUsd;
1549
+ },
1550
+ total() {
1551
+ return {
1552
+ promptTokens,
1553
+ completionTokens,
1554
+ costUsd
1555
+ };
1556
+ },
1557
+ budgetStatus(budget) {
1558
+ const max = budget?.maxCostUsd;
1559
+ if (max === void 0 || costUsd === null) return "ok";
1560
+ if (costUsd >= max) return "exceeded";
1561
+ if (costUsd >= max * WARNING_THRESHOLD) return "warning";
1562
+ return "ok";
1563
+ }
1564
+ };
1565
+ }
1566
+ //#endregion
1567
+ //#region src/agent/infra/transcript-store.ts
1568
+ const DEFAULT_TOKEN_ESTIMATOR = (text) => Math.ceil(text.length / 4);
1569
+ function createTranscriptStore() {
1570
+ const turns = [];
1571
+ function firstUserTurn() {
1572
+ for (const turn of turns) if (turn.role === "user") return turn;
1573
+ return null;
1574
+ }
1575
+ return {
1576
+ kind: "transcript-store",
1577
+ append(turn) {
1578
+ turns.push(turn);
1579
+ },
1580
+ all() {
1581
+ return Object.freeze([...turns]);
1582
+ },
1583
+ tail(limit) {
1584
+ if (limit <= 0) return Object.freeze([]);
1585
+ if (turns.length <= limit) return Object.freeze([...turns]);
1586
+ const start = turns.length - limit;
1587
+ const tail = turns.slice(start);
1588
+ const first = firstUserTurn();
1589
+ if (first === null || tail.includes(first)) return Object.freeze(tail);
1590
+ return Object.freeze([first, ...tail]);
1591
+ },
1592
+ tailByTokens(maxTokens, estimator = DEFAULT_TOKEN_ESTIMATOR) {
1593
+ if (maxTokens <= 0) return Object.freeze([]);
1594
+ const reversed = [...turns].reverse();
1595
+ const selected = [];
1596
+ let used = 0;
1597
+ for (const turn of reversed) {
1598
+ const cost = estimator(turn.content);
1599
+ if (used + cost > maxTokens) break;
1600
+ selected.unshift(turn);
1601
+ used += cost;
1602
+ }
1603
+ const first = firstUserTurn();
1604
+ if (first !== null && !selected.includes(first)) selected.unshift(first);
1605
+ return Object.freeze(selected);
1606
+ }
1607
+ };
1608
+ }
1609
+ //#endregion
1610
+ //#region src/agent/infra/goal-progress.ts
1611
+ function createGoalProgressTracker(options = {}) {
1612
+ const windowSize = options.windowSize ?? 3;
1613
+ const stallThreshold = options.stallThreshold ?? .02;
1614
+ const regressionThreshold = options.regressionThreshold ?? .1;
1615
+ const steps = [];
1616
+ return {
1617
+ kind: "goal-progress-tracker",
1618
+ recordStep(step) {
1619
+ steps.push(step);
1620
+ },
1621
+ status() {
1622
+ if (steps.length < windowSize) return "progressing";
1623
+ const window = steps.slice(-windowSize);
1624
+ const latest = window[window.length - 1];
1625
+ const earlierMax = steps.slice(0, -1).reduce((m, s) => s.goalSatisfaction > m ? s.goalSatisfaction : m, -Infinity);
1626
+ if (latest.goalSatisfaction < earlierMax - regressionThreshold) return "regressed";
1627
+ const min = window.reduce((m, s) => s.goalSatisfaction < m ? s.goalSatisfaction : m, Infinity);
1628
+ if (window.reduce((m, s) => s.goalSatisfaction > m ? s.goalSatisfaction : m, -Infinity) - min <= stallThreshold) return "stalled";
1629
+ return "progressing";
1630
+ }
1631
+ };
1632
+ }
1633
+ //#endregion
1634
+ //#region src/agent/infra/action-history.ts
1635
+ /**
1636
+ * ActionHistory — Phase 21 (v1.2).
1637
+ *
1638
+ * Detects stuck patterns in the agent loop's tool-call sequence:
1639
+ * - "consecutive-identical-tool-call" — same (toolName, argsHash) N+ times in a row
1640
+ * - "ping-pong" — last 4 records alternate between 2 distinct (toolName, argsHash) pairs
1641
+ * - "no-progress" — reserved for callers wiring goal-progress feedback here
1642
+ *
1643
+ * Standalone (no dependency on the agent runtime); callers register the
1644
+ * primitive externally and pump `recordAction` from their loop or hook.
1645
+ */
1646
+ const STUCK_REASONS = [
1647
+ "consecutive-identical-tool-call",
1648
+ "no-progress",
1649
+ "ping-pong"
1650
+ ];
1651
+ function actionKey(record) {
1652
+ return `${record.toolName}::${record.argsHash}`;
1653
+ }
1654
+ function createActionHistory(options = {}) {
1655
+ const consecutiveLimit = options.consecutiveLimit ?? 3;
1656
+ const records = [];
1657
+ return {
1658
+ kind: "action-history",
1659
+ recordAction(action) {
1660
+ records.push(action);
1661
+ if (records.length >= consecutiveLimit) {
1662
+ const tail = records.slice(-consecutiveLimit);
1663
+ const firstKey = actionKey(tail[0]);
1664
+ if (tail.every((r) => actionKey(r) === firstKey)) return "consecutive-identical-tool-call";
1665
+ }
1666
+ if (records.length >= 4) {
1667
+ const keys = records.slice(-4).map(actionKey);
1668
+ if (Array.from(new Set(keys)).length === 2 && keys[0] === keys[2] && keys[1] === keys[3] && keys[0] !== keys[1]) return "ping-pong";
1669
+ }
1670
+ return null;
1671
+ },
1672
+ history() {
1673
+ return Object.freeze([...records]);
1674
+ }
1675
+ };
1676
+ }
1677
+ //#endregion
1678
+ //#region src/agent/infra/permission-context.ts
1679
+ /**
1680
+ * PermissionContext — Phase 21 (v1.2).
1681
+ *
1682
+ * Gates tool execution per-tool / per-iteration / per-resource. Includes
1683
+ * a SAFETY-band hook helper that wires the context into the agent loop's
1684
+ * BEFORE_TOOL pipeline via the Phase 19 `controls.deny(reason)` veto.
1685
+ */
1686
+ function matches(matcher, value) {
1687
+ if (matcher === void 0) return true;
1688
+ if (value === void 0) return false;
1689
+ if (typeof matcher === "string") return matcher === value;
1690
+ return matcher.test(value);
1691
+ }
1692
+ function createPermissionContext(rules) {
1693
+ return {
1694
+ kind: "permission-context",
1695
+ decide(input) {
1696
+ for (const rule of rules) {
1697
+ if (!matches(rule.toolName, input.toolName)) continue;
1698
+ if (rule.resource !== void 0 && !matches(rule.resource, input.resource)) continue;
1699
+ if (rule.verdict === "allow") return { allow: true };
1700
+ return {
1701
+ allow: false,
1702
+ reason: rule.reason ?? `denied by permission rule for ${input.toolName}`
1703
+ };
1704
+ }
1705
+ return { allow: true };
1706
+ }
1707
+ };
1708
+ }
1709
+ function createPermissionGuardHook(context) {
1710
+ return (ctx, controls) => {
1711
+ const verdict = context.decide({
1712
+ iterationIndex: ctx.iterationIndex,
1713
+ toolName: ctx.toolName,
1714
+ ...ctx.resource !== void 0 ? { resource: ctx.resource } : {},
1715
+ ...ctx.args !== void 0 ? { args: ctx.args } : {}
1716
+ });
1717
+ if (!verdict.allow) controls?.deny(verdict.reason);
1718
+ };
1719
+ }
1720
+ /**
1721
+ * Convenience: returns RegisterOptions for the SAFETY-band registration.
1722
+ * Callers do `pipeline.register("BEFORE_TOOL", hook, permissionGuardRegisterOptions())`.
1723
+ */
1724
+ function permissionGuardRegisterOptions() {
1725
+ return { band: BAND.SAFETY };
1726
+ }
1727
+ //#endregion
1728
+ //#region src/agent/eval.ts
1729
+ function evalAgentRun(baseline, current, options = {}) {
1730
+ const iterLimit = options.iterationsToGoalRegressionLimit ?? 1;
1731
+ const costLimit = options.costUsdRegressionLimit ?? .1;
1732
+ const regressions = [];
1733
+ const iterDelta = current.iterationsToGoal - baseline.iterationsToGoal;
1734
+ if (iterDelta > iterLimit) regressions.push({
1735
+ kind: "iterations-to-goal",
1736
+ baseline: baseline.iterationsToGoal,
1737
+ current: current.iterationsToGoal,
1738
+ limit: iterLimit,
1739
+ message: `Iterations-to-goal ${current.iterationsToGoal} exceeds baseline ${baseline.iterationsToGoal} by ${iterDelta} (limit: ${iterLimit}).`
1740
+ });
1741
+ const bCost = baseline.usage.costUsd;
1742
+ const cCost = current.usage.costUsd;
1743
+ if (bCost === null && cCost === null) {} else if (bCost === null || cCost === null) regressions.push({
1744
+ kind: "mixed-cost-unknown",
1745
+ baseline: bCost,
1746
+ current: cCost,
1747
+ limit: costLimit,
1748
+ message: `Cost mixed: baseline=${bCost} current=${cCost}; cannot compare regression.`
1749
+ });
1750
+ else if (bCost > 0) {
1751
+ const ratio = (cCost - bCost) / bCost;
1752
+ if (ratio > costLimit) regressions.push({
1753
+ kind: "cost-regression",
1754
+ baseline: bCost,
1755
+ current: cCost,
1756
+ limit: costLimit,
1757
+ message: `Cost regression: $${cCost.toFixed(6)} vs baseline $${bCost.toFixed(6)} (+${(ratio * 100).toFixed(1)}%; limit ${(costLimit * 100).toFixed(1)}%).`
1758
+ });
1759
+ } else if (bCost === 0 && cCost > 0) regressions.push({
1760
+ kind: "cost-regression",
1761
+ baseline: bCost,
1762
+ current: cCost,
1763
+ limit: costLimit,
1764
+ message: `Cost regression: baseline was $0; current $${cCost.toFixed(6)}.`
1765
+ });
1766
+ return {
1767
+ ok: regressions.length === 0,
1768
+ regressions: Object.freeze(regressions)
1769
+ };
1770
+ }
1771
+ //#endregion
1772
+ //#region src/context/context-pack.ts
1773
+ function buildContextPack(input) {
1774
+ const routeBudget = input.route?.estimates.inputTokens ?? input.route?.inputModalities.length ?? 0;
1775
+ const tokenBudget = input.tokenBudget ?? Math.max(512, Math.min(input.route?.estimates.inputTokens ?? 4e3, 16e3));
1776
+ const remainingBudget = Math.max(512, tokenBudget - estimateTokens(input.task) - routeBudget);
1777
+ const included = [];
1778
+ const summarized = [];
1779
+ const archived = [];
1780
+ const omitted = [];
1781
+ const warnings = [];
1782
+ let usedTokens = 0;
1783
+ for (const artifact of input.artifacts) {
1784
+ const artifactTokens = estimateArtifactTokens(artifact);
1785
+ const item = {
1786
+ artifactId: artifact.id,
1787
+ reason: "Run artifact included for provider consideration.",
1788
+ estimatedTokens: artifactTokens,
1789
+ trust: trustForArtifact(artifact)
1790
+ };
1791
+ if (usedTokens + artifactTokens <= remainingBudget) {
1792
+ included.push(item);
1793
+ usedTokens += artifactTokens;
1794
+ continue;
1795
+ }
1796
+ if (artifact.kind === "text" || artifact.kind === "document" || artifact.kind === "json") {
1797
+ summarized.push({
1798
+ ...item,
1799
+ reason: "Artifact exceeded live context budget and needs summary packaging."
1800
+ });
1801
+ usedTokens += Math.min(artifactTokens, 256);
1802
+ continue;
1803
+ }
1804
+ omitted.push({
1805
+ ...item,
1806
+ reason: "Artifact exceeded context budget and cannot be summarized by the default packer."
1807
+ });
1808
+ warnings.push(`Artifact ${artifact.id} omitted from live context budget.`);
1809
+ }
1810
+ for (const turn of input.session?.turns ?? []) {
1811
+ const turnTokens = estimateTokens(turn.task);
1812
+ const item = {
1813
+ sessionTurnId: turn.id,
1814
+ reason: "Prior session turn retained for continuity.",
1815
+ estimatedTokens: turnTokens,
1816
+ trust: "user"
1817
+ };
1818
+ if (usedTokens + turnTokens <= remainingBudget) {
1819
+ included.push(item);
1820
+ usedTokens += turnTokens;
1821
+ } else archived.push({
1822
+ ...item,
1823
+ reason: "Prior session turn archived because the run budget was exhausted."
1824
+ });
1825
+ }
1826
+ return {
1827
+ id: createContextPackId(),
1828
+ kind: "context-pack",
1829
+ tokenBudget,
1830
+ estimatedTokens: usedTokens,
1831
+ included,
1832
+ summarized,
1833
+ archived,
1834
+ omitted,
1835
+ warnings
1836
+ };
1837
+ }
1838
+ function estimateArtifactTokens(artifact) {
1839
+ if (artifact.size?.characters !== void 0) return estimateTokensFromCharacters(artifact.size.characters);
1840
+ if (artifact.size?.bytes !== void 0) return estimateTokensFromCharacters(Math.ceil(artifact.size.bytes / 2));
1841
+ if ("value" in artifact && typeof artifact.value === "string") return estimateTokens(artifact.value);
1842
+ if ("value" in artifact && artifact.value !== void 0) {
1843
+ const serialized = JSON.stringify(artifact.value);
1844
+ return serialized === void 0 ? 64 : estimateTokens(serialized);
1845
+ }
1846
+ return 64;
1847
+ }
1848
+ function estimateTokens(value) {
1849
+ return Math.max(1, estimateTokensFromCharacters(value.length));
1850
+ }
1851
+ function estimateTokensFromCharacters(characters) {
1852
+ return Math.ceil(characters / 4);
1853
+ }
1854
+ function trustForArtifact(artifact) {
1855
+ if (artifact.source === "tool") return "tool";
1856
+ if (artifact.source === "generated") return "model-summary";
1857
+ return "user";
1858
+ }
1859
+ function createContextPackId() {
1860
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return `context-pack:${crypto.randomUUID()}`;
1861
+ return `context-pack:${Date.now()}:${Math.random().toString(16).slice(2)}`;
1862
+ }
1863
+ //#endregion
1864
+ //#region src/policy/policy.ts
1865
+ function mergePolicy(defaultPolicy, runPolicy) {
1866
+ if (defaultPolicy === void 0 && runPolicy === void 0) return;
1867
+ return {
1868
+ ...defaultPolicy,
1869
+ ...runPolicy
1870
+ };
1871
+ }
1872
+ //#endregion
1873
+ //#region src/providers/packaging.ts
1874
+ function packageArtifactsForProvider(input) {
1875
+ const route = input.route;
1876
+ if (route === void 0) return {
1877
+ plan: {
1878
+ providerId: "none",
1879
+ modelId: "none",
1880
+ artifacts: [],
1881
+ warnings: ["No selected route; provider packaging skipped."]
1882
+ },
1883
+ packagedArtifacts: [],
1884
+ blocked: []
1885
+ };
1886
+ const packaged = [];
1887
+ const packagedArtifacts = [];
1888
+ const warnings = [];
1889
+ const blocked = [];
1890
+ for (const inputArtifact of input.artifacts) {
1891
+ const choice = chooseTransport(inputArtifact, route.fileTransport, input.policy);
1892
+ if (choice.blocked !== void 0) {
1893
+ blocked.push(choice.blocked);
1894
+ warnings.push(choice.blocked);
1895
+ continue;
1896
+ }
1897
+ packaged.push({
1898
+ artifactId: inputArtifact.id,
1899
+ transport: choice.transport,
1900
+ ...inputArtifact.mediaType !== void 0 ? { mediaType: inputArtifact.mediaType } : {},
1901
+ lineageTransform: "provider-packaging",
1902
+ warnings: choice.warnings
1903
+ });
1904
+ packagedArtifacts.push(toArtifactRef(artifact.derive({
1905
+ id: `${inputArtifact.id}:packaged:${route.providerId}:${route.modelId}`,
1906
+ kind: inputArtifact.kind,
1907
+ source: choice.transport === "provider-upload" ? "provider-upload" : "generated",
1908
+ parents: [inputArtifact],
1909
+ transform: {
1910
+ kind: "provider-packaging",
1911
+ name: `${route.providerId}:${choice.transport}`,
1912
+ metadata: {
1913
+ providerId: route.providerId,
1914
+ modelId: route.modelId,
1915
+ transport: choice.transport
1916
+ }
1917
+ },
1918
+ metadata: {
1919
+ providerId: route.providerId,
1920
+ modelId: route.modelId,
1921
+ transport: choice.transport
1922
+ },
1923
+ ...inputArtifact.mediaType !== void 0 ? { mediaType: inputArtifact.mediaType } : {},
1924
+ privacy: inputArtifact.privacy
1925
+ })));
1926
+ }
1927
+ return {
1928
+ plan: {
1929
+ providerId: route.providerId,
1930
+ modelId: route.modelId,
1931
+ artifacts: packaged,
1932
+ warnings
1933
+ },
1934
+ packagedArtifacts,
1935
+ blocked
1936
+ };
1937
+ }
1938
+ function chooseTransport(inputArtifact, supported, policy) {
1939
+ const warnings = [];
1940
+ const preferred = preferredTransports(inputArtifact);
1941
+ for (const transport of preferred) {
1942
+ if (!supported.includes(transport)) continue;
1943
+ if (policy?.noUpload === true && transport === "provider-upload") continue;
1944
+ if (policy?.noPublicUrl === true && transport === "url") continue;
1945
+ if (inputArtifact.privacy === "restricted" && (transport === "provider-upload" || transport === "url" || transport === "base64")) continue;
1946
+ if (transport === "base64") warnings.push(`Artifact ${inputArtifact.id} will be encoded as base64.`);
1947
+ return {
1948
+ transport,
1949
+ warnings
1950
+ };
1951
+ }
1952
+ return {
1953
+ transport: "inline",
1954
+ warnings,
1955
+ blocked: `No policy-safe transport for artifact ${inputArtifact.id}.`
1956
+ };
1957
+ }
1958
+ function preferredTransports(inputArtifact) {
1959
+ switch (inputArtifact.kind) {
1960
+ case "text": return ["inline", "extracted-text"];
1961
+ case "json":
1962
+ case "tool-result": return ["json", "inline"];
1963
+ case "url": return ["url", "inline"];
1964
+ case "document": return [
1965
+ "extracted-text",
1966
+ "provider-upload",
1967
+ "base64",
1968
+ "url"
1969
+ ];
1970
+ case "audio": return [
1971
+ "transcript",
1972
+ "provider-upload",
1973
+ "base64",
1974
+ "url"
1975
+ ];
1976
+ case "image":
1977
+ case "file":
1978
+ case "video": return [
1979
+ "provider-upload",
1980
+ "base64",
1981
+ "url"
1982
+ ];
1983
+ }
1984
+ }
1985
+ //#endregion
1986
+ //#region src/routing/router.ts
1987
+ function routeDeterministically(catalog, request) {
1988
+ const requiredInputs = requiredInputModalities(request.artifacts);
1989
+ const requiredOutputs = requiredOutputModalities(request.outputs);
1990
+ const requiresStructuredOutput = outputRequiresStructuredOutput(request.outputs);
1991
+ const estimatedInputTokens = estimateTokens(request.task) + request.artifacts.reduce((total, artifact) => total + estimateArtifactTokens(artifact), 0);
1992
+ const candidates = catalog.models.map((capability, index) => evaluateCapability(capability, {
1993
+ requiredInputs,
1994
+ requiredOutputs,
1995
+ requiresStructuredOutput,
1996
+ estimatedInputTokens,
1997
+ ...request.policy !== void 0 ? { policy: request.policy } : {},
1998
+ ...request.provider !== void 0 ? { provider: request.provider } : {},
1999
+ ...request.model !== void 0 ? { model: request.model } : {},
2000
+ ...request.contract !== void 0 ? { contract: request.contract } : {},
2001
+ index
2002
+ })).sort(compareCandidates);
2003
+ const accepted = candidates.filter((candidate) => candidate.accepted);
2004
+ const selected = accepted[0];
2005
+ return {
2006
+ catalogVersion: catalog.version,
2007
+ ...selected !== void 0 ? { selected: {
2008
+ providerId: selected.providerId,
2009
+ modelId: selected.modelId,
2010
+ score: selected.score,
2011
+ estimates: selected.estimates,
2012
+ inputModalities: selected.capability.inputModalities,
2013
+ outputModalities: selected.capability.outputModalities,
2014
+ fileTransport: selected.capability.fileTransport
2015
+ } } : {},
2016
+ candidates,
2017
+ rejected: candidates.filter((candidate) => !candidate.accepted),
2018
+ fallbackChain: accepted.slice(1).map((candidate) => ({
2019
+ providerId: candidate.providerId,
2020
+ modelId: candidate.modelId,
2021
+ score: candidate.score,
2022
+ reason: "policy-preserving-fallback"
2023
+ })),
2024
+ noRouteReasons: selected === void 0 ? summarizeNoRouteReasons(candidates) : []
2025
+ };
2026
+ }
2027
+ function evaluateCapability(capability, input) {
2028
+ const reasons = [];
2029
+ if (capability.available === false) reasons.push({
2030
+ code: "provider-unavailable",
2031
+ message: `${capability.providerId}/${capability.modelId} is not available.`
2032
+ });
2033
+ if (input.provider !== void 0 && capability.providerId !== input.provider) reasons.push({
2034
+ code: "provider-forced-mismatch",
2035
+ message: `Provider override requires ${input.provider}.`
2036
+ });
2037
+ if (input.model !== void 0 && capability.modelId !== input.model) reasons.push({
2038
+ code: "model-forced-mismatch",
2039
+ message: `Model override requires ${input.model}.`
2040
+ });
2041
+ for (const modality of input.requiredInputs) if (!capability.inputModalities.includes(modality)) reasons.push({
2042
+ code: "input-modality-unsupported",
2043
+ message: `${capability.modelId} does not support ${modality} input.`
2044
+ });
2045
+ for (const modality of input.requiredOutputs) if (!capability.outputModalities.includes(modality)) reasons.push({
2046
+ code: "output-modality-unsupported",
2047
+ message: `${capability.modelId} does not support ${modality} output.`
2048
+ });
2049
+ if (input.requiresStructuredOutput && !capability.structuredOutput) reasons.push({
2050
+ code: "structured-output-unsupported",
2051
+ message: `${capability.modelId} does not support structured output contracts.`
2052
+ });
2053
+ if (input.estimatedInputTokens > capability.contextWindow) reasons.push({
2054
+ code: "context-window-exceeded",
2055
+ message: `Estimated input ${input.estimatedInputTokens} tokens exceeds ${capability.contextWindow}.`
2056
+ });
2057
+ const estimates = estimateRoute(capability, input.estimatedInputTokens);
2058
+ addPolicyRejectReasons(reasons, capability, estimates, input.policy);
2059
+ const contractResult = evaluateContractAgainstRoute(input.contract, {
2060
+ capability,
2061
+ estimatedInputTokens: input.estimatedInputTokens,
2062
+ estimatedOutputTokens: estimates.outputTokens
2063
+ });
2064
+ for (const reason of contractResult.reasons) reasons.push(reason);
2065
+ const score = scoreCapability(capability, estimates, input.index);
2066
+ return {
2067
+ providerId: capability.providerId,
2068
+ modelId: capability.modelId,
2069
+ capability,
2070
+ score,
2071
+ accepted: reasons.length === 0,
2072
+ reasons,
2073
+ estimates
2074
+ };
2075
+ }
2076
+ function addPolicyRejectReasons(reasons, capability, estimates, policy) {
2077
+ if (policy === void 0) return;
2078
+ if (policy.providerAllowList !== void 0 && !policy.providerAllowList.includes(capability.providerId)) reasons.push({
2079
+ code: "provider-not-allowed",
2080
+ message: `${capability.providerId} is not in the provider allow list.`
2081
+ });
2082
+ if (policy.providerDenyList?.includes(capability.providerId) === true) reasons.push({
2083
+ code: "provider-denied",
2084
+ message: `${capability.providerId} is in the provider deny list.`
2085
+ });
2086
+ if (policy.privacy !== void 0 && !capability.dataPolicy.privacy.includes(policy.privacy)) reasons.push({
2087
+ code: "privacy-unsupported",
2088
+ message: `${capability.modelId} does not satisfy ${policy.privacy} privacy.`
2089
+ });
2090
+ if (policy.noLogging === true && capability.dataPolicy.supportsNoLogging !== true) reasons.push({
2091
+ code: "no-logging-unsupported",
2092
+ message: `${capability.modelId} cannot satisfy noLogging.`
2093
+ });
2094
+ if (policy.noUpload === true && capability.fileTransport.length > 0 && capability.fileTransport.every((transport) => transport === "provider-upload")) reasons.push({
2095
+ code: "no-upload-violated",
2096
+ message: `${capability.modelId} requires an upload transport disallowed by policy.`
2097
+ });
2098
+ if (policy.latency !== void 0 && capability.latency !== policy.latency) reasons.push({
2099
+ code: "latency-class-mismatch",
2100
+ message: `${capability.modelId} latency class is ${capability.latency}, not ${policy.latency}.`
2101
+ });
2102
+ if (policy.maxCostUsd !== void 0 && estimates.costUsd !== void 0 && estimates.costUsd > policy.maxCostUsd) reasons.push({
2103
+ code: "budget-exceeded",
2104
+ message: `${capability.modelId} estimated cost ${estimates.costUsd} exceeds maxCostUsd ${policy.maxCostUsd}.`
2105
+ });
2106
+ }
2107
+ function estimateRoute(capability, inputTokens) {
2108
+ const outputTokens = 512;
2109
+ const inputCost = capability.pricing?.inputCostPer1M === void 0 ? void 0 : inputTokens / 1e6 * capability.pricing.inputCostPer1M;
2110
+ const outputCost = capability.pricing?.outputCostPer1M === void 0 ? void 0 : outputTokens / 1e6 * capability.pricing.outputCostPer1M;
2111
+ return {
2112
+ inputTokens,
2113
+ outputTokens,
2114
+ ...inputCost !== void 0 || outputCost !== void 0 ? { costUsd: (inputCost ?? 0) + (outputCost ?? 0) } : {},
2115
+ latencyMs: capability.latency === "interactive" ? 1e3 : 1e4
2116
+ };
2117
+ }
2118
+ function scoreCapability(capability, estimates, index) {
2119
+ const costScore = Math.round((estimates.costUsd ?? 0) * 1e6);
2120
+ const latencyScore = capability.latency === "interactive" ? 0 : 1e5;
2121
+ const contextHeadroom = Math.max(0, capability.contextWindow - estimates.inputTokens);
2122
+ const contextScore = Math.max(0, 1e4 - Math.min(contextHeadroom, 1e4));
2123
+ return costScore + latencyScore + contextScore + index;
2124
+ }
2125
+ function compareCandidates(left, right) {
2126
+ if (left.accepted !== right.accepted) return left.accepted ? -1 : 1;
2127
+ if (left.score !== right.score) return left.score - right.score;
2128
+ const provider = left.providerId.localeCompare(right.providerId);
2129
+ return provider === 0 ? left.modelId.localeCompare(right.modelId) : provider;
2130
+ }
2131
+ function requiredInputModalities(artifacts) {
2132
+ const modalities = new Set(["text"]);
2133
+ for (const artifact of artifacts) switch (artifact.kind) {
2134
+ case "text":
2135
+ modalities.add("text");
2136
+ break;
2137
+ case "json":
2138
+ modalities.add("json");
2139
+ break;
2140
+ case "image":
2141
+ modalities.add("image");
2142
+ break;
2143
+ case "audio":
2144
+ modalities.add("audio");
2145
+ break;
2146
+ case "video":
2147
+ modalities.add("video");
2148
+ break;
2149
+ case "document":
2150
+ modalities.add("document");
2151
+ break;
2152
+ case "url":
2153
+ modalities.add("url");
2154
+ break;
2155
+ case "tool-result":
2156
+ modalities.add("tool");
2157
+ break;
2158
+ case "file":
2159
+ modalities.add("file");
2160
+ break;
2161
+ }
2162
+ return [...modalities];
2163
+ }
2164
+ function requiredOutputModalities(outputs) {
2165
+ const modalities = /* @__PURE__ */ new Set();
2166
+ for (const contract of Object.values(outputs)) {
2167
+ if (contract === "text") {
2168
+ modalities.add("text");
2169
+ continue;
2170
+ }
2171
+ if (isStructuredContract(contract)) {
2172
+ modalities.add("json");
2173
+ continue;
2174
+ }
2175
+ if (isReferenceContract(contract)) modalities.add("json");
2176
+ }
2177
+ return [...modalities];
2178
+ }
2179
+ function outputRequiresStructuredOutput(outputs) {
2180
+ return Object.values(outputs).some(isStructuredContract);
2181
+ }
2182
+ function isStructuredContract(contract) {
2183
+ return typeof contract === "object" && contract !== null && "~standard" in contract;
2184
+ }
2185
+ function isReferenceContract(contract) {
2186
+ return typeof contract === "object" && contract !== null && "kind" in contract && (contract.kind === "citations" || contract.kind === "artifacts");
2187
+ }
2188
+ function summarizeNoRouteReasons(candidates) {
2189
+ if (candidates.length === 0) return [{
2190
+ code: "catalog-empty",
2191
+ message: "No provider capabilities are configured."
2192
+ }];
2193
+ const unique = /* @__PURE__ */ new Map();
2194
+ for (const candidate of candidates) for (const reason of candidate.reasons) unique.set(reason.code, reason);
2195
+ return [...unique.values()];
2196
+ }
2197
+ //#endregion
2198
+ //#region src/storage/fingerprint.ts
2199
+ const textEncoder = new TextEncoder();
2200
+ async function fingerprintArtifactValue(value) {
2201
+ const bytes = await valueToBytes(value);
2202
+ if (bytes === void 0) return;
2203
+ const digest = await crypto.subtle.digest("SHA-256", toArrayBuffer(bytes));
2204
+ return {
2205
+ algorithm: "sha256",
2206
+ value: toHex(new Uint8Array(digest))
2207
+ };
2208
+ }
2209
+ async function valueToBytes(value) {
2210
+ if (typeof value === "string") return textEncoder.encode(value);
2211
+ if (value instanceof Uint8Array) return value;
2212
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
2213
+ if (isBlobLike$1(value)) return new Uint8Array(await value.arrayBuffer());
2214
+ const serialized = JSON.stringify(value);
2215
+ return serialized === void 0 ? void 0 : textEncoder.encode(serialized);
2216
+ }
2217
+ function isBlobLike$1(value) {
2218
+ return typeof Blob !== "undefined" && value instanceof Blob;
2219
+ }
2220
+ function toHex(bytes) {
2221
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
2222
+ }
2223
+ function toArrayBuffer(bytes) {
2224
+ const copy = new Uint8Array(bytes.byteLength);
2225
+ copy.set(bytes);
2226
+ return copy.buffer;
2227
+ }
2228
+ //#endregion
2229
+ //#region src/tracing/tracing.ts
2230
+ function createRunEvent(kind, input) {
2231
+ return {
2232
+ kind,
2233
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2234
+ ...input
2235
+ };
2236
+ }
2237
+ //#endregion
2238
+ //#region src/runtime/config.ts
2239
+ function normalizeConfig(config = {}) {
2240
+ const normalized = {
2241
+ providers: normalizeProviders(config.providers),
2242
+ defaults: config.defaults ?? {},
2243
+ events: normalizeEventSinks(config.events)
2244
+ };
2245
+ if (config.storage !== void 0 && config.storage !== false) normalized.storage = config.storage;
2246
+ if (config.sessions !== void 0 && config.sessions !== false) normalized.sessions = config.sessions;
2247
+ if (config.tracing !== void 0 && config.tracing !== false) normalized.tracing = config.tracing;
2248
+ if (config.signer !== void 0) normalized.signer = config.signer;
2249
+ return normalized;
2250
+ }
2251
+ function normalizeEventSinks(events) {
2252
+ if (events === void 0) return [];
2253
+ return typeof events === "function" ? [events] : events;
2254
+ }
2255
+ function normalizeProviders(providers = []) {
2256
+ return providers.map((provider) => {
2257
+ if (typeof provider === "string") return {
2258
+ id: provider,
2259
+ kind: "provider-ref"
2260
+ };
2261
+ return provider;
2262
+ });
2263
+ }
2264
+ //#endregion
2265
+ //#region src/runtime/create-ai.ts
2266
+ const ZERO_USAGE = {
2267
+ promptTokens: 0,
2268
+ completionTokens: 0,
2269
+ costUsd: 0
2270
+ };
2271
+ const UNMEASURED_USAGE = {
2272
+ promptTokens: 0,
2273
+ completionTokens: 0,
2274
+ costUsd: null
2275
+ };
2276
+ function createAI(config = {}) {
2277
+ const normalized = normalizeConfig(config);
2278
+ return {
2279
+ session(id) {
2280
+ return {
2281
+ id,
2282
+ kind: "session-ref"
2283
+ };
2284
+ },
2285
+ async plan(intent) {
2286
+ return (await buildPlan(normalized, intent)).plan;
2287
+ },
2288
+ run(intent) {
2289
+ return runWithConfig(normalized, intent);
2290
+ },
2291
+ runAgent(intent) {
2292
+ return import("./runtime-CfZ-sGGk.js").then((n) => n.n).then((mod) => mod.runAgent(intent, config));
2293
+ }
2294
+ };
2295
+ }
2296
+ async function runWithConfig(normalized, intent) {
2297
+ if (intent.signal?.aborted === true) throw new DOMException("Run aborted before execution.", "AbortError");
2298
+ const runId = createRunId();
2299
+ const events = [];
2300
+ await emitEvent(normalized, events, createRunEvent("run.start", { runId }));
2301
+ const built = await buildPlan(normalized, intent, runId, events);
2302
+ let plan = built.plan;
2303
+ const selected = plan.route.selected;
2304
+ if (selected === void 0) {
2305
+ const isContractFailure = plan.route.noRouteReasons.filter((r) => r.code === "contract-budget-exceeded" || r.code === "contract-quality-floor" || r.code === "contract-modality-missing" || r.code === "contract-privacy-mismatch").length > 0;
2306
+ const receipt = await maybeIssueReceipt(normalized, {
2307
+ runId,
2308
+ ...intent.contract !== void 0 ? { contract: intent.contract } : {},
2309
+ artifacts: intent.artifacts ?? [],
2310
+ contractVerdict: isContractFailure ? "no-contract-match" : "execution-failed",
2311
+ model: {
2312
+ requested: intent.overrides?.model ?? "",
2313
+ observed: null
2314
+ },
2315
+ route: {
2316
+ providerId: "",
2317
+ capabilityId: "",
2318
+ attemptNumber: 0
2319
+ },
2320
+ usage: ZERO_USAGE,
2321
+ ...isContractFailure ? { noRouteReasons: plan.route.noRouteReasons } : {}
2322
+ });
2323
+ const failure = isContractFailure ? {
2324
+ ok: false,
2325
+ error: {
2326
+ kind: "no-contract-match",
2327
+ message: "No route satisfies the contract.",
2328
+ noRouteReasons: plan.route.noRouteReasons
2329
+ },
2330
+ usage: { ...ZERO_USAGE },
2331
+ plan,
2332
+ events,
2333
+ ...receipt !== void 0 ? { receipt } : {}
2334
+ } : {
2335
+ ok: false,
2336
+ error: {
2337
+ kind: "no_route",
2338
+ message: "No route satisfied the run requirements.",
2339
+ reasons: plan.route.noRouteReasons.map((reason) => reason.message)
2340
+ },
2341
+ usage: { ...ZERO_USAGE },
2342
+ plan,
2343
+ events,
2344
+ ...receipt !== void 0 ? { receipt } : {}
2345
+ };
2346
+ await emitEvent(normalized, events, createRunEvent("run.failed", {
2347
+ runId,
2348
+ planId: plan.id,
2349
+ metadata: { reason: isContractFailure ? "no-contract-match" : "no-route" }
2350
+ }));
2351
+ return failure;
2352
+ }
2353
+ const routes = [selected, ...plan.route.fallbackChain.map((fallback) => routeFromCandidate(plan, fallback.providerId, fallback.modelId) ?? {
2354
+ providerId: fallback.providerId,
2355
+ modelId: fallback.modelId,
2356
+ score: fallback.score,
2357
+ estimates: selected.estimates,
2358
+ inputModalities: selected.inputModalities,
2359
+ outputModalities: selected.outputModalities,
2360
+ fileTransport: selected.fileTransport
2361
+ })];
2362
+ const attempts = [];
2363
+ let lastError;
2364
+ let anyExecutableAdapter = false;
2365
+ for (const [index, route] of routes.entries()) {
2366
+ const adapter = findExecutableAdapter(normalized, route.providerId);
2367
+ if (adapter === void 0) {
2368
+ lastError = /* @__PURE__ */ new Error("No Phase 1 provider adapter with execute() is configured.");
2369
+ continue;
2370
+ }
2371
+ anyExecutableAdapter = true;
2372
+ if (index > 0) await emitEvent(normalized, events, createRunEvent("fallback.activated", {
2373
+ runId,
2374
+ planId: plan.id,
2375
+ providerId: route.providerId,
2376
+ modelId: route.modelId
2377
+ }));
2378
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
2379
+ const attemptPackaging = packageArtifactsForProvider({
2380
+ artifacts: built.artifacts,
2381
+ route,
2382
+ ...built.mergedPolicy !== void 0 ? { policy: built.mergedPolicy } : {}
2383
+ });
2384
+ if (attemptPackaging.blocked.length > 0) {
2385
+ const message = attemptPackaging.blocked.join("; ");
2386
+ attempts.push(attemptFailed(route.providerId, route.modelId, startedAt, (/* @__PURE__ */ new Date()).toISOString(), message));
2387
+ lastError = new Error(message);
2388
+ continue;
2389
+ }
2390
+ const request = {
2391
+ task: intent.task,
2392
+ artifacts: built.artifacts,
2393
+ outputs: Object.keys(intent.outputs),
2394
+ outputContracts: intent.outputs,
2395
+ ...built.mergedPolicy !== void 0 ? { policy: built.mergedPolicy } : {},
2396
+ ...intent.signal !== void 0 ? { signal: intent.signal } : {},
2397
+ plan,
2398
+ contextPack: built.contextPack,
2399
+ providerPackaging: attemptPackaging.plan,
2400
+ packagedArtifacts: attemptPackaging.packagedArtifacts
2401
+ };
2402
+ try {
2403
+ await emitEvent(normalized, events, createRunEvent("provider.attempt", {
2404
+ runId,
2405
+ planId: plan.id,
2406
+ providerId: route.providerId,
2407
+ modelId: route.modelId,
2408
+ metadata: {
2409
+ status: "started",
2410
+ fallback: index > 0
2411
+ }
2412
+ }));
2413
+ await intent.overrides?.hooks?.beforeProviderCall?.({
2414
+ plan,
2415
+ request
2416
+ });
2417
+ plan = withPlanStatus(plan, "running", {
2418
+ stages: markStage(plan.stages, "execution", "running"),
2419
+ attempts: [...attempts, {
2420
+ providerId: route.providerId,
2421
+ modelId: route.modelId,
2422
+ status: "running",
2423
+ startedAt
2424
+ }]
2425
+ });
2426
+ const response = await adapter.execute(request);
2427
+ await intent.overrides?.hooks?.afterProviderCall?.({
2428
+ plan,
2429
+ response
2430
+ });
2431
+ const completedAt = (/* @__PURE__ */ new Date()).toISOString();
2432
+ const validation = await validateOutputMap(intent.outputs, response.rawOutputs, plan);
2433
+ const succeededAttempt = attemptSucceeded(route.providerId, route.modelId, startedAt, completedAt, response.usage);
2434
+ if (!validation.ok) {
2435
+ attempts.push({
2436
+ ...succeededAttempt,
2437
+ status: "failed",
2438
+ error: validation.error.message
2439
+ });
2440
+ const failedPlan = withPlanStatus(plan, "failed", {
2441
+ stages: markStage(plan.stages, "validation", "failed"),
2442
+ attempts
2443
+ });
2444
+ await emitEvent(normalized, events, createRunEvent("validation.failed", {
2445
+ runId,
2446
+ planId: plan.id,
2447
+ providerId: route.providerId,
2448
+ modelId: route.modelId,
2449
+ metadata: { error: validation.error.message }
2450
+ }));
2451
+ if (index === routes.length - 1) {
2452
+ const receipt = await maybeIssueReceipt(normalized, {
2453
+ runId,
2454
+ ...intent.contract !== void 0 ? { contract: intent.contract } : {},
2455
+ artifacts: built.artifacts,
2456
+ contractVerdict: "validation-failed",
2457
+ model: {
2458
+ requested: route.modelId,
2459
+ observed: null
2460
+ },
2461
+ route: {
2462
+ providerId: route.providerId,
2463
+ capabilityId: route.modelId,
2464
+ attemptNumber: attempts.length
2465
+ },
2466
+ usage: normalizeAdapterUsage(response)
2467
+ });
2468
+ return {
2469
+ ...validation,
2470
+ usage: normalizeAdapterUsage(response),
2471
+ plan: failedPlan,
2472
+ events,
2473
+ ...receipt !== void 0 ? { receipt } : {}
2474
+ };
2475
+ }
2476
+ lastError = new Error(validation.error.message);
2477
+ continue;
2478
+ }
2479
+ const invariants = intent.contract?.invariants ?? [];
2480
+ if (invariants.length > 0) {
2481
+ const tripwireResult = await evaluateTripwires(validation.outputs, invariants);
2482
+ if (!tripwireResult.ok) {
2483
+ const tripwireFailedAt = (/* @__PURE__ */ new Date()).toISOString();
2484
+ attempts.push({
2485
+ ...succeededAttempt,
2486
+ status: "failed",
2487
+ error: tripwireResult.evidence.message,
2488
+ completedAt: tripwireFailedAt
2489
+ });
2490
+ const failedPlan = withPlanStatus(plan, "failed", {
2491
+ stages: markStage(markStage(markStage(plan.stages, "execution", "completed"), "validation", "completed"), "tripwire", "failed", { invariantId: tripwireResult.evidence.invariantId }),
2492
+ attempts
2493
+ });
2494
+ await emitEvent(normalized, events, createRunEvent("run.failed", {
2495
+ runId,
2496
+ planId: failedPlan.id,
2497
+ providerId: route.providerId,
2498
+ modelId: route.modelId,
2499
+ metadata: {
2500
+ reason: "tripwire-violated",
2501
+ invariantId: tripwireResult.evidence.invariantId
2502
+ }
2503
+ }));
2504
+ const receipt = await maybeIssueReceipt(normalized, {
2505
+ runId,
2506
+ ...intent.contract !== void 0 ? { contract: intent.contract } : {},
2507
+ artifacts: built.artifacts,
2508
+ contractVerdict: "tripwire-violated",
2509
+ model: {
2510
+ requested: route.modelId,
2511
+ observed: null
2512
+ },
2513
+ route: {
2514
+ providerId: route.providerId,
2515
+ capabilityId: route.modelId,
2516
+ attemptNumber: attempts.length
2517
+ },
2518
+ usage: normalizeAdapterUsage(response),
2519
+ tripwireEvidence: tripwireResult.evidence
2520
+ });
2521
+ return {
2522
+ ok: false,
2523
+ error: {
2524
+ kind: "tripwire-violated",
2525
+ message: tripwireResult.evidence.message,
2526
+ invariantId: tripwireResult.evidence.invariantId,
2527
+ evidence: tripwireResult.evidence,
2528
+ terminal: true
2529
+ },
2530
+ usage: normalizeAdapterUsage(response),
2531
+ plan: failedPlan,
2532
+ events,
2533
+ ...receipt !== void 0 ? { receipt } : {}
2534
+ };
2535
+ }
2536
+ }
2537
+ attempts.push(succeededAttempt);
2538
+ const artifactRefs = response.artifactRefs !== void 0 ? response.artifactRefs.map(toArtifactRef) : [];
2539
+ const completedPlan = withPlanStatus(plan, "completed", {
2540
+ stages: markStage(markStage(markStage(markStage(markStage(plan.stages, "execution", "completed"), "validation", "completed"), "persistence", "completed"), "tool-execution", built.toolResults.length > 0 ? "completed" : "skipped"), "tripwire", invariants.length > 0 ? "completed" : "skipped"),
2541
+ attempts
2542
+ });
2543
+ if (built.sessionRecord !== void 0 && normalized.sessions !== void 0) await normalized.sessions.appendTurn({
2544
+ sessionId: built.sessionRecord.id,
2545
+ task: intent.task,
2546
+ artifactRefs: built.artifacts.map(toArtifactRef),
2547
+ outputArtifactRefs: artifactRefs,
2548
+ planId: completedPlan.id
2549
+ });
2550
+ await emitEvent(normalized, events, createRunEvent("validation.complete", {
2551
+ runId,
2552
+ planId: completedPlan.id,
2553
+ providerId: route.providerId,
2554
+ modelId: route.modelId
2555
+ }));
2556
+ await emitEvent(normalized, events, createRunEvent("run.complete", {
2557
+ runId,
2558
+ planId: completedPlan.id
2559
+ }));
2560
+ const successValidation = validation;
2561
+ const receipt = await maybeIssueReceipt(normalized, {
2562
+ runId,
2563
+ ...intent.contract !== void 0 ? { contract: intent.contract } : {},
2564
+ artifacts: built.artifacts,
2565
+ contractVerdict: "success",
2566
+ model: {
2567
+ requested: route.modelId,
2568
+ observed: null
2569
+ },
2570
+ route: {
2571
+ providerId: route.providerId,
2572
+ capabilityId: route.modelId,
2573
+ attemptNumber: attempts.length
2574
+ },
2575
+ usage: normalizeAdapterUsage(response),
2576
+ outputs: JSON.stringify(successValidation.outputs)
2577
+ });
2578
+ return {
2579
+ ...validation,
2580
+ artifacts: artifactRefs,
2581
+ usage: normalizeAdapterUsage(response),
2582
+ plan: completedPlan,
2583
+ events,
2584
+ ...receipt !== void 0 ? { receipt } : {}
2585
+ };
2586
+ } catch (error) {
2587
+ const completedAt = (/* @__PURE__ */ new Date()).toISOString();
2588
+ const message = error instanceof Error ? error.message : "Provider adapter execution failed.";
2589
+ attempts.push(attemptFailed(route.providerId, route.modelId, startedAt, completedAt, message));
2590
+ lastError = error instanceof Error ? error : new Error(message);
2591
+ await emitEvent(normalized, events, createRunEvent("provider.attempt", {
2592
+ runId,
2593
+ planId: plan.id,
2594
+ providerId: route.providerId,
2595
+ modelId: route.modelId,
2596
+ metadata: {
2597
+ status: "failed",
2598
+ error: message
2599
+ }
2600
+ }));
2601
+ }
2602
+ }
2603
+ if (!anyExecutableAdapter) {
2604
+ const receipt = await maybeIssueReceipt(normalized, {
2605
+ runId,
2606
+ ...intent.contract !== void 0 ? { contract: intent.contract } : {},
2607
+ artifacts: built.artifacts,
2608
+ contractVerdict: "execution-failed",
2609
+ model: {
2610
+ requested: selected.modelId,
2611
+ observed: null
2612
+ },
2613
+ route: {
2614
+ providerId: selected.providerId,
2615
+ capabilityId: selected.modelId,
2616
+ attemptNumber: 0
2617
+ },
2618
+ usage: ZERO_USAGE
2619
+ });
2620
+ return {
2621
+ ok: false,
2622
+ error: {
2623
+ kind: "execution_unavailable",
2624
+ message: "No Phase 1 provider adapter with execute() is configured."
2625
+ },
2626
+ usage: { ...ZERO_USAGE },
2627
+ plan,
2628
+ events,
2629
+ ...receipt !== void 0 ? { receipt } : {}
2630
+ };
2631
+ }
2632
+ const failedPlan = withPlanStatus(plan, "failed", {
2633
+ stages: markStage(plan.stages, "execution", "failed"),
2634
+ attempts
2635
+ });
2636
+ await emitEvent(normalized, events, createRunEvent("run.failed", {
2637
+ runId,
2638
+ planId: failedPlan.id,
2639
+ metadata: { error: lastError?.message ?? "Provider adapter execution failed." }
2640
+ }));
2641
+ const receipt = await maybeIssueReceipt(normalized, {
2642
+ runId,
2643
+ ...intent.contract !== void 0 ? { contract: intent.contract } : {},
2644
+ artifacts: built.artifacts,
2645
+ contractVerdict: "execution-failed",
2646
+ model: {
2647
+ requested: selected.modelId,
2648
+ observed: null
2649
+ },
2650
+ route: {
2651
+ providerId: selected.providerId,
2652
+ capabilityId: selected.modelId,
2653
+ attemptNumber: attempts.length
2654
+ },
2655
+ usage: UNMEASURED_USAGE
2656
+ });
2657
+ return {
2658
+ ok: false,
2659
+ error: {
2660
+ kind: "provider_execution",
2661
+ message: lastError?.message ?? "Provider adapter execution failed.",
2662
+ providerId: selected.providerId,
2663
+ modelId: selected.modelId
2664
+ },
2665
+ usage: { ...UNMEASURED_USAGE },
2666
+ plan: failedPlan,
2667
+ events,
2668
+ ...receipt !== void 0 ? { receipt } : {}
2669
+ };
2670
+ }
2671
+ async function buildPlan(normalized, intent, runId = createRunId(), events = []) {
2672
+ const prepared = await prepareArtifacts(intent);
2673
+ const artifacts = prepared.artifacts;
2674
+ const mergedPolicy = mergePolicy(mergePolicy(normalized.defaults.policy, intent.policy), intent.overrides?.routingPolicy);
2675
+ const sessionRecord = intent.session !== void 0 && normalized.sessions !== void 0 ? await loadOrCreateSession(normalized, intent.session) : void 0;
2676
+ const route = routeDeterministically(createCapabilityCatalog(normalized.providers), {
2677
+ task: intent.task,
2678
+ artifacts,
2679
+ outputs: intent.outputs,
2680
+ ...mergedPolicy !== void 0 ? { policy: mergedPolicy } : {},
2681
+ ...intent.overrides?.provider !== void 0 ? { provider: intent.overrides.provider } : {},
2682
+ ...intent.overrides?.model !== void 0 ? { model: intent.overrides.model } : {},
2683
+ ...intent.contract !== void 0 ? { contract: intent.contract } : {}
2684
+ });
2685
+ const contextPack = buildContextPack({
2686
+ task: intent.task,
2687
+ artifacts,
2688
+ ...route.selected !== void 0 ? { route: route.selected } : {},
2689
+ ...sessionRecord !== void 0 ? { session: sessionRecord } : {},
2690
+ ...intent.overrides?.tokenBudget !== void 0 ? { tokenBudget: intent.overrides.tokenBudget } : {}
2691
+ });
2692
+ const summaryRefs = contextPack.summarized.length > 0 && intent.overrides?.summarizer !== void 0 ? await intent.overrides.summarizer.summarize({
2693
+ artifacts: artifacts.map(toArtifactRef),
2694
+ budgetTokens: contextPack.tokenBudget
2695
+ }) : [];
2696
+ const packaging = packageArtifactsForProvider({
2697
+ artifacts,
2698
+ ...route.selected !== void 0 ? { route: route.selected } : {},
2699
+ ...mergedPolicy !== void 0 ? { policy: mergedPolicy } : {}
2700
+ });
2701
+ let plan = createExecutionPlan({
2702
+ task: intent.task,
2703
+ artifacts: artifacts.map(toArtifactRef),
2704
+ outputs: intent.outputs,
2705
+ route,
2706
+ context: contextPack,
2707
+ providerPackaging: packaging.plan,
2708
+ warnings: packaging.blocked,
2709
+ metadata: {
2710
+ ...intent.tools !== void 0 ? { tools: intent.tools.map((tool) => tool.name) } : {},
2711
+ ...summaryRefs.length > 0 ? { summaryArtifactIds: summaryRefs.map((summary) => summary.id) } : {}
2712
+ }
2713
+ });
2714
+ plan = withPlanStatus(plan, plan.status, { stages: markStage(plan.stages, "tool-execution", prepared.toolResults.length > 0 ? "completed" : "skipped", prepared.toolResults.length > 0 ? { toolNames: prepared.toolResults.map((result) => result.toolName) } : void 0) });
2715
+ for (const result of prepared.toolResults) {
2716
+ await emitEvent(normalized, events, createRunEvent("tool.call", {
2717
+ runId,
2718
+ planId: plan.id,
2719
+ artifactId: result.artifact.id,
2720
+ metadata: {
2721
+ toolName: result.toolName,
2722
+ callId: result.callId
2723
+ }
2724
+ }));
2725
+ await emitEvent(normalized, events, createRunEvent("artifact.created", {
2726
+ runId,
2727
+ planId: plan.id,
2728
+ artifactId: result.artifact.id,
2729
+ metadata: { source: "tool" }
2730
+ }));
2731
+ }
2732
+ for (const artifactRef of artifacts.map(toArtifactRef)) await emitEvent(normalized, events, createRunEvent("artifact.ingested", {
2733
+ runId,
2734
+ planId: plan.id,
2735
+ artifactId: artifactRef.id
2736
+ }));
2737
+ await emitEvent(normalized, events, createRunEvent("context.packed", {
2738
+ runId,
2739
+ planId: plan.id,
2740
+ metadata: {
2741
+ estimatedTokens: contextPack.estimatedTokens,
2742
+ included: contextPack.included.length,
2743
+ summarized: contextPack.summarized.length,
2744
+ omitted: contextPack.omitted.length
2745
+ }
2746
+ }));
2747
+ await emitEvent(normalized, events, createRunEvent("router.candidates", {
2748
+ runId,
2749
+ planId: plan.id,
2750
+ metadata: {
2751
+ selected: route.selected?.modelId,
2752
+ rejected: route.rejected.length,
2753
+ fallbacks: route.fallbackChain.length
2754
+ }
2755
+ }));
2756
+ return {
2757
+ plan,
2758
+ artifacts,
2759
+ contextPack,
2760
+ packagedArtifacts: packaging.packagedArtifacts,
2761
+ blockedPackaging: packaging.blocked,
2762
+ toolResults: prepared.toolResults,
2763
+ ...mergedPolicy !== void 0 ? { mergedPolicy } : {},
2764
+ ...sessionRecord !== void 0 ? { sessionRecord } : {}
2765
+ };
2766
+ }
2767
+ async function prepareArtifacts(intent) {
2768
+ let artifacts = [...intent.artifacts ?? []];
2769
+ for (const transform of intent.overrides?.transforms ?? []) {
2770
+ const transformed = await transform.transform({
2771
+ task: intent.task,
2772
+ artifacts
2773
+ });
2774
+ artifacts = artifacts.concat(Array.isArray(transformed) ? transformed : [transformed]);
2775
+ }
2776
+ const toolResults = [];
2777
+ for (const tool of intent.tools ?? []) {
2778
+ const result = await runTool(tool, intent.toolInputs?.[tool.name] ?? {});
2779
+ toolResults.push(result);
2780
+ artifacts.push(result.artifact);
2781
+ }
2782
+ return {
2783
+ artifacts,
2784
+ toolResults
2785
+ };
2786
+ }
2787
+ async function loadOrCreateSession(normalized, session) {
2788
+ const existing = await normalized.sessions?.load(session.id);
2789
+ if (existing !== void 0) return existing;
2790
+ if (normalized.sessions === void 0) throw new Error("Session storage is not configured.");
2791
+ return normalized.sessions.create({ id: session.id });
2792
+ }
2793
+ function attemptSucceeded(providerId, modelId, startedAt, completedAt, usage) {
2794
+ return {
2795
+ providerId,
2796
+ modelId,
2797
+ status: "succeeded",
2798
+ startedAt,
2799
+ completedAt,
2800
+ ...usage !== void 0 ? { usage } : {}
2801
+ };
2802
+ }
2803
+ function attemptFailed(providerId, modelId, startedAt, completedAt, error) {
2804
+ return {
2805
+ providerId,
2806
+ modelId,
2807
+ status: "failed",
2808
+ startedAt,
2809
+ completedAt,
2810
+ error
2811
+ };
2812
+ }
2813
+ function findExecutableAdapter(normalized, providerId) {
2814
+ return normalized.providers.find((provider) => provider.kind === "provider-adapter" && provider.id === providerId && typeof provider.execute === "function");
2815
+ }
2816
+ function routeFromCandidate(plan, providerId, modelId) {
2817
+ const candidate = plan.route.candidates.find((item) => item.providerId === providerId && item.modelId === modelId);
2818
+ if (candidate === void 0) return;
2819
+ return {
2820
+ providerId,
2821
+ modelId,
2822
+ score: candidate.score,
2823
+ estimates: candidate.estimates,
2824
+ inputModalities: candidate.capability.inputModalities,
2825
+ outputModalities: candidate.capability.outputModalities,
2826
+ fileTransport: candidate.capability.fileTransport
2827
+ };
2828
+ }
2829
+ async function emitEvent(normalized, events, event) {
2830
+ events.push(event);
2831
+ normalized.tracing?.event?.(event.kind, {
2832
+ ...event.metadata,
2833
+ planId: event.planId,
2834
+ providerId: event.providerId,
2835
+ modelId: event.modelId,
2836
+ artifactId: event.artifactId
2837
+ });
2838
+ await Promise.all(normalized.events.map((sink) => sink(event)));
2839
+ }
2840
+ function createRunId() {
2841
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return `run:${crypto.randomUUID()}`;
2842
+ return `run:${Date.now()}:${Math.random().toString(16).slice(2)}`;
2843
+ }
2844
+ /**
2845
+ * Normalize an adapter response into the `RunResult.usage` shape.
2846
+ *
2847
+ * Prefers `ProviderRunResponse.normalizedUsage` (the Phase 7 shape emitted by
2848
+ * openai / openai-compat / ai-sdk / fake adapters). Falls back to mapping the
2849
+ * legacy `UsageRecord` (inputTokens / outputTokens) so v1.0 adapters that have
2850
+ * not yet been re-rolled still surface a usable Usage value.
2851
+ */
2852
+ function normalizeAdapterUsage(response) {
2853
+ if (response.normalizedUsage !== void 0) return response.normalizedUsage;
2854
+ return {
2855
+ promptTokens: response.usage?.inputTokens ?? 0,
2856
+ completionTokens: response.usage?.outputTokens ?? 0,
2857
+ costUsd: response.usage?.costUsd ?? null
2858
+ };
2859
+ }
2860
+ /**
2861
+ * Phase 9 — hash each artifact's canonical value via SHA-256 and return the
2862
+ * hex digests in declaration order. Missing/undefined values produce an
2863
+ * empty string so the array length matches `artifacts.length` exactly.
2864
+ */
2865
+ async function hashInputArtifacts(artifacts) {
2866
+ const out = [];
2867
+ for (const artifact of artifacts) {
2868
+ const fp = await fingerprintArtifactValue(artifact.value);
2869
+ out.push(fp?.value ?? "");
2870
+ }
2871
+ return out;
2872
+ }
2873
+ /**
2874
+ * Phase 9 — SHA-256 hex of `canonicalize(contract)` for the receipt's
2875
+ * contractHash field. Returns null when no contract is attached or when
2876
+ * canonicalize cannot serialize the input.
2877
+ */
2878
+ async function sha256HexOfCanonicalContract(contract) {
2879
+ if (contract === void 0 || contract === null) return null;
2880
+ const canonical = canonicalize(contract);
2881
+ if (canonical === void 0) return null;
2882
+ const bytes = new TextEncoder().encode(canonical);
2883
+ const ab = new Uint8Array(bytes.byteLength);
2884
+ ab.set(bytes);
2885
+ const digest = await crypto.subtle.digest("SHA-256", ab.buffer);
2886
+ return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
2887
+ }
2888
+ /**
2889
+ * Phase 9 — issue a signed receipt at a terminal branch when a signer is
2890
+ * configured. Signer failures degrade gracefully to `undefined` so a faulty
2891
+ * signer never crashes `ai.run`.
2892
+ */
2893
+ async function maybeIssueReceipt(normalized, input) {
2894
+ if (normalized.signer === void 0) return void 0;
2895
+ try {
2896
+ const inputHashes = await hashInputArtifacts(input.artifacts);
2897
+ const outputHash = input.outputs === void 0 ? null : (await fingerprintArtifactValue(input.outputs))?.value ?? null;
2898
+ const contractHash = await sha256HexOfCanonicalContract(input.contract);
2899
+ return await createReceipt({
2900
+ runId: input.runId,
2901
+ model: input.model,
2902
+ route: input.route,
2903
+ usage: input.usage,
2904
+ contractVerdict: input.contractVerdict,
2905
+ contractHash,
2906
+ inputHashes,
2907
+ outputHash,
2908
+ ...input.noRouteReasons !== void 0 ? { noRouteReasons: input.noRouteReasons } : {},
2909
+ ...input.tripwireEvidence !== void 0 ? { tripwireEvidence: input.tripwireEvidence } : {}
2910
+ }, normalized.signer);
2911
+ } catch {
2912
+ return;
2913
+ }
2914
+ }
2915
+ //#endregion
2916
+ //#region src/sessions/session.ts
2917
+ function createMemorySessionStore(options = {}) {
2918
+ const storeId = options.id ?? "memory-sessions";
2919
+ const sessions = /* @__PURE__ */ new Map();
2920
+ return {
2921
+ kind: "session-store",
2922
+ id: storeId,
2923
+ async create(createOptions = {}) {
2924
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2925
+ const session = {
2926
+ id: createOptions.id ?? createSessionId(),
2927
+ kind: "session-ref",
2928
+ ...createOptions.parentId !== void 0 ? { parentId: createOptions.parentId } : {},
2929
+ ...createOptions.branchPointRunId !== void 0 ? { branchPointRunId: createOptions.branchPointRunId } : {},
2930
+ turns: [],
2931
+ summaries: [],
2932
+ artifactRefs: [],
2933
+ planIds: [],
2934
+ createdAt: now,
2935
+ updatedAt: now
2936
+ };
2937
+ sessions.set(session.id, clone(session));
2938
+ return clone(session);
2939
+ },
2940
+ async load(id) {
2941
+ const session = sessions.get(id);
2942
+ return session === void 0 ? void 0 : clone(session);
2943
+ },
2944
+ async save(session) {
2945
+ sessions.set(session.id, clone(session));
2946
+ return clone(session);
2947
+ },
2948
+ async branch(parentId, branchOptions = {}) {
2949
+ const parent = sessions.get(parentId);
2950
+ const branched = await this.create({
2951
+ ...branchOptions,
2952
+ parentId
2953
+ });
2954
+ if (parent === void 0) return branched;
2955
+ const inherited = {
2956
+ ...branched,
2957
+ turns: clone(parent.turns),
2958
+ summaries: clone(parent.summaries),
2959
+ artifactRefs: clone(parent.artifactRefs),
2960
+ planIds: clone(parent.planIds)
2961
+ };
2962
+ sessions.set(inherited.id, clone(inherited));
2963
+ return clone(inherited);
2964
+ },
2965
+ async appendTurn(input) {
2966
+ const existing = sessions.get(input.sessionId) ?? await this.create({ id: input.sessionId });
2967
+ const turn = {
2968
+ id: createTurnId(),
2969
+ task: input.task,
2970
+ artifactRefs: clone(input.artifactRefs),
2971
+ outputArtifactRefs: clone(input.outputArtifactRefs ?? []),
2972
+ ...input.planId !== void 0 ? { planId: input.planId } : {},
2973
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2974
+ };
2975
+ const artifactRefs = mergeArtifactRefs(existing.artifactRefs, input.artifactRefs, input.outputArtifactRefs ?? []);
2976
+ const planIds = input.planId === void 0 ? existing.planIds : [...existing.planIds, input.planId];
2977
+ const next = {
2978
+ ...existing,
2979
+ turns: [...existing.turns, turn],
2980
+ artifactRefs,
2981
+ planIds,
2982
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2983
+ };
2984
+ sessions.set(next.id, clone(next));
2985
+ return clone(next);
2986
+ }
2987
+ };
2988
+ }
2989
+ function mergeArtifactRefs(current, ...groups) {
2990
+ const byId = new Map(current.map((artifact) => [artifact.id, artifact]));
2991
+ for (const group of groups) for (const artifact of group) byId.set(artifact.id, artifact);
2992
+ return [...byId.values()];
2993
+ }
2994
+ function createSessionId() {
2995
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return `session:${crypto.randomUUID()}`;
2996
+ return `session:${Date.now()}:${Math.random().toString(16).slice(2)}`;
2997
+ }
2998
+ function createTurnId() {
2999
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return `turn:${crypto.randomUUID()}`;
3000
+ return `turn:${Date.now()}:${Math.random().toString(16).slice(2)}`;
3001
+ }
3002
+ function clone(value) {
3003
+ try {
3004
+ return structuredClone(value);
3005
+ } catch {
3006
+ return value;
3007
+ }
3008
+ }
3009
+ //#endregion
3010
+ //#region src/storage/local.ts
3011
+ function createLocalArtifactStore(rootDir, options = {}) {
3012
+ const rootPath = rootDir instanceof URL ? fileURLToPath(rootDir) : rootDir;
3013
+ const storeId = options.id ?? "local";
3014
+ return {
3015
+ kind: "artifact-store",
3016
+ id: storeId,
3017
+ async put(artifact) {
3018
+ const artifactDir = artifactDirectory(rootPath, artifact.id);
3019
+ const fingerprint = artifact.fingerprint ?? await fingerprintArtifactValue(artifact.value);
3020
+ const ref = toArtifactRef({
3021
+ ...artifact,
3022
+ storage: {
3023
+ storeId,
3024
+ key: artifact.id
3025
+ },
3026
+ ...fingerprint !== void 0 ? { fingerprint } : {}
3027
+ });
3028
+ await rm(artifactDir, {
3029
+ recursive: true,
3030
+ force: true
3031
+ });
3032
+ await mkdir(artifactDir, { recursive: true });
3033
+ const payload = await writePayload(artifactDir, artifact.value);
3034
+ const envelope = {
3035
+ version: 1,
3036
+ ref,
3037
+ ...payload !== void 0 ? { payload } : {}
3038
+ };
3039
+ await writeFile(metadataPath(rootPath, artifact.id), `${JSON.stringify(envelope, null, 2)}\n`, "utf8");
3040
+ return ref;
3041
+ },
3042
+ async get(id) {
3043
+ return (await readEnvelope(rootPath, id))?.ref;
3044
+ },
3045
+ async load(id) {
3046
+ const envelope = await readEnvelope(rootPath, id);
3047
+ if (envelope === void 0) return;
3048
+ if (envelope.payload === void 0) return envelope.ref;
3049
+ const value = await readPayload(artifactDirectory(rootPath, id), envelope.payload);
3050
+ return {
3051
+ ...envelope.ref,
3052
+ value
3053
+ };
3054
+ },
3055
+ async has(id) {
3056
+ try {
3057
+ await stat(metadataPath(rootPath, id));
3058
+ return true;
3059
+ } catch (error) {
3060
+ if (isNotFoundError(error)) return false;
3061
+ throw error;
3062
+ }
3063
+ },
3064
+ async delete(id) {
3065
+ if (!await this.has(id)) return false;
3066
+ await rm(artifactDirectory(rootPath, id), {
3067
+ recursive: true,
3068
+ force: true
3069
+ });
3070
+ return true;
3071
+ },
3072
+ async list() {
3073
+ const artifactsPath = join(rootPath, "artifacts");
3074
+ let entries;
3075
+ try {
3076
+ entries = await readdir(artifactsPath, { withFileTypes: true });
3077
+ } catch (error) {
3078
+ if (isNotFoundError(error)) return [];
3079
+ throw error;
3080
+ }
3081
+ return (await Promise.all(entries.filter((entry) => entry.isDirectory()).map(async (entry) => {
3082
+ return (await readEnvelopeByDirectory(join(artifactsPath, entry.name))).ref;
3083
+ }))).sort((left, right) => left.id.localeCompare(right.id));
3084
+ }
3085
+ };
3086
+ }
3087
+ async function writePayload(artifactDir, value) {
3088
+ if (value === void 0) return;
3089
+ if (value instanceof Uint8Array || value instanceof ArrayBuffer || isBlobLike(value)) {
3090
+ await writeFile(join(artifactDir, "payload.bin"), await toBinaryPayload(value));
3091
+ return {
3092
+ kind: "binary",
3093
+ path: "payload.bin"
3094
+ };
3095
+ }
3096
+ const serialized = JSON.stringify(value, null, 2);
3097
+ if (serialized === void 0) return;
3098
+ await writeFile(join(artifactDir, "payload.json"), `${serialized}\n`, "utf8");
3099
+ return {
3100
+ kind: "json",
3101
+ path: "payload.json"
3102
+ };
3103
+ }
3104
+ async function readPayload(artifactDir, payload) {
3105
+ const payloadPath = join(artifactDir, payload.path);
3106
+ if (payload.kind === "binary") {
3107
+ const bytes = await readFile(payloadPath);
3108
+ return new Uint8Array(bytes);
3109
+ }
3110
+ return JSON.parse(await readFile(payloadPath, "utf8"));
3111
+ }
3112
+ async function readEnvelope(rootPath, id) {
3113
+ try {
3114
+ return JSON.parse(await readFile(metadataPath(rootPath, id), "utf8"));
3115
+ } catch (error) {
3116
+ if (isNotFoundError(error)) return;
3117
+ throw error;
3118
+ }
3119
+ }
3120
+ async function readEnvelopeByDirectory(artifactDir) {
3121
+ return JSON.parse(await readFile(join(artifactDir, "metadata.json"), "utf8"));
3122
+ }
3123
+ async function toBinaryPayload(value) {
3124
+ if (value instanceof Uint8Array) return value;
3125
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
3126
+ return new Uint8Array(await value.arrayBuffer());
3127
+ }
3128
+ function artifactDirectory(rootPath, id) {
3129
+ return join(rootPath, "artifacts", encodeURIComponent(id));
3130
+ }
3131
+ function metadataPath(rootPath, id) {
3132
+ return join(artifactDirectory(rootPath, id), "metadata.json");
3133
+ }
3134
+ function isBlobLike(value) {
3135
+ return typeof Blob !== "undefined" && value instanceof Blob;
3136
+ }
3137
+ function isNotFoundError(error) {
3138
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
3139
+ }
3140
+ //#endregion
3141
+ //#region src/storage/memory.ts
3142
+ function createMemoryArtifactStore(options = {}) {
3143
+ const storeId = options.id ?? "memory";
3144
+ const artifacts = /* @__PURE__ */ new Map();
3145
+ return {
3146
+ kind: "artifact-store",
3147
+ id: storeId,
3148
+ async put(artifact) {
3149
+ const fingerprint = artifact.fingerprint ?? await fingerprintArtifactValue(artifact.value);
3150
+ const storedArtifact = {
3151
+ ...cloneArtifactInput(artifact),
3152
+ storage: {
3153
+ storeId,
3154
+ key: artifact.id
3155
+ },
3156
+ ...fingerprint !== void 0 ? { fingerprint } : {}
3157
+ };
3158
+ const ref = toArtifactRef(storedArtifact);
3159
+ artifacts.set(artifact.id, {
3160
+ ref: cloneArtifactRef(ref),
3161
+ artifact: cloneArtifactInput(storedArtifact)
3162
+ });
3163
+ return cloneArtifactRef(ref);
3164
+ },
3165
+ async get(id) {
3166
+ const record = artifacts.get(id);
3167
+ return record === void 0 ? void 0 : cloneArtifactRef(record.ref);
3168
+ },
3169
+ async load(id) {
3170
+ const record = artifacts.get(id);
3171
+ return record === void 0 ? void 0 : cloneArtifactInput(record.artifact);
3172
+ },
3173
+ async has(id) {
3174
+ return artifacts.has(id);
3175
+ },
3176
+ async delete(id) {
3177
+ return artifacts.delete(id);
3178
+ },
3179
+ async list() {
3180
+ return Array.from(artifacts.values(), (record) => cloneArtifactRef(record.ref));
3181
+ }
3182
+ };
3183
+ }
3184
+ function cloneArtifactInput(artifact) {
3185
+ return cloneValue(artifact);
3186
+ }
3187
+ function cloneArtifactRef(ref) {
3188
+ return cloneValue(ref);
3189
+ }
3190
+ function cloneValue(value) {
3191
+ try {
3192
+ return structuredClone(value);
3193
+ } catch {
3194
+ return value;
3195
+ }
3196
+ }
3197
+ //#endregion
3198
+ export { AgentDeniedError, BAND, DEFAULT_CHECKPOINT_BAND, STEP_TRANSITION_EVENT_NAME, STUCK_REASONS, artifact, contract, createAI, createAISdkProvider, createActionHistory, createAnthropicProvider, createCheckpointHook, createCostTracker, createFakeProvider, createGeminiProvider, createGoalProgressTracker, createHookPipeline, createInMemorySigner, createLmStudioProvider, createLocalArtifactStore, createMemoryArtifactStore, createMemoryKeySet, createMemorySessionStore, createNoopAgentHost, createNoopSurvivabilityAdapter, createOpenAICompatibleProvider, createOpenAIProvider, createOpenRouterProvider, createPermissionContext, createPermissionGuardHook, createReceipt, createReplayEnvelope, createTranscriptStore, createXaiProvider, defaultPiiDetectors, defineTool, estimateRouteCost, evalAgentRun, evaluateContractAgainstRoute, evaluateTripwires, formatToolsForProvider, generateEd25519KeyPairJwk, importMcpTools, inv, isTerminal, latticeVersion, materializeReplayEnvelope, output, permissionGuardRegisterOptions, redactArtifactRef, redactPlan, redactReplayEnvelope, replayOffline, rerunLive, runAgent, runTool, toolArtifactRef, toolSchemaToJsonSchema, verifyReceipt };
3199
+
3200
+ //# sourceMappingURL=index.js.map