@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.
@@ -0,0 +1,1359 @@
1
+ import mime from "mime";
2
+ import canonicalize from "canonicalize";
3
+ //#region \0rolldown/runtime.js
4
+ var __defProp = Object.defineProperty;
5
+ var __exportAll = (all, no_symbols) => {
6
+ let target = {};
7
+ for (var name in all) __defProp(target, name, {
8
+ get: all[name],
9
+ enumerable: true
10
+ });
11
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
12
+ return target;
13
+ };
14
+ //#endregion
15
+ //#region src/artifacts/metadata.ts
16
+ const textEncoder$1 = new TextEncoder();
17
+ function inferMediaType(value, options) {
18
+ if (options.mediaType !== void 0) return options.mediaType;
19
+ if (isBlobLike(value) && value.type !== "") return value.type;
20
+ if (typeof value === "string") return mime.getType(value) ?? options.defaultMediaType;
21
+ return options.defaultMediaType;
22
+ }
23
+ function measureArtifactValue(value, kind) {
24
+ if (kind === "text" && typeof value === "string") return measureString(value);
25
+ if (kind === "json") {
26
+ const serialized = JSON.stringify(value);
27
+ return serialized === void 0 ? void 0 : measureString(serialized);
28
+ }
29
+ if (isBlobLike(value)) return { bytes: value.size };
30
+ }
31
+ function measureString(value) {
32
+ return {
33
+ characters: value.length,
34
+ bytes: textEncoder$1.encode(value).byteLength
35
+ };
36
+ }
37
+ function isBlobLike(value) {
38
+ return typeof Blob !== "undefined" && value instanceof Blob;
39
+ }
40
+ //#endregion
41
+ //#region src/artifacts/artifact.ts
42
+ const artifact = {
43
+ text(value, options = {}) {
44
+ return createArtifact("text", "inline", value, options, "text/plain");
45
+ },
46
+ json(value, options = {}) {
47
+ return createArtifact("json", "inline", value, options, "application/json");
48
+ },
49
+ file(value, options = {}) {
50
+ return createArtifact("file", "file", value, options);
51
+ },
52
+ image(value, options = {}) {
53
+ return createArtifact("image", "file", value, options);
54
+ },
55
+ audio(value, options = {}) {
56
+ return createArtifact("audio", "file", value, options);
57
+ },
58
+ document(value, options = {}) {
59
+ return createArtifact("document", "file", value, options);
60
+ },
61
+ url(value, options = {}) {
62
+ return createArtifact("url", "url", value.toString(), options);
63
+ },
64
+ toolResult(value, options) {
65
+ return createArtifact("tool-result", "tool", value, {
66
+ ...options,
67
+ metadata: {
68
+ ...options.metadata,
69
+ toolName: options.toolName,
70
+ ...options.callId !== void 0 ? { callId: options.callId } : {}
71
+ }
72
+ }, "application/json");
73
+ },
74
+ derive(input) {
75
+ const { kind, source = "generated", value, parents, transform, ...options } = input;
76
+ return createArtifact(kind, source, value, {
77
+ ...options,
78
+ lineage: {
79
+ parents: parents.map(toArtifactRef),
80
+ transform
81
+ }
82
+ }, defaultMediaTypeForKind(kind));
83
+ }
84
+ };
85
+ function toArtifactRef(input) {
86
+ return {
87
+ id: input.id,
88
+ kind: input.kind,
89
+ source: input.source,
90
+ privacy: input.privacy,
91
+ ...input.mediaType !== void 0 ? { mediaType: input.mediaType } : {},
92
+ ...input.label !== void 0 ? { label: input.label } : {},
93
+ ...input.metadata !== void 0 ? { metadata: input.metadata } : {},
94
+ ...input.size !== void 0 ? { size: input.size } : {},
95
+ ...input.fingerprint !== void 0 ? { fingerprint: input.fingerprint } : {},
96
+ ...input.storage !== void 0 ? { storage: input.storage } : {},
97
+ ...input.lineage !== void 0 ? { lineage: input.lineage } : {}
98
+ };
99
+ }
100
+ function isArtifactRef(value) {
101
+ if (!isRecord(value)) return false;
102
+ return typeof value.id === "string" && isArtifactKind(value.kind) && isArtifactSource(value.source) && isArtifactPrivacy(value.privacy);
103
+ }
104
+ function createArtifact(kind, source, value, options, defaultMediaType) {
105
+ const mediaType = inferMediaType(value, {
106
+ kind,
107
+ ...options.mediaType !== void 0 ? { mediaType: options.mediaType } : {},
108
+ ...defaultMediaType !== void 0 ? { defaultMediaType } : {}
109
+ });
110
+ const size = options.size ?? measureArtifactValue(value, kind);
111
+ return {
112
+ id: options.id ?? createArtifactId(kind),
113
+ kind,
114
+ source,
115
+ value,
116
+ privacy: options.privacy ?? "standard",
117
+ ...mediaType !== void 0 ? { mediaType } : {},
118
+ ...options.label !== void 0 ? { label: options.label } : {},
119
+ ...options.metadata !== void 0 ? { metadata: options.metadata } : {},
120
+ ...size !== void 0 ? { size } : {},
121
+ ...options.fingerprint !== void 0 ? { fingerprint: options.fingerprint } : {},
122
+ ...options.storage !== void 0 ? { storage: options.storage } : {},
123
+ ...options.lineage !== void 0 ? { lineage: options.lineage } : {}
124
+ };
125
+ }
126
+ function defaultMediaTypeForKind(kind) {
127
+ switch (kind) {
128
+ case "text": return "text/plain";
129
+ case "json":
130
+ case "tool-result": return "application/json";
131
+ default: return;
132
+ }
133
+ }
134
+ function isArtifactKind(value) {
135
+ return value === "text" || value === "json" || value === "file" || value === "image" || value === "audio" || value === "video" || value === "document" || value === "url" || value === "tool-result";
136
+ }
137
+ function isArtifactSource(value) {
138
+ return value === "inline" || value === "file" || value === "url" || value === "generated" || value === `provider-upload` || value === "tool";
139
+ }
140
+ function isArtifactPrivacy(value) {
141
+ return value === "standard" || value === "sensitive" || value === "restricted";
142
+ }
143
+ function isRecord(value) {
144
+ return typeof value === "object" && value !== null;
145
+ }
146
+ function createArtifactId(kind) {
147
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return `artifact:${kind}:${crypto.randomUUID()}`;
148
+ return `artifact:${kind}:${Date.now()}:${Math.random().toString(16).slice(2)}`;
149
+ }
150
+ //#endregion
151
+ //#region src/contract/bands.ts
152
+ /**
153
+ * Priority bands. Lower number = higher priority (runs first).
154
+ *
155
+ * SAFETY (0) -- safety / breaker hooks; cannot be overridden by lower bands
156
+ * OBSERVABILITY (1) -- logging, metrics, audit; runs after safety, before extension
157
+ * EXTENSION (2) -- user-supplied hooks; runs last
158
+ *
159
+ * Within a band, handlers run in registration order.
160
+ */
161
+ const BAND = {
162
+ SAFETY: 0,
163
+ OBSERVABILITY: 1,
164
+ EXTENSION: 2
165
+ };
166
+ const BAND_ORDER = [
167
+ BAND.SAFETY,
168
+ BAND.OBSERVABILITY,
169
+ BAND.EXTENSION
170
+ ];
171
+ const PIPELINE_FROZEN_ERROR_NAME = "PIPELINE_FROZEN";
172
+ const HOOK_TIMEOUT_EVENT_NAME = "HOOK_TIMEOUT";
173
+ function freezeContext(ctx) {
174
+ let cloned;
175
+ try {
176
+ cloned = structuredClone(ctx);
177
+ } catch {
178
+ cloned = ctx;
179
+ }
180
+ if (typeof cloned === "object" && cloned !== null) Object.freeze(cloned);
181
+ return cloned;
182
+ }
183
+ async function runHandlerWithBudget(record, ctx, controls, emit, event, sessionId) {
184
+ const startedAt = performance.now();
185
+ let timeoutFired = false;
186
+ const budgetMs = record.budgetMs;
187
+ const budgetPromise = new Promise((resolve) => {
188
+ setTimeout(() => {
189
+ timeoutFired = true;
190
+ resolve("__timeout__");
191
+ }, budgetMs);
192
+ });
193
+ const handlerPromise = (async () => {
194
+ try {
195
+ await record.handler(ctx, controls);
196
+ } catch {}
197
+ return "__done__";
198
+ })();
199
+ if (await Promise.race([handlerPromise, budgetPromise]) === "__timeout__" && timeoutFired) {
200
+ const elapsedMs = Math.round(performance.now() - startedAt);
201
+ if (emit !== void 0) emit(HOOK_TIMEOUT_EVENT_NAME, {
202
+ event,
203
+ band: record.band,
204
+ budgetMs,
205
+ ...sessionId !== void 0 ? { sessionId } : {},
206
+ handlerIndex: record.registrationIndex,
207
+ elapsedMs
208
+ });
209
+ }
210
+ }
211
+ /**
212
+ * Factory: build a fresh hook pipeline.
213
+ */
214
+ function createHookPipeline(options) {
215
+ const tracer = options?.tracer;
216
+ const sessionId = options?.sessionId;
217
+ const defaultBudgetMs = options?.defaultBudgetMs ?? 100;
218
+ const registry = /* @__PURE__ */ new Map();
219
+ let frozen = false;
220
+ let globalRegistrationCounter = 0;
221
+ let currentDenialReason = null;
222
+ const emit = tracer !== void 0 ? (kind, payload) => {
223
+ tracer.event?.(kind, payload);
224
+ } : void 0;
225
+ function register(event, handler, opts) {
226
+ if (frozen) {
227
+ const err = /* @__PURE__ */ new Error("HookPipeline.register() called after freeze()");
228
+ err.name = PIPELINE_FROZEN_ERROR_NAME;
229
+ throw err;
230
+ }
231
+ let perEventBands = registry.get(event);
232
+ if (perEventBands === void 0) {
233
+ perEventBands = /* @__PURE__ */ new Map();
234
+ registry.set(event, perEventBands);
235
+ }
236
+ let arr = perEventBands.get(opts.band);
237
+ if (arr === void 0) {
238
+ arr = [];
239
+ perEventBands.set(opts.band, arr);
240
+ }
241
+ const record = {
242
+ handler,
243
+ ...opts.matcher !== void 0 ? { matcher: opts.matcher } : {},
244
+ budgetMs: opts.budgetMs ?? defaultBudgetMs,
245
+ band: opts.band,
246
+ registrationIndex: globalRegistrationCounter
247
+ };
248
+ globalRegistrationCounter += 1;
249
+ arr.push(record);
250
+ }
251
+ function freezePipeline() {
252
+ frozen = true;
253
+ }
254
+ function isFrozen() {
255
+ return frozen;
256
+ }
257
+ async function run(event, context) {
258
+ currentDenialReason = null;
259
+ const perEventBands = registry.get(event);
260
+ if (perEventBands === void 0) return;
261
+ const controls = { deny: (reason) => {
262
+ currentDenialReason = reason;
263
+ } };
264
+ for (const band of BAND_ORDER) {
265
+ const arr = perEventBands.get(band);
266
+ if (arr === void 0 || arr.length === 0) continue;
267
+ for (const record of arr) {
268
+ if (record.matcher !== void 0 && !record.matcher.test(event)) continue;
269
+ await runHandlerWithBudget(record, freezeContext(context), controls, emit, event, sessionId);
270
+ }
271
+ }
272
+ }
273
+ function lastDenialReason() {
274
+ return currentDenialReason;
275
+ }
276
+ return {
277
+ kind: "hook-pipeline",
278
+ register,
279
+ freeze: freezePipeline,
280
+ isFrozen,
281
+ run,
282
+ lastDenialReason
283
+ };
284
+ }
285
+ //#endregion
286
+ //#region src/receipts/canonical.ts
287
+ const encoder = new TextEncoder();
288
+ /**
289
+ * Convert costUsd (number | null) to its canonical string form.
290
+ * RFC 8785 requires deterministic float-to-string; using JS Number→string
291
+ * directly is unsafe across V8 versions (Grisu3 vs Dragonbox). We pin the
292
+ * format by routing through Number.prototype.toString() for FINITE numbers
293
+ * only, and treat NaN/Infinity as null. This matches "I-JSON only" from
294
+ * 09-CONTEXT.md — receipts NEVER carry non-finite floats.
295
+ */
296
+ function stringifyCostUsd(costUsd) {
297
+ if (costUsd === null) return null;
298
+ if (!Number.isFinite(costUsd)) return null;
299
+ return costUsd.toString();
300
+ }
301
+ /**
302
+ * Convert a runtime Usage (number costUsd) to its canonical receipt form
303
+ * (string costUsd). This is the single conversion site — canonical bytes
304
+ * NEVER see a raw float in the cost field.
305
+ */
306
+ function usageToCanonical(usage) {
307
+ return {
308
+ promptTokens: usage.promptTokens,
309
+ completionTokens: usage.completionTokens,
310
+ costUsd: stringifyCostUsd(usage.costUsd)
311
+ };
312
+ }
313
+ /**
314
+ * Canonicalize a receipt body to JCS bytes (RFC 8785).
315
+ *
316
+ * INVARIANT: callers MUST pass an already-redacted body. The redactor in
317
+ * redact.ts produces the input to this function — never the cleartext.
318
+ * See 09-CONTEXT.md "Redact-Then-Sign Ordering (UNRETROFITTABLE)".
319
+ *
320
+ * Throws if canonicalize returns undefined (impossible for valid bodies
321
+ * — surfaces a programmer error rather than silently producing zero
322
+ * bytes that would later fail signature verification).
323
+ */
324
+ function canonicalizeReceiptBody(body) {
325
+ const json = canonicalize(body);
326
+ if (json === void 0) throw new Error("canonicalizeReceiptBody: canonicalize returned undefined; receipt body contained a non-canonicalizable value (function/symbol/undefined).");
327
+ return encoder.encode(json);
328
+ }
329
+ //#endregion
330
+ //#region src/receipts/envelope.ts
331
+ const PAYLOAD_TYPE = "application/vnd.lattice.receipt+json";
332
+ const textEncoder = new TextEncoder();
333
+ function base64Encode(bytes) {
334
+ return Buffer.from(bytes).toString("base64");
335
+ }
336
+ function base64Decode(value) {
337
+ return new Uint8Array(Buffer.from(value, "base64"));
338
+ }
339
+ /**
340
+ * DSSE v1.0 Pre-Authentication Encoding.
341
+ *
342
+ * Reference: https://github.com/secure-systems-lab/dsse/blob/v1.0.0/protocol.md
343
+ *
344
+ * PAE = UTF-8("DSSEv1 " + len(payloadType) + " " + payloadType
345
+ * + " " + len(payload) + " " + payload)
346
+ *
347
+ * `payload` here is the BASE64-encoded string per DSSE v1.0 spec (NOT raw
348
+ * canonical bytes). Both signing and verification MUST construct PAE the
349
+ * same way; this module is the single source of truth.
350
+ *
351
+ * ASCII length is decimal (no zero-padding). e.g. length 1000 → "1000".
352
+ */
353
+ function buildPae(payloadType, payloadBase64) {
354
+ const ascii = "DSSEv1 " + payloadType.length.toString() + " " + payloadType + " " + payloadBase64.length.toString() + " " + payloadBase64;
355
+ return textEncoder.encode(ascii);
356
+ }
357
+ function encodeEnvelope(input) {
358
+ return {
359
+ payloadType: PAYLOAD_TYPE,
360
+ payload: base64Encode(input.payloadBytes),
361
+ signatures: input.signatures.map((entry) => ({
362
+ keyid: entry.keyid,
363
+ sig: base64Encode(entry.sig)
364
+ }))
365
+ };
366
+ }
367
+ function decodeEnvelope(envelope) {
368
+ if (envelope.payloadType !== "application/vnd.lattice.receipt+json") throw new Error(`envelope payloadType mismatch: expected "${PAYLOAD_TYPE}" got "${envelope.payloadType}"`);
369
+ return {
370
+ payloadType: envelope.payloadType,
371
+ payloadBytes: base64Decode(envelope.payload),
372
+ signatures: envelope.signatures.map((entry) => ({
373
+ keyid: entry.keyid,
374
+ sig: base64Decode(entry.sig)
375
+ }))
376
+ };
377
+ }
378
+ //#endregion
379
+ //#region src/receipts/redact.ts
380
+ /**
381
+ * Default redaction policy id for v1.1. Free-form string per
382
+ * 09-CONTEXT.md — registry enforcement deferred to v1.2.
383
+ */
384
+ const DEFAULT_REDACTION_POLICY_ID = "lattice.default.v1";
385
+ /**
386
+ * Redact a receipt body BEFORE canonicalization (and BEFORE signing).
387
+ *
388
+ * The signed digest commits to canonicalize(redact(body)). NEVER the
389
+ * other way around. See 09-CONTEXT.md "Redact-Then-Sign Ordering
390
+ * (UNRETROFITTABLE)" and PITFALLS.md Pitfall #1.
391
+ *
392
+ * For v1.1 the default policy is minimal — the heavy lifting already
393
+ * happened upstream:
394
+ * - Tripwire evaluator emits {detector, substring} for no-pii (T-08-01).
395
+ * - Provider responses are hashed into inputHashes/outputHash, never
396
+ * embedded raw.
397
+ * - Router reject messages do not contain PII by construction.
398
+ *
399
+ * This function therefore primarily:
400
+ * 1. Materializes the redactions[] manifest declaring what WAS elided
401
+ * upstream (so receipts are self-describing).
402
+ * 2. Provides the extension point future policies will use.
403
+ *
404
+ * Returns a NEW body — never mutates the input.
405
+ */
406
+ function redactReceiptBody(body, policyId = DEFAULT_REDACTION_POLICY_ID) {
407
+ const redactions = [];
408
+ if (body.tripwireEvidence !== void 0 && body.tripwireEvidence.kind === "no-pii") redactions.push({
409
+ path: "tripwireEvidence.observed",
410
+ reason: "no-pii-detector-substring-only"
411
+ });
412
+ const sorted = [...redactions].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
413
+ return {
414
+ body: {
415
+ ...body,
416
+ redactionPolicyId: policyId,
417
+ redactions: sorted
418
+ },
419
+ redactions: sorted
420
+ };
421
+ }
422
+ //#endregion
423
+ //#region src/receipts/receipt.ts
424
+ /**
425
+ * Build, redact, canonicalize, sign, and envelope a CapabilityReceipt.
426
+ *
427
+ * Ordering INVARIANT (09-CONTEXT.md, PITFALLS.md Pitfall #1):
428
+ * redact -> canonicalize -> PAE -> sign -> encode
429
+ *
430
+ * The signed digest commits to canonicalize(redact(body)). The function
431
+ * structure makes any other ordering impossible to write by accident —
432
+ * canonicalizeReceiptBody is ONLY called on the output of redactReceiptBody.
433
+ *
434
+ * Defense in depth:
435
+ * - body.kid is assigned from signer.kid, never from input (input has no
436
+ * kid field). The signed body and the envelope keyid CANNOT disagree by
437
+ * construction.
438
+ * - signer.kid is also written to envelope.signatures[0].keyid, so the
439
+ * verifier can cross-check (Step 7 of verifyReceipt).
440
+ *
441
+ * I-JSON guarantees: usage.costUsd is converted to string (or null) via
442
+ * usageToCanonical. Receipts NEVER carry raw floats in the canonical form.
443
+ */
444
+ async function createReceipt(input, signer) {
445
+ const policyId = input.redactionPolicyId ?? "lattice.default.v1";
446
+ const receiptId = input.receiptId ?? crypto.randomUUID();
447
+ const issuedAt = input.issuedAt ?? (/* @__PURE__ */ new Date()).toISOString();
448
+ const { body } = redactReceiptBody({
449
+ version: "lattice-receipt/v1.1",
450
+ receiptId,
451
+ runId: input.runId,
452
+ issuedAt,
453
+ kid: signer.kid,
454
+ model: input.model,
455
+ route: input.route,
456
+ usage: usageToCanonical(input.usage),
457
+ contractVerdict: input.contractVerdict,
458
+ contractHash: input.contractHash,
459
+ inputHashes: input.inputHashes,
460
+ outputHash: input.outputHash,
461
+ redactionPolicyId: policyId,
462
+ redactions: [],
463
+ ...input.noRouteReasons !== void 0 ? { noRouteReasons: input.noRouteReasons } : {},
464
+ ...input.tripwireEvidence !== void 0 ? { tripwireEvidence: input.tripwireEvidence } : {},
465
+ ...input.stepName !== void 0 ? { stepName: input.stepName } : {},
466
+ ...input.stepIndex !== void 0 ? { stepIndex: input.stepIndex } : {},
467
+ ...input.parentStepName !== void 0 ? { parentStepName: input.parentStepName } : {},
468
+ ...input.previousStepName !== void 0 ? { previousStepName: input.previousStepName } : {},
469
+ ...input.sessionId !== void 0 ? { sessionId: input.sessionId } : {},
470
+ ...input.timestamp !== void 0 ? { timestamp: input.timestamp } : {}
471
+ }, policyId);
472
+ const payloadBytes = canonicalizeReceiptBody(body);
473
+ const pae = buildPae(PAYLOAD_TYPE, base64Encode(payloadBytes));
474
+ const sig = await signer.sign(pae);
475
+ return encodeEnvelope({
476
+ payloadBytes,
477
+ signatures: [{
478
+ keyid: signer.kid,
479
+ sig
480
+ }]
481
+ });
482
+ }
483
+ //#endregion
484
+ //#region src/contract/checkpoint.ts
485
+ /**
486
+ * The tracer event name Lattice's checkpoint hook emits per step transition.
487
+ * Identical to the literal added to RunEventKind in tracing.ts.
488
+ */
489
+ const STEP_TRANSITION_EVENT_NAME = "step.transition";
490
+ /**
491
+ * Default band convention for the checkpoint hook (D-06). The caller is
492
+ * free to register in a different band but the documented convention is
493
+ * OBSERVABILITY -- between SAFETY (runs first) and EXTENSION (runs last).
494
+ */
495
+ const DEFAULT_CHECKPOINT_BAND = BAND.OBSERVABILITY;
496
+ const DEFAULT_MODEL = {
497
+ requested: "lattice-checkpoint/observability",
498
+ observed: null
499
+ };
500
+ const DEFAULT_ROUTE = {
501
+ providerId: "lattice-checkpoint",
502
+ capabilityId: "lattice-checkpoint/step-transition",
503
+ attemptNumber: 1
504
+ };
505
+ const DEFAULT_USAGE = {
506
+ promptTokens: 0,
507
+ completionTokens: 0,
508
+ costUsd: null
509
+ };
510
+ /**
511
+ * Build a checkpoint hook handler.
512
+ *
513
+ * The returned handler is suitable for registration on a HookPipeline
514
+ * created via createHookPipeline (see ./bands.ts). The handler does not
515
+ * auto-register; the caller passes it to pipeline.register(...) with the
516
+ * desired lifecycle event (typically AFTER_TOOL or BEFORE_TOOL) and band
517
+ * (typically BAND.OBSERVABILITY, exported as DEFAULT_CHECKPOINT_BAND).
518
+ *
519
+ * Per-invocation behavior:
520
+ * 1. Build event metadata from options + per-call context.
521
+ * 2. If signer is configured, attempt createReceipt(...) inside a
522
+ * try/catch. On success, set metadata.receiptId + metadata.envelope.
523
+ * On failure, set metadata.mintError (string from caught error).
524
+ * 3. If tracer is configured, emit exactly one tracer.event?.(
525
+ * STEP_TRANSITION_EVENT_NAME, metadata) call.
526
+ * 4. Return void.
527
+ *
528
+ * NO upstream throw (D-07). NO global mutation (D-05).
529
+ */
530
+ function createCheckpointHook(options) {
531
+ const runId = options.runId;
532
+ const tracer = options.tracer;
533
+ const signer = options.signer;
534
+ const sessionId = options.sessionId;
535
+ const model = options.model ?? DEFAULT_MODEL;
536
+ const route = options.route ?? DEFAULT_ROUTE;
537
+ const contractVerdict = options.contractVerdict ?? "success";
538
+ return async function checkpointHookHandler(ctx) {
539
+ const baseMetadata = {
540
+ runId,
541
+ stepName: ctx.stepName,
542
+ stepIndex: ctx.stepIndex,
543
+ timestamp: ctx.timestamp,
544
+ ...ctx.parentStepName !== void 0 ? { parentStepName: ctx.parentStepName } : {},
545
+ ...ctx.previousStepName !== void 0 ? { previousStepName: ctx.previousStepName } : {},
546
+ ...sessionId !== void 0 ? { sessionId } : {}
547
+ };
548
+ let envelope;
549
+ let receiptId;
550
+ let mintError;
551
+ if (signer !== void 0) try {
552
+ envelope = await createReceipt({
553
+ runId,
554
+ model,
555
+ route,
556
+ usage: DEFAULT_USAGE,
557
+ contractVerdict,
558
+ contractHash: null,
559
+ inputHashes: [],
560
+ outputHash: null,
561
+ stepName: ctx.stepName,
562
+ stepIndex: ctx.stepIndex,
563
+ timestamp: ctx.timestamp,
564
+ ...ctx.parentStepName !== void 0 ? { parentStepName: ctx.parentStepName } : {},
565
+ ...ctx.previousStepName !== void 0 ? { previousStepName: ctx.previousStepName } : {},
566
+ ...sessionId !== void 0 ? { sessionId } : {}
567
+ }, signer);
568
+ receiptId = extractReceiptId(envelope);
569
+ } catch (err) {
570
+ mintError = err instanceof Error ? err.message : String(err);
571
+ }
572
+ const metadata = {
573
+ ...baseMetadata,
574
+ ...receiptId !== void 0 ? { receiptId } : {},
575
+ ...envelope !== void 0 ? { envelope } : {},
576
+ ...mintError !== void 0 ? { mintError } : {}
577
+ };
578
+ tracer?.event?.(STEP_TRANSITION_EVENT_NAME, metadata);
579
+ };
580
+ }
581
+ /**
582
+ * Decode the canonical payload of a freshly-minted envelope and return
583
+ * its receiptId. The envelope's payload is base64-encoded JSON of the
584
+ * signed body (DSSE v1.0 form); receiptId is a top-level field.
585
+ *
586
+ * Returns undefined if decoding fails (defensive -- the handler still
587
+ * emits the tracer event with the envelope itself so subscribers can
588
+ * re-derive the id if they want).
589
+ */
590
+ function extractReceiptId(envelope) {
591
+ try {
592
+ const bytes = Uint8Array.from(atob(envelope.payload), (c) => c.charCodeAt(0));
593
+ const body = JSON.parse(new TextDecoder().decode(bytes));
594
+ return typeof body.receiptId === "string" ? body.receiptId : void 0;
595
+ } catch {
596
+ return;
597
+ }
598
+ }
599
+ //#endregion
600
+ //#region src/runtime/survivability.ts
601
+ /**
602
+ * Reference implementation of SurvivabilityAdapter<TState>. Records
603
+ * eviction events but does NOT persist; serialize / deserialize round-
604
+ * trip via JSON.stringify / JSON.parse. Analog to createFakeProvider
605
+ * in the providers/ module -- gives Lattice's vitest a complete shape-
606
+ * conformance target before the real (FSB-side) adapter ships in
607
+ * Plan 05-05.
608
+ *
609
+ * Per CONTEXT.md D-11 the noop adapter ships in Lattice (not FSB)
610
+ * because it covers the contract surface in Lattice's own test suite;
611
+ * FSB's real chrome.storage.session-backed adapter is glue layer.
612
+ */
613
+ function createNoopSurvivabilityAdapter(options = {}) {
614
+ const id = options.id ?? "noop-survivability";
615
+ const defaultPolicy = options.policy ?? "SAFE";
616
+ const hooks = /* @__PURE__ */ new Set();
617
+ return {
618
+ kind: "survivability-adapter",
619
+ id,
620
+ serialize(state) {
621
+ return {
622
+ kind: "survivability-snapshot",
623
+ version: "lattice-survivability/v1",
624
+ payload: JSON.stringify(state ?? null),
625
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString()
626
+ };
627
+ },
628
+ deserialize(snapshot) {
629
+ return JSON.parse(snapshot.payload);
630
+ },
631
+ onEviction(hook) {
632
+ hooks.add(hook);
633
+ let unsubscribed = false;
634
+ return () => {
635
+ if (unsubscribed) return;
636
+ unsubscribed = true;
637
+ hooks.delete(hook);
638
+ };
639
+ },
640
+ async resume(_snapshot) {
641
+ return defaultPolicy;
642
+ }
643
+ };
644
+ }
645
+ //#endregion
646
+ //#region src/outputs/validate.ts
647
+ async function validateSchemaOutput(name, schema, value) {
648
+ const result = schema["~standard"].validate(value);
649
+ const validation = result instanceof Promise ? await result : result;
650
+ if (validation.issues) return {
651
+ ok: false,
652
+ issue: {
653
+ ["output"]: name,
654
+ issues: validation.issues.map(normalizeIssue)
655
+ }
656
+ };
657
+ return {
658
+ ok: true,
659
+ value: validation.value
660
+ };
661
+ }
662
+ async function validateOutputMap(contracts, rawOutputs, plan) {
663
+ const outputs = {};
664
+ for (const [name, contract] of Object.entries(contracts)) {
665
+ const value = rawOutputs[name];
666
+ const issue = await validateOutput(name, contract, value);
667
+ if (!issue.ok) return {
668
+ ok: false,
669
+ error: {
670
+ kind: "validation",
671
+ message: `Invalid output "${name}".`,
672
+ ["output"]: name,
673
+ issues: issue.issues
674
+ },
675
+ usage: {
676
+ promptTokens: 0,
677
+ completionTokens: 0,
678
+ costUsd: null
679
+ },
680
+ raw: rawOutputs,
681
+ partialOutputs: outputs,
682
+ plan
683
+ };
684
+ outputs[name] = issue.value;
685
+ }
686
+ return {
687
+ ok: true,
688
+ outputs,
689
+ artifacts: [],
690
+ usage: {
691
+ promptTokens: 0,
692
+ completionTokens: 0,
693
+ costUsd: null
694
+ },
695
+ plan
696
+ };
697
+ }
698
+ async function validateOutput(name, contract, value) {
699
+ if (contract === "text") {
700
+ if (typeof value !== "string") return {
701
+ ok: false,
702
+ issues: [{ message: "Expected text output to be a string." }]
703
+ };
704
+ return {
705
+ ok: true,
706
+ value
707
+ };
708
+ }
709
+ if (isStandardSchema(contract)) {
710
+ const result = await validateSchemaOutput(name, contract, value);
711
+ if (!result.ok) return {
712
+ ok: false,
713
+ issues: result.issue.issues
714
+ };
715
+ return {
716
+ ok: true,
717
+ value: result.value
718
+ };
719
+ }
720
+ if (contract.kind === "citations") {
721
+ if (!Array.isArray(value)) return {
722
+ ok: false,
723
+ issues: [{ message: "Expected citations output to be an array." }]
724
+ };
725
+ return {
726
+ ok: true,
727
+ value
728
+ };
729
+ }
730
+ if (contract.kind === "artifacts") {
731
+ if (!Array.isArray(value)) return {
732
+ ok: false,
733
+ issues: [{ message: "Expected artifacts output to be an array." }]
734
+ };
735
+ for (const item of value) {
736
+ if (!isArtifactRef(item)) return {
737
+ ok: false,
738
+ issues: [{ message: "Expected artifacts output item to be an artifact ref." }]
739
+ };
740
+ if (contract.artifactKind !== void 0 && item.kind !== contract.artifactKind) return {
741
+ ok: false,
742
+ issues: [{ message: `Expected artifacts output item kind to be "${contract.artifactKind}".` }]
743
+ };
744
+ }
745
+ return {
746
+ ok: true,
747
+ value: value.map(toArtifactRef)
748
+ };
749
+ }
750
+ return {
751
+ ok: false,
752
+ issues: [{ message: "Unsupported output contract." }]
753
+ };
754
+ }
755
+ function isStandardSchema(contract) {
756
+ if (typeof contract !== "object" || contract === null) return false;
757
+ return typeof contract["~standard"]?.validate === "function";
758
+ }
759
+ function normalizeIssue(issue) {
760
+ const path = issue.path?.map(normalizePathSegment).filter((segment) => segment !== void 0);
761
+ return {
762
+ message: issue.message,
763
+ ...path !== void 0 && path.length > 0 ? { path } : {}
764
+ };
765
+ }
766
+ function normalizePathSegment(segment) {
767
+ if (typeof segment === "string" || typeof segment === "number" || typeof segment === "symbol") return segment;
768
+ return normalizePathKey(segment.key);
769
+ }
770
+ function normalizePathKey(key) {
771
+ return key;
772
+ }
773
+ //#endregion
774
+ //#region src/tools/tools.ts
775
+ function defineTool(definition) {
776
+ return {
777
+ kind: "tool",
778
+ ...definition
779
+ };
780
+ }
781
+ async function runTool(tool, input, context = {}) {
782
+ const validation = await validateSchemaOutput(tool.name, tool.inputSchema, input);
783
+ if (!validation.ok) throw new Error(`Invalid input for tool "${tool.name}".`);
784
+ const callId = createToolCallId();
785
+ const output = await tool.execute(validation.value, context);
786
+ return {
787
+ callId,
788
+ toolName: tool.name,
789
+ artifact: artifact.toolResult(output, {
790
+ id: `artifact:tool-result:${tool.name}:${callId}`,
791
+ toolName: tool.name,
792
+ callId
793
+ })
794
+ };
795
+ }
796
+ async function importMcpTools(client, toolNames) {
797
+ const descriptors = await Promise.resolve(client.listTools?.() ?? []);
798
+ const allowed = toolNames === void 0 ? void 0 : new Set(toolNames);
799
+ return descriptors.filter((descriptor) => allowed === void 0 || allowed.has(descriptor.name)).map((descriptor) => defineTool({
800
+ name: descriptor.name,
801
+ ...descriptor.description !== void 0 ? { description: descriptor.description } : {},
802
+ inputSchema: descriptor.inputSchema,
803
+ execute: async (input) => client.callTool({
804
+ name: descriptor.name,
805
+ arguments: input
806
+ })
807
+ }));
808
+ }
809
+ function toolArtifactRef(result) {
810
+ const { value: _value, ...ref } = result.artifact;
811
+ return ref;
812
+ }
813
+ function createToolCallId() {
814
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
815
+ return `${Date.now()}:${Math.random().toString(16).slice(2)}`;
816
+ }
817
+ //#endregion
818
+ //#region src/agent/format-tools.ts
819
+ /**
820
+ * Convert a Standard Schema to a JSON Schema-shaped descriptor suitable for
821
+ * inclusion in an LLM tool description. Standard Schema vendors can
822
+ * optionally expose `toJSONSchema` on their schema objects; when absent,
823
+ * we fall back to a minimal structural description that lists the schema
824
+ * vendor + version + a placeholder. Models tolerate placeholder schemas in
825
+ * practice because the tool description is supplementary — what matters
826
+ * is the envelope contract (`{tool_call: {name, args}}`).
827
+ */
828
+ function toolSchemaToJsonSchema(schema) {
829
+ const standardSchema = schema["~standard"];
830
+ if (typeof standardSchema === "object" && standardSchema !== null && "vendor" in standardSchema) {
831
+ const vendor = standardSchema;
832
+ const maybeToJson = schema.toJSONSchema;
833
+ if (typeof maybeToJson === "function") try {
834
+ return maybeToJson();
835
+ } catch {}
836
+ return {
837
+ $comment: `standard-schema vendor: ${vendor.vendor}; toJSONSchema not available`,
838
+ type: "object"
839
+ };
840
+ }
841
+ return {
842
+ $comment: "non-standard-schema input",
843
+ type: "object"
844
+ };
845
+ }
846
+ /**
847
+ * Builds the prompt-reencoded tool-use protocol handle for any provider.
848
+ *
849
+ * Phase 19 ships a uniform implementation across all 7 logical providers
850
+ * (openai, openai-compat, anthropic, gemini, xai, openrouter, lm-studio).
851
+ * The `providerName` argument is accepted for forward compatibility but
852
+ * does not branch the implementation in v1.2.
853
+ */
854
+ function formatToolsForProvider(providerName, tools, options = {}) {
855
+ options.mode;
856
+ const system = options.system?.trim() ?? "";
857
+ const toolDescriptions = tools.map((tool) => {
858
+ const schemaDescriptor = toolSchemaToJsonSchema(tool.inputSchema);
859
+ const schemaJson = JSON.stringify(schemaDescriptor, null, 2);
860
+ const desc = tool.description?.trim() ?? "(no description)";
861
+ return `- name: ${tool.name}\n description: ${desc}\n args_schema: ${schemaJson}`;
862
+ }).join("\n");
863
+ const envelopeInstructions = [
864
+ "You are a single-agent loop. You can either:",
865
+ " (a) answer the user directly with a final response, OR",
866
+ " (b) request one or more tool calls.",
867
+ "",
868
+ "To request tool calls, respond with ONE JSON object on a line by itself:",
869
+ " {\"tool_calls\": [{\"id\": \"...\", \"name\": \"tool_name\", \"args\": {...}}]}",
870
+ "Each tool_call needs a unique id (any string). The args MUST match the tool's args_schema.",
871
+ "",
872
+ "To answer directly, respond with a final answer in natural language with NO JSON envelope.",
873
+ "Do not mix a final answer and a tool_calls envelope in the same response."
874
+ ].join("\n");
875
+ const systemBlock = [
876
+ system,
877
+ "",
878
+ "Available tools:",
879
+ toolDescriptions || "(none)",
880
+ "",
881
+ envelopeInstructions
882
+ ].filter((s) => s !== "" || true).join("\n").replace(/^\n+/, "").trimEnd();
883
+ function buildTask(conversation) {
884
+ const lines = [];
885
+ lines.push(systemBlock);
886
+ lines.push("");
887
+ lines.push("---");
888
+ lines.push("");
889
+ for (const turn of conversation) {
890
+ if (turn.role === "user") lines.push(`USER:\n${turn.content}`);
891
+ else if (turn.role === "assistant") lines.push(`ASSISTANT:\n${turn.content}`);
892
+ else {
893
+ const idHint = turn.toolCallId !== void 0 ? ` id=${turn.toolCallId}` : "";
894
+ const nameHint = turn.toolName !== void 0 ? ` name=${turn.toolName}` : "";
895
+ lines.push(`TOOL_RESULT (${nameHint.trim() || "tool"}${idHint}):\n${turn.content}`);
896
+ }
897
+ lines.push("");
898
+ }
899
+ lines.push("ASSISTANT:");
900
+ return lines.join("\n");
901
+ }
902
+ function parseToolUse(responseText) {
903
+ if (typeof responseText !== "string" || responseText.length === 0) return null;
904
+ const candidates = extractJsonCandidates(responseText);
905
+ for (const candidate of candidates) {
906
+ const parsed = tryParseEnvelope(candidate);
907
+ if (parsed !== null) return parsed;
908
+ }
909
+ return null;
910
+ }
911
+ function describeForSystem() {
912
+ return systemBlock;
913
+ }
914
+ return {
915
+ buildTask,
916
+ parseToolUse,
917
+ describeForSystem,
918
+ mode: "prompt-reencoded"
919
+ };
920
+ }
921
+ /**
922
+ * Extracts JSON-looking candidate substrings from a response text.
923
+ *
924
+ * Models routinely wrap JSON in markdown code fences (```json ... ```),
925
+ * prepend explanatory prose ("I'll call the search tool: { ... }"), or
926
+ * produce multiple JSON-shaped blobs. This extractor scans for plausible
927
+ * candidates ordered by likelihood.
928
+ */
929
+ function extractJsonCandidates(text) {
930
+ const candidates = [];
931
+ const fenceRegex = /```(?:json)?\s*([\s\S]*?)```/g;
932
+ let fenceMatch;
933
+ while ((fenceMatch = fenceRegex.exec(text)) !== null) {
934
+ const inner = fenceMatch[1];
935
+ if (inner !== void 0) candidates.push(inner.trim());
936
+ }
937
+ const braceStart = text.indexOf("{");
938
+ const braceEnd = text.lastIndexOf("}");
939
+ if (braceStart !== -1 && braceEnd > braceStart) candidates.push(text.slice(braceStart, braceEnd + 1));
940
+ candidates.push(text.trim());
941
+ return candidates;
942
+ }
943
+ function tryParseEnvelope(jsonLike) {
944
+ let parsed;
945
+ try {
946
+ parsed = JSON.parse(jsonLike);
947
+ } catch {
948
+ return null;
949
+ }
950
+ if (typeof parsed !== "object" || parsed === null) return null;
951
+ const toolCalls = parsed["tool_calls"];
952
+ if (!Array.isArray(toolCalls) || toolCalls.length === 0) return null;
953
+ const requests = [];
954
+ for (const call of toolCalls) {
955
+ if (typeof call !== "object" || call === null) return null;
956
+ const callRecord = call;
957
+ const id = callRecord["id"];
958
+ const name = callRecord["name"];
959
+ const args = callRecord["args"];
960
+ if (typeof id !== "string" || typeof name !== "string") return null;
961
+ requests.push({
962
+ id,
963
+ name,
964
+ args
965
+ });
966
+ }
967
+ return requests;
968
+ }
969
+ //#endregion
970
+ //#region src/agent/host.ts
971
+ /**
972
+ * Reference implementation suitable for Node tests + the Phase 19 default
973
+ * behavior.
974
+ *
975
+ * - scheduler: resolves immediately (no yield between iterations).
976
+ * - transport: pass-through to provider.execute().
977
+ * - storage: save() / clear() are no-ops; load() always returns null.
978
+ *
979
+ * Equivalent to passing no host at all.
980
+ */
981
+ function createNoopAgentHost() {
982
+ return {
983
+ kind: "agent-host",
984
+ scheduler: { async scheduleNext(_iterationIndex) {} },
985
+ transport: { async call(provider, request) {
986
+ if (provider.execute === void 0) throw new Error(`AgentTransport: provider ${provider.id} has no execute() method.`);
987
+ return provider.execute(request);
988
+ } },
989
+ storage: {
990
+ async save(_snapshot) {},
991
+ async load() {
992
+ return null;
993
+ },
994
+ async clear() {}
995
+ }
996
+ };
997
+ }
998
+ //#endregion
999
+ //#region src/agent/types.ts
1000
+ /**
1001
+ * Typed error raised when a SAFETY-band handler sets `controls.deny(reason)`
1002
+ * during `BEFORE_AGENT_ITERATION`. Carries `terminal: true` semantics to
1003
+ * align with v1.1 `TripwireViolationError`: the failure is NOT retried by
1004
+ * the fallback chain.
1005
+ *
1006
+ * Surfaced via `AgentFailure { kind: "agent-iteration-denied", reason, ... }`
1007
+ * — callers can also catch the typed error if they prefer.
1008
+ */
1009
+ var AgentDeniedError = class extends Error {
1010
+ kind = "agent-iteration-denied";
1011
+ terminal = true;
1012
+ reason;
1013
+ iterationIndex;
1014
+ constructor(reason, iterationIndex) {
1015
+ super(`Agent iteration ${iterationIndex} denied: ${reason}`);
1016
+ this.name = "AgentDeniedError";
1017
+ this.reason = reason;
1018
+ this.iterationIndex = iterationIndex;
1019
+ }
1020
+ };
1021
+ //#endregion
1022
+ //#region src/agent/runtime.ts
1023
+ var runtime_exports = /* @__PURE__ */ __exportAll({ runAgent: () => runAgent });
1024
+ const ZERO_USAGE = {
1025
+ promptTokens: 0,
1026
+ completionTokens: 0,
1027
+ costUsd: null
1028
+ };
1029
+ /**
1030
+ * Resolves the runtime's behaviour for a single `ai.runAgent(intent)` call.
1031
+ *
1032
+ * Phase 19 ships an in-process default scheduler (the loop runs in the
1033
+ * calling Promise), direct transport (provider.execute()), and in-memory
1034
+ * transcript (the `conversation` array). Phase 20 promotes scheduler /
1035
+ * transport / storage to the pluggable `AgentHost` adapter.
1036
+ */
1037
+ async function runAgent(intent, config = {}) {
1038
+ const startedAt = Date.now();
1039
+ const cumulativeUsage = {
1040
+ promptTokens: 0,
1041
+ completionTokens: 0,
1042
+ costUsd: null
1043
+ };
1044
+ const iterations = [];
1045
+ const host = intent.host ?? createNoopAgentHost();
1046
+ const survivabilityAdapter = intent.survivabilityAdapter ?? createNoopSurvivabilityAdapter();
1047
+ const pipeline = ensurePipeline(intent);
1048
+ maybeAutoRegisterCheckpoint(pipeline, intent);
1049
+ const provider = pickFirstExecutableProvider(config);
1050
+ if (provider === null) return buildFailure({
1051
+ kind: "execution_unavailable",
1052
+ reason: "No provider adapter with execute() is configured.",
1053
+ iterations,
1054
+ usage: cumulativeUsage
1055
+ });
1056
+ let providerName = provider.id;
1057
+ let conversation = [{
1058
+ role: "user",
1059
+ content: intent.task
1060
+ }];
1061
+ const handle = formatToolsForProvider(providerName, intent.tools);
1062
+ const budget = intent.contract?.budget;
1063
+ const maxIterations = budget?.maxIterations ?? Number.POSITIVE_INFINITY;
1064
+ const maxWallTimeMs = budget?.maxWallTimeMs ?? Number.POSITIVE_INFINITY;
1065
+ const maxCostUsd = budget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
1066
+ let iterationIndex = 0;
1067
+ const existingSnapshot = await host.storage?.load();
1068
+ if (existingSnapshot !== null && existingSnapshot !== void 0) {
1069
+ intent.tracer?.event?.("recovery.start", {
1070
+ snapshotVersion: existingSnapshot.version,
1071
+ capturedAt: existingSnapshot.capturedAt
1072
+ });
1073
+ try {
1074
+ const restored = survivabilityAdapter.deserialize(existingSnapshot);
1075
+ iterationIndex = restored.iterationIndex;
1076
+ conversation = [...restored.conversation];
1077
+ cumulativeUsage.promptTokens = restored.cumulativeUsage.promptTokens;
1078
+ cumulativeUsage.completionTokens = restored.cumulativeUsage.completionTokens;
1079
+ cumulativeUsage.costUsd = restored.cumulativeUsage.costUsd;
1080
+ providerName = restored.providerName;
1081
+ intent.tracer?.event?.("recovery.complete", {
1082
+ iterationIndex,
1083
+ providerName
1084
+ });
1085
+ } catch (error) {
1086
+ intent.tracer?.event?.("recovery.failed", { reason: error instanceof Error ? error.message : "deserialize failed" });
1087
+ await host.storage?.clear();
1088
+ }
1089
+ }
1090
+ while (iterationIndex < maxIterations) {
1091
+ const elapsedMs = Date.now() - startedAt;
1092
+ if (elapsedMs >= maxWallTimeMs) return buildFailure({
1093
+ kind: "agent-wall-time-exceeded",
1094
+ reason: `Wall-time budget ${maxWallTimeMs}ms exceeded after ${elapsedMs}ms`,
1095
+ iterations,
1096
+ usage: cumulativeUsage
1097
+ });
1098
+ if (cumulativeUsage.costUsd !== null && cumulativeUsage.costUsd >= maxCostUsd) return buildFailure({
1099
+ kind: "no-contract-match",
1100
+ reason: `Cost budget $${maxCostUsd} exceeded at $${cumulativeUsage.costUsd}`,
1101
+ iterations,
1102
+ usage: cumulativeUsage
1103
+ });
1104
+ await pipeline.run("BEFORE_AGENT_ITERATION", {
1105
+ iterationIndex,
1106
+ intent,
1107
+ conversation: conversation.map((t) => ({ ...t })),
1108
+ stepName: `agent-iteration-${iterationIndex}-before`,
1109
+ stepIndex: iterationIndex,
1110
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1111
+ ...iterationIndex > 0 ? { previousStepName: `agent-iteration-${iterationIndex - 1}-after` } : {}
1112
+ });
1113
+ const denial = pipeline.lastDenialReason();
1114
+ if (denial !== null) {
1115
+ const failedRecord = {
1116
+ index: iterationIndex,
1117
+ provider: providerName,
1118
+ promptTokens: 0,
1119
+ completionTokens: 0,
1120
+ costUsd: null,
1121
+ durationMs: 0,
1122
+ toolCalls: [],
1123
+ deniedReason: denial
1124
+ };
1125
+ iterations.push(failedRecord);
1126
+ await pipeline.run("AFTER_AGENT_ITERATION", {
1127
+ iterationIndex,
1128
+ intent,
1129
+ record: failedRecord,
1130
+ stepName: `agent-iteration-${iterationIndex}-after`,
1131
+ stepIndex: iterationIndex,
1132
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1133
+ previousStepName: `agent-iteration-${iterationIndex}-before`
1134
+ });
1135
+ return buildFailure({
1136
+ kind: "agent-iteration-denied",
1137
+ reason: denial,
1138
+ iterations,
1139
+ usage: cumulativeUsage
1140
+ });
1141
+ }
1142
+ const task = handle.buildTask(conversation);
1143
+ const iterStart = Date.now();
1144
+ let response;
1145
+ try {
1146
+ if (provider.execute === void 0) return buildFailure({
1147
+ kind: "execution_unavailable",
1148
+ reason: "Selected provider has no execute() method.",
1149
+ iterations,
1150
+ usage: cumulativeUsage
1151
+ });
1152
+ const providerRequest = {
1153
+ task,
1154
+ artifacts: [],
1155
+ outputs: ["answer"],
1156
+ ...intent.policy !== void 0 ? { policy: intent.policy } : {}
1157
+ };
1158
+ response = host.transport !== void 0 ? await host.transport.call(provider, providerRequest) : await provider.execute(providerRequest);
1159
+ } catch (error) {
1160
+ return buildFailure({
1161
+ kind: "provider_execution",
1162
+ reason: error instanceof Error ? error.message : "Provider execution failed",
1163
+ cause: error,
1164
+ iterations,
1165
+ usage: cumulativeUsage
1166
+ });
1167
+ }
1168
+ const iterDuration = Date.now() - iterStart;
1169
+ const iterUsage = response.normalizedUsage ?? ZERO_USAGE;
1170
+ accumulateUsage(cumulativeUsage, iterUsage);
1171
+ const responseText = extractResponseText(response);
1172
+ const toolUseRequests = handle.parseToolUse(responseText);
1173
+ if (toolUseRequests === null) {
1174
+ const finalRecord = {
1175
+ index: iterationIndex,
1176
+ provider: providerName,
1177
+ promptTokens: iterUsage.promptTokens,
1178
+ completionTokens: iterUsage.completionTokens,
1179
+ costUsd: iterUsage.costUsd,
1180
+ durationMs: iterDuration,
1181
+ toolCalls: []
1182
+ };
1183
+ iterations.push(finalRecord);
1184
+ conversation.push({
1185
+ role: "assistant",
1186
+ content: responseText
1187
+ });
1188
+ await pipeline.run("AFTER_AGENT_ITERATION", {
1189
+ iterationIndex,
1190
+ intent,
1191
+ record: finalRecord,
1192
+ stepName: `agent-iteration-${iterationIndex}-after`,
1193
+ stepIndex: iterationIndex,
1194
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1195
+ previousStepName: `agent-iteration-${iterationIndex}-before`
1196
+ });
1197
+ await host.storage?.clear();
1198
+ return {
1199
+ kind: "success",
1200
+ output: { answer: responseText },
1201
+ usage: snapshotUsage(cumulativeUsage),
1202
+ iterations: Object.freeze([...iterations])
1203
+ };
1204
+ }
1205
+ conversation.push({
1206
+ role: "assistant",
1207
+ content: responseText
1208
+ });
1209
+ const toolCallRecords = [];
1210
+ for (const req of toolUseRequests) {
1211
+ const tool = intent.tools.find((t) => t.name === req.name);
1212
+ let toolResult = null;
1213
+ let resultContent;
1214
+ let resultHash = "tool-not-found";
1215
+ if (tool === void 0) resultContent = JSON.stringify({ error: `Unknown tool: ${req.name}` });
1216
+ else try {
1217
+ await pipeline.run("BEFORE_TOOL", {
1218
+ iterationIndex,
1219
+ toolName: req.name,
1220
+ args: req.args
1221
+ });
1222
+ toolResult = await runTool(tool, req.args);
1223
+ resultContent = stringifyArtifactValue(toolResult.artifact.value);
1224
+ resultHash = toolResult.callId;
1225
+ await pipeline.run("AFTER_TOOL", {
1226
+ iterationIndex,
1227
+ toolName: req.name,
1228
+ args: req.args,
1229
+ result: toolResult.artifact.value
1230
+ });
1231
+ } catch (error) {
1232
+ resultContent = JSON.stringify({ error: error instanceof Error ? error.message : "Tool execution failed" });
1233
+ }
1234
+ conversation.push({
1235
+ role: "tool",
1236
+ content: resultContent,
1237
+ toolCallId: req.id,
1238
+ toolName: req.name
1239
+ });
1240
+ toolCallRecords.push({
1241
+ id: req.id,
1242
+ name: req.name,
1243
+ argsHash: stableHash(req.args),
1244
+ resultHash
1245
+ });
1246
+ }
1247
+ const record = {
1248
+ index: iterationIndex,
1249
+ provider: providerName,
1250
+ promptTokens: iterUsage.promptTokens,
1251
+ completionTokens: iterUsage.completionTokens,
1252
+ costUsd: iterUsage.costUsd,
1253
+ durationMs: iterDuration,
1254
+ toolCalls: Object.freeze([...toolCallRecords])
1255
+ };
1256
+ iterations.push(record);
1257
+ await pipeline.run("AFTER_AGENT_ITERATION", {
1258
+ iterationIndex,
1259
+ intent,
1260
+ record,
1261
+ stepName: `agent-iteration-${iterationIndex}-after`,
1262
+ stepIndex: iterationIndex,
1263
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1264
+ previousStepName: `agent-iteration-${iterationIndex}-before`
1265
+ });
1266
+ if (host.storage !== void 0) {
1267
+ const snapshot = survivabilityAdapter.serialize({
1268
+ version: "agent-snapshot/v1",
1269
+ iterationIndex: iterationIndex + 1,
1270
+ conversation: [...conversation],
1271
+ cumulativeUsage: snapshotUsage(cumulativeUsage),
1272
+ providerName,
1273
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString()
1274
+ });
1275
+ await host.storage.save(snapshot);
1276
+ }
1277
+ if (host.scheduler !== void 0) await host.scheduler.scheduleNext(iterationIndex);
1278
+ iterationIndex += 1;
1279
+ }
1280
+ return buildFailure({
1281
+ kind: "agent-max-iterations",
1282
+ reason: `Iteration budget ${maxIterations} reached without a final answer`,
1283
+ iterations,
1284
+ usage: cumulativeUsage
1285
+ });
1286
+ }
1287
+ function ensurePipeline(intent) {
1288
+ if (intent.pipeline !== void 0) return intent.pipeline;
1289
+ return createHookPipeline(intent.tracer !== void 0 ? { tracer: intent.tracer } : {});
1290
+ }
1291
+ function maybeAutoRegisterCheckpoint(pipeline, intent) {
1292
+ if (intent.signer === void 0) return;
1293
+ if (intent.autoRegisterCheckpoint === false) return;
1294
+ if (pipeline.isFrozen()) return;
1295
+ const handler = createCheckpointHook({
1296
+ runId: `runAgent-${Date.now()}-${Math.random().toString(16).slice(2)}`,
1297
+ signer: intent.signer,
1298
+ ...intent.tracer !== void 0 ? { tracer: intent.tracer } : {}
1299
+ });
1300
+ pipeline.register("AFTER_AGENT_ITERATION", handler, { band: BAND.OBSERVABILITY });
1301
+ }
1302
+ function pickFirstExecutableProvider(config) {
1303
+ const providers = config.providers ?? [];
1304
+ for (const entry of providers) {
1305
+ if (typeof entry === "string") continue;
1306
+ if ("kind" in entry && entry.kind === "provider-adapter" && entry.execute !== void 0) return entry;
1307
+ }
1308
+ return null;
1309
+ }
1310
+ function extractResponseText(response) {
1311
+ const raw = response.rawOutputs ?? {};
1312
+ const text = raw["answer"];
1313
+ if (typeof text === "string") return text;
1314
+ for (const value of Object.values(raw)) if (typeof value === "string") return value;
1315
+ return "";
1316
+ }
1317
+ function accumulateUsage(cumulative, iter) {
1318
+ cumulative.promptTokens += iter.promptTokens;
1319
+ cumulative.completionTokens += iter.completionTokens;
1320
+ if (iter.costUsd !== null) cumulative.costUsd = (cumulative.costUsd ?? 0) + iter.costUsd;
1321
+ }
1322
+ function snapshotUsage(c) {
1323
+ return {
1324
+ promptTokens: c.promptTokens,
1325
+ completionTokens: c.completionTokens,
1326
+ costUsd: c.costUsd
1327
+ };
1328
+ }
1329
+ function buildFailure(input) {
1330
+ return {
1331
+ kind: input.kind,
1332
+ usage: snapshotUsage(input.usage),
1333
+ iterations: Object.freeze([...input.iterations]),
1334
+ ...input.reason !== void 0 ? { reason: input.reason } : {},
1335
+ ...input.cause !== void 0 ? { cause: input.cause } : {}
1336
+ };
1337
+ }
1338
+ function stringifyArtifactValue(value) {
1339
+ if (typeof value === "string") return value;
1340
+ try {
1341
+ return JSON.stringify(value);
1342
+ } catch {
1343
+ return String(value);
1344
+ }
1345
+ }
1346
+ function stableHash(input) {
1347
+ try {
1348
+ const json = JSON.stringify(input);
1349
+ let hash = 5381;
1350
+ for (let i = 0; i < json.length; i += 1) hash = hash * 33 ^ json.charCodeAt(i);
1351
+ return `djb2:${(hash >>> 0).toString(16)}`;
1352
+ } catch {
1353
+ return "djb2:0";
1354
+ }
1355
+ }
1356
+ //#endregion
1357
+ export { createHookPipeline as C, BAND as S, toArtifactRef as T, PAYLOAD_TYPE as _, formatToolsForProvider as a, decodeEnvelope as b, importMcpTools as c, validateOutputMap as d, createNoopSurvivabilityAdapter as f, createReceipt as g, createCheckpointHook as h, createNoopAgentHost as i, runTool as l, STEP_TRANSITION_EVENT_NAME as m, runtime_exports as n, toolSchemaToJsonSchema as o, DEFAULT_CHECKPOINT_BAND as p, AgentDeniedError as r, defineTool as s, runAgent as t, toolArtifactRef as u, base64Encode as v, artifact as w, canonicalizeReceiptBody as x, buildPae as y };
1358
+
1359
+ //# sourceMappingURL=runtime-CfZ-sGGk.js.map