@agentfield/sdk 0.1.110 → 0.1.111-rc.2

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 CHANGED
@@ -1,6 +1,6 @@
1
- import fs, { promises } from 'fs';
1
+ import fs, { promises, readFileSync } from 'fs';
2
2
  import * as path2 from 'path';
3
- import path2__default, { resolve } from 'path';
3
+ import path2__default, { resolve, dirname } from 'path';
4
4
  import { createRequire } from 'module';
5
5
  import { spawn } from 'child_process';
6
6
  import express from 'express';
@@ -25,6 +25,7 @@ import { Buffer as Buffer$1 } from 'buffer';
25
25
  import { zodToJsonSchema } from 'zod-to-json-schema';
26
26
  import { generateKeyPair, exportJWK, importJWK, CompactEncrypt, compactDecrypt } from 'jose';
27
27
  import { readFile } from 'fs/promises';
28
+ import { fileURLToPath } from 'url';
28
29
 
29
30
  var __defProp = Object.defineProperty;
30
31
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -154,14 +155,14 @@ function getZodConverter() {
154
155
  }
155
156
  return zodConverter;
156
157
  }
157
- function isRecord(value) {
158
+ function isRecord2(value) {
158
159
  return typeof value === "object" && value !== null && !Array.isArray(value);
159
160
  }
160
161
  function hasJsonSchema(value) {
161
- return isRecord(value) && typeof value.jsonSchema === "function";
162
+ return isRecord2(value) && typeof value.jsonSchema === "function";
162
163
  }
163
164
  function hasParse(value) {
164
- return isRecord(value) && typeof value.parse === "function";
165
+ return isRecord2(value) && typeof value.parse === "function";
165
166
  }
166
167
  function estimateTokens(text2) {
167
168
  return Math.floor(text2.length / 4);
@@ -190,7 +191,7 @@ function getSchemaPath(cwd) {
190
191
  return path2__default.join(cwd, SCHEMA_FILENAME);
191
192
  }
192
193
  function schemaToJsonSchema(schema) {
193
- if (isRecord(schema)) {
194
+ if (isRecord2(schema)) {
194
195
  if ("type" in schema || "properties" in schema || "$schema" in schema) {
195
196
  return schema;
196
197
  }
@@ -338,7 +339,7 @@ var claude_exports = {};
338
339
  __export(claude_exports, {
339
340
  ClaudeCodeProvider: () => ClaudeCodeProvider
340
341
  });
341
- function isRecord2(value) {
342
+ function isRecord3(value) {
342
343
  return typeof value === "object" && value !== null;
343
344
  }
344
345
  function getString(record, key) {
@@ -382,10 +383,16 @@ var init_claude = __esm({
382
383
  let totalCost;
383
384
  let numTurns = 0;
384
385
  let sessionId = "";
386
+ let usageObj;
387
+ let inputTokens;
388
+ let outputTokens;
389
+ let cacheReadTokens;
390
+ let cacheCreationTokens;
391
+ let modelName;
385
392
  const startApi = Date.now();
386
393
  try {
387
394
  for await (const msg of sdk.query({ prompt, options: agentOptions })) {
388
- const msgObj = isRecord2(msg) ? msg : { raw: String(msg) };
395
+ const msgObj = isRecord3(msg) ? msg : { raw: String(msg) };
389
396
  messages.push(msgObj);
390
397
  const msgType = getString(msgObj, "type") ?? "";
391
398
  if (msgType === "result") {
@@ -404,16 +411,28 @@ var init_claude = __esm({
404
411
  }
405
412
  const turns = getNumber(msgObj, "num_turns");
406
413
  numTurns = turns === void 0 ? messages.length : Math.trunc(turns);
407
- } else if (msgType === "assistant" && resultText === void 0) {
414
+ if (isRecord3(msgObj.usage)) {
415
+ usageObj = msgObj.usage;
416
+ inputTokens = getNumber(usageObj, "input_tokens");
417
+ outputTokens = getNumber(usageObj, "output_tokens");
418
+ cacheReadTokens = getNumber(usageObj, "cache_read_input_tokens");
419
+ cacheCreationTokens = getNumber(usageObj, "cache_creation_input_tokens");
420
+ }
421
+ modelName = getString(msgObj, "model") ?? modelName;
422
+ } else if (msgType === "assistant") {
423
+ if (modelName === void 0 && isRecord3(msgObj.message)) {
424
+ modelName = getString(msgObj.message, "model");
425
+ }
426
+ if (resultText !== void 0) continue;
408
427
  let content = msgObj.content;
409
- if (content === void 0 && isRecord2(msgObj.message)) {
428
+ if (content === void 0 && isRecord3(msgObj.message)) {
410
429
  content = msgObj.message.content;
411
430
  }
412
431
  if (typeof content === "string") {
413
432
  resultText = content;
414
433
  } else if (Array.isArray(content)) {
415
434
  for (const block of content) {
416
- if (isRecord2(block) && block.type === "text" && typeof block.text === "string") {
435
+ if (isRecord3(block) && block.type === "text" && typeof block.text === "string") {
417
436
  resultText = block.text;
418
437
  }
419
438
  }
@@ -427,7 +446,13 @@ var init_claude = __esm({
427
446
  durationApiMs: Date.now() - startApi,
428
447
  numTurns,
429
448
  totalCostUsd: totalCost,
430
- sessionId
449
+ sessionId,
450
+ usage: usageObj,
451
+ inputTokens,
452
+ outputTokens,
453
+ cacheReadTokens,
454
+ cacheCreationTokens,
455
+ model: modelName
431
456
  }),
432
457
  isError: false
433
458
  });
@@ -454,7 +479,7 @@ function resolveIdleMs(idleSeconds) {
454
479
  return seconds > 0 ? seconds * 1e3 : void 0;
455
480
  }
456
481
  function runCli(cmd, options) {
457
- return new Promise((resolve2, reject) => {
482
+ return new Promise((resolve3, reject) => {
458
483
  const [bin, ...args] = cmd;
459
484
  const env = { ...process.env, ...options?.env };
460
485
  applyOpenRouterAttributionEnv(env);
@@ -514,7 +539,7 @@ function runCli(cmd, options) {
514
539
  }
515
540
  settled = true;
516
541
  cleanup();
517
- resolve2({ stdout, stderr, exitCode: code ?? 0 });
542
+ resolve3({ stdout, stderr, exitCode: code ?? 0 });
518
543
  });
519
544
  proc.on("error", (err) => {
520
545
  if (settled) {
@@ -841,6 +866,10 @@ var runner_exports = {};
841
866
  __export(runner_exports, {
842
867
  HarnessRunner: () => HarnessRunner
843
868
  });
869
+ function tokenMetrics(raw) {
870
+ const { inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, totalTokens, model } = raw.metrics;
871
+ return { inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, totalTokens, model };
872
+ }
844
873
  var TRANSIENT_PATTERNS, HarnessRunner;
845
874
  var init_runner = __esm({
846
875
  "src/harness/runner.ts"() {
@@ -891,7 +920,8 @@ var init_runner = __esm({
891
920
  numTurns: raw.metrics.numTurns,
892
921
  durationMs: Date.now() - startTime,
893
922
  sessionId: raw.metrics.sessionId,
894
- messages: raw.messages
923
+ messages: raw.messages,
924
+ ...tokenMetrics(raw)
895
925
  });
896
926
  } finally {
897
927
  if (schema !== void 0) {
@@ -984,7 +1014,8 @@ var init_runner = __esm({
984
1014
  numTurns: raw.metrics.numTurns,
985
1015
  durationMs: Date.now() - startTime,
986
1016
  sessionId: raw.metrics.sessionId,
987
- messages: raw.messages
1017
+ messages: raw.messages,
1018
+ ...tokenMetrics(raw)
988
1019
  });
989
1020
  }
990
1021
  return createHarnessResult({
@@ -995,7 +1026,8 @@ var init_runner = __esm({
995
1026
  numTurns: raw.metrics.numTurns,
996
1027
  durationMs: Date.now() - startTime,
997
1028
  sessionId: raw.metrics.sessionId,
998
- messages: raw.messages
1029
+ messages: raw.messages,
1030
+ ...tokenMetrics(raw)
999
1031
  });
1000
1032
  }
1001
1033
  async buildProvider(providerName, options) {
@@ -1008,8 +1040,8 @@ var init_runner = __esm({
1008
1040
  return base + jitter;
1009
1041
  }
1010
1042
  sleep(delaySeconds) {
1011
- return new Promise((resolve2) => {
1012
- setTimeout(resolve2, Math.max(0, delaySeconds) * 1e3);
1043
+ return new Promise((resolve3) => {
1044
+ setTimeout(resolve3, Math.max(0, delaySeconds) * 1e3);
1013
1045
  });
1014
1046
  }
1015
1047
  };
@@ -1200,8 +1232,8 @@ var PauseManager = class {
1200
1232
  return existing.promise;
1201
1233
  }
1202
1234
  let resolveFn;
1203
- const promise = new Promise((resolve2) => {
1204
- resolveFn = resolve2;
1235
+ const promise = new Promise((resolve3) => {
1236
+ resolveFn = resolve3;
1205
1237
  });
1206
1238
  this.pending.set(approvalRequestId, { resolve: resolveFn, promise });
1207
1239
  if (executionId) {
@@ -1423,7 +1455,7 @@ var ApprovalClient = class {
1423
1455
  }
1424
1456
  };
1425
1457
  function sleep(ms) {
1426
- return new Promise((resolve2) => setTimeout(resolve2, ms));
1458
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
1427
1459
  }
1428
1460
 
1429
1461
  // src/triggers/factories.ts
@@ -1481,6 +1513,205 @@ function triggerToPayload(trigger) {
1481
1513
  const _exhaustive = trigger;
1482
1514
  throw new TypeError(`Unknown trigger kind: ${_exhaustive}`);
1483
1515
  }
1516
+
1517
+ // src/triggers/dispatch.ts
1518
+ function isTriggerEnvelope(body) {
1519
+ if (!body || typeof body !== "object" || Array.isArray(body)) return false;
1520
+ const obj = body;
1521
+ if (!("event" in obj && "_meta" in obj)) return false;
1522
+ const meta = obj._meta;
1523
+ if (!meta || typeof meta !== "object" || Array.isArray(meta)) return false;
1524
+ return "trigger_id" in meta;
1525
+ }
1526
+ function unwrapEnvelope(body) {
1527
+ if (!isTriggerEnvelope(body)) {
1528
+ return { input: body };
1529
+ }
1530
+ const meta = body._meta;
1531
+ let receivedAt;
1532
+ try {
1533
+ receivedAt = new Date(meta.received_at);
1534
+ if (isNaN(receivedAt.getTime())) {
1535
+ receivedAt = /* @__PURE__ */ new Date();
1536
+ }
1537
+ } catch {
1538
+ receivedAt = /* @__PURE__ */ new Date();
1539
+ }
1540
+ const triggerContext = {
1541
+ triggerId: meta.trigger_id,
1542
+ source: meta.source,
1543
+ eventType: meta.event_type,
1544
+ eventId: meta.event_id,
1545
+ idempotencyKey: meta.idempotency_key,
1546
+ receivedAt,
1547
+ vcId: meta.vc_id
1548
+ };
1549
+ return {
1550
+ input: body.event,
1551
+ triggerContext
1552
+ };
1553
+ }
1554
+ function applyTriggerTransform(triggerContext, bindings, input) {
1555
+ if (!bindings || bindings.length === 0) return input;
1556
+ let bestMatch;
1557
+ let bestSpecificity = -1;
1558
+ for (const binding of bindings) {
1559
+ if (binding.kind !== "event") continue;
1560
+ if (binding.spec.source !== triggerContext.source) continue;
1561
+ const types = binding.spec.types ?? [];
1562
+ if (types.length > 0) {
1563
+ const matched = types.some(
1564
+ (t) => triggerContext.eventType === t || triggerContext.eventType.startsWith(t + ".")
1565
+ );
1566
+ if (!matched) continue;
1567
+ if (1 > bestSpecificity) {
1568
+ bestMatch = binding;
1569
+ bestSpecificity = 1;
1570
+ }
1571
+ } else {
1572
+ if (0 > bestSpecificity) {
1573
+ bestMatch = binding;
1574
+ bestSpecificity = 0;
1575
+ }
1576
+ }
1577
+ }
1578
+ if (bestMatch?.spec.transform) {
1579
+ try {
1580
+ return bestMatch.spec.transform(input);
1581
+ } catch {
1582
+ return input;
1583
+ }
1584
+ }
1585
+ return input;
1586
+ }
1587
+
1588
+ // src/usage/costTracker.ts
1589
+ var USAGE_ENVELOPE_KEY = "__agentfield_usage__";
1590
+ function deriveProvider(model) {
1591
+ if (!model) return null;
1592
+ const slug = String(model).trim();
1593
+ const slash = slug.indexOf("/");
1594
+ if (slash < 0) return null;
1595
+ return slug.slice(0, slash).toLowerCase() || null;
1596
+ }
1597
+ function toCount(value) {
1598
+ if (typeof value !== "number" || !Number.isFinite(value)) return 0;
1599
+ return Math.trunc(value);
1600
+ }
1601
+ function toCost(value) {
1602
+ if (typeof value !== "number" || !Number.isFinite(value)) return null;
1603
+ return value;
1604
+ }
1605
+ function round6(value) {
1606
+ return Math.round(value * 1e6) / 1e6;
1607
+ }
1608
+ var CostTracker = class {
1609
+ entries = [];
1610
+ /**
1611
+ * Record a single call's usage.
1612
+ *
1613
+ * Cost is optional: a call with known token counts but unknown price is
1614
+ * still recorded (`costUsd: null`) so tokens are never discarded.
1615
+ */
1616
+ record(init) {
1617
+ this.entries.push({
1618
+ model: init.model,
1619
+ inputTokens: toCount(init.inputTokens),
1620
+ outputTokens: toCount(init.outputTokens),
1621
+ totalTokens: toCount(init.totalTokens),
1622
+ costUsd: toCost(init.costUsd),
1623
+ reasonerName: init.reasonerName ?? null,
1624
+ source: init.source ?? "llm",
1625
+ provider: init.provider ?? deriveProvider(init.model),
1626
+ harness: init.harness ?? null,
1627
+ cacheReadTokens: toCount(init.cacheReadTokens),
1628
+ cacheCreationTokens: toCount(init.cacheCreationTokens),
1629
+ costSource: init.costSource ?? null
1630
+ });
1631
+ }
1632
+ /** Total accumulated cost in USD (unknown costs count as zero). */
1633
+ get totalCostUsd() {
1634
+ return this.entries.reduce((sum, e) => sum + (e.costUsd ?? 0), 0);
1635
+ }
1636
+ /** Total tokens used across all calls (per-entry total, no fallback). */
1637
+ get totalTokens() {
1638
+ return this.entries.reduce((sum, e) => sum + e.totalTokens, 0);
1639
+ }
1640
+ /** Number of calls tracked. */
1641
+ get callCount() {
1642
+ return this.entries.length;
1643
+ }
1644
+ get hasEntries() {
1645
+ return this.entries.length > 0;
1646
+ }
1647
+ /**
1648
+ * Return the transport contract form attached to execution envelopes.
1649
+ *
1650
+ * Matches the Python SDK's `CostTracker.serialize()` byte-for-byte in shape:
1651
+ * unset string fields serialize as null, `total_cost_usd` is null when no
1652
+ * entry had a known cost, and a per-entry `total_tokens` of zero falls back
1653
+ * to input + output.
1654
+ */
1655
+ serialize() {
1656
+ const entries = [];
1657
+ let totalInput = 0;
1658
+ let totalOutput = 0;
1659
+ let totalTokens = 0;
1660
+ let totalCost = 0;
1661
+ let anyCost = false;
1662
+ for (const e of this.entries) {
1663
+ const entryTotal = e.totalTokens || e.inputTokens + e.outputTokens;
1664
+ entries.push({
1665
+ source: e.source,
1666
+ provider: e.provider,
1667
+ model: e.model,
1668
+ harness: e.harness,
1669
+ reasoner: e.reasonerName,
1670
+ input_tokens: e.inputTokens,
1671
+ output_tokens: e.outputTokens,
1672
+ cache_read_tokens: e.cacheReadTokens,
1673
+ cache_creation_tokens: e.cacheCreationTokens,
1674
+ total_tokens: entryTotal,
1675
+ cost_usd: e.costUsd,
1676
+ cost_source: e.costSource
1677
+ });
1678
+ totalInput += e.inputTokens;
1679
+ totalOutput += e.outputTokens;
1680
+ totalTokens += entryTotal;
1681
+ if (e.costUsd !== null) {
1682
+ totalCost += e.costUsd;
1683
+ anyCost = true;
1684
+ }
1685
+ }
1686
+ return {
1687
+ total_cost_usd: anyCost ? round6(totalCost) : null,
1688
+ total_input_tokens: totalInput,
1689
+ total_output_tokens: totalOutput,
1690
+ total_tokens: totalTokens,
1691
+ entries
1692
+ };
1693
+ }
1694
+ /** Clear all tracked entries. */
1695
+ reset() {
1696
+ this.entries = [];
1697
+ }
1698
+ };
1699
+ function usageSummaryOrNull(tracker) {
1700
+ if (!tracker || !tracker.hasEntries) return null;
1701
+ return tracker.serialize();
1702
+ }
1703
+ function isPlainObject(value) {
1704
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
1705
+ const proto = Object.getPrototypeOf(value);
1706
+ return proto === Object.prototype || proto === null;
1707
+ }
1708
+ function attachUsageToSyncResult(result, tracker) {
1709
+ const usage = usageSummaryOrNull(tracker);
1710
+ if (usage === null || !isPlainObject(result)) return result;
1711
+ return { ...result, [USAGE_ENVELOPE_KEY]: usage };
1712
+ }
1713
+
1714
+ // src/context/ExecutionContext.ts
1484
1715
  var store = new AsyncLocalStorage();
1485
1716
  var ExecutionContext = class {
1486
1717
  input;
@@ -1488,12 +1719,21 @@ var ExecutionContext = class {
1488
1719
  req;
1489
1720
  res;
1490
1721
  agent;
1722
+ /**
1723
+ * Per-execution LLM/harness usage accumulator. Each top-level execution
1724
+ * binds a fresh tracker (isolated across concurrent executions via the
1725
+ * AsyncLocalStorage this context lives in); nested local `agent.call()`
1726
+ * executions inherit the parent's tracker so their usage rolls up into the
1727
+ * parent's report.
1728
+ */
1729
+ costTracker;
1491
1730
  constructor(params) {
1492
1731
  this.input = params.input;
1493
1732
  this.metadata = params.metadata;
1494
1733
  this.req = params.req;
1495
1734
  this.res = params.res;
1496
1735
  this.agent = params.agent;
1736
+ this.costTracker = params.costTracker ?? new CostTracker();
1497
1737
  }
1498
1738
  get logger() {
1499
1739
  return this.agent.getExecutionLogger();
@@ -1505,6 +1745,79 @@ var ExecutionContext = class {
1505
1745
  return store.getStore();
1506
1746
  }
1507
1747
  };
1748
+
1749
+ // src/usage/aiUsage.ts
1750
+ function isRecord(value) {
1751
+ return typeof value === "object" && value !== null;
1752
+ }
1753
+ function num(value) {
1754
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
1755
+ }
1756
+ function extractTokenUsage(usage) {
1757
+ if (!isRecord(usage)) {
1758
+ return { inputTokens: 0, outputTokens: 0, totalTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0 };
1759
+ }
1760
+ const inputDetails = isRecord(usage.inputTokenDetails) ? usage.inputTokenDetails : void 0;
1761
+ return {
1762
+ inputTokens: num(usage.inputTokens),
1763
+ outputTokens: num(usage.outputTokens),
1764
+ totalTokens: num(usage.totalTokens),
1765
+ // `cachedInputTokens` is the deprecated pre-details alias — keep it as a
1766
+ // fallback so older provider adapters still report cache reads.
1767
+ cacheReadTokens: num(inputDetails?.cacheReadTokens) || num(usage.cachedInputTokens),
1768
+ cacheCreationTokens: num(inputDetails?.cacheWriteTokens)
1769
+ };
1770
+ }
1771
+ function costFromUsageRaw(usage) {
1772
+ if (!isRecord(usage) || !isRecord(usage.raw)) return null;
1773
+ const cost = usage.raw.cost;
1774
+ return typeof cost === "number" && Number.isFinite(cost) && cost >= 0 ? cost : null;
1775
+ }
1776
+ function costFromProviderMetadata(providerMetadata) {
1777
+ if (!isRecord(providerMetadata)) return null;
1778
+ const openrouter = providerMetadata.openrouter;
1779
+ if (!isRecord(openrouter) || !isRecord(openrouter.usage)) return null;
1780
+ const cost = openrouter.usage.cost;
1781
+ return typeof cost === "number" && Number.isFinite(cost) && cost >= 0 ? cost : null;
1782
+ }
1783
+ function extractProviderCostUsd(source) {
1784
+ if (Array.isArray(source.steps) && source.steps.length > 0) {
1785
+ let sum = 0;
1786
+ let any = false;
1787
+ for (const step of source.steps) {
1788
+ const cost = costFromUsageRaw(isRecord(step) ? step.usage : void 0);
1789
+ if (cost !== null) {
1790
+ sum += cost;
1791
+ any = true;
1792
+ }
1793
+ }
1794
+ if (any) return sum;
1795
+ }
1796
+ return costFromUsageRaw(source.totalUsage) ?? costFromUsageRaw(source.usage) ?? costFromProviderMetadata(source.providerMetadata);
1797
+ }
1798
+ function recordAiSdkUsage(params) {
1799
+ try {
1800
+ const current = ExecutionContext.getCurrent();
1801
+ const tracker = params.tracker ?? current?.costTracker;
1802
+ if (!tracker) return;
1803
+ const tokens = extractTokenUsage(params.source.totalUsage ?? params.source.usage);
1804
+ const cost = extractProviderCostUsd(params.source);
1805
+ const hasTokens = tokens.inputTokens > 0 || tokens.outputTokens > 0 || tokens.totalTokens > 0 || tokens.cacheReadTokens > 0 || tokens.cacheCreationTokens > 0;
1806
+ if (!hasTokens && cost === null) return;
1807
+ tracker.record({
1808
+ model: params.model,
1809
+ ...tokens,
1810
+ costUsd: cost,
1811
+ costSource: cost !== null ? "provider" : null,
1812
+ reasonerName: params.reasonerName !== void 0 ? params.reasonerName : current?.metadata.reasonerId ?? null,
1813
+ source: "llm",
1814
+ provider: params.provider ?? void 0
1815
+ });
1816
+ } catch {
1817
+ }
1818
+ }
1819
+
1820
+ // src/ai/ToolCalling.ts
1508
1821
  var DEFAULT_MAX_TURNS = 10;
1509
1822
  var DEFAULT_MAX_TOOL_CALLS = 25;
1510
1823
  var DEFAULT_HEALTH_STATUS = void 0;
@@ -1722,9 +2035,15 @@ function wrapToolsWithObservability(toolMap, agent, trace, maxToolCalls, getCurr
1722
2035
  }
1723
2036
  return { tools: observableTools, getTotalCalls: () => totalCalls };
1724
2037
  }
1725
- async function executeToolCallLoop(agent, prompt, toolMap, config, needsLazyHydration, buildModel, options = {}) {
2038
+ async function executeToolCallLoop(agent, prompt, toolMap, config, needsLazyHydration, buildModel, options = {}, modelChoice) {
1726
2039
  const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
1727
2040
  const maxToolCalls = config.maxToolCalls ?? DEFAULT_MAX_TOOL_CALLS;
2041
+ const usageModel = modelChoice?.modelName ?? options.model;
2042
+ const recordLoopUsage = (result2) => {
2043
+ if (usageModel) {
2044
+ recordAiSdkUsage({ source: result2, model: usageModel, provider: modelChoice?.provider });
2045
+ }
2046
+ };
1728
2047
  const trace = {
1729
2048
  calls: [],
1730
2049
  totalTurns: 0,
@@ -1751,6 +2070,7 @@ async function executeToolCallLoop(agent, prompt, toolMap, config, needsLazyHydr
1751
2070
  stopWhen: stepCountIs(1)
1752
2071
  // Stop after LLM's first response (tool selection)
1753
2072
  });
2073
+ recordLoopUsage(selectionResult);
1754
2074
  const selectedNames = /* @__PURE__ */ new Set();
1755
2075
  for (const step of selectionResult.steps) {
1756
2076
  for (const tc of step.toolCalls) {
@@ -1794,6 +2114,7 @@ async function executeToolCallLoop(agent, prompt, toolMap, config, needsLazyHydr
1794
2114
  trace.totalTurns = currentTurn;
1795
2115
  }
1796
2116
  });
2117
+ recordLoopUsage(result);
1797
2118
  trace.finalResponse = result.text;
1798
2119
  trace.totalTurns = result.steps.length;
1799
2120
  return { text: result.text, trace };
@@ -1821,6 +2142,13 @@ var ReasonerContext = class {
1821
2142
  memory;
1822
2143
  workflow;
1823
2144
  did;
2145
+ /**
2146
+ * Per-execution token/cost usage accumulator. LLM calls made through
2147
+ * `ctx.ai()` / `ctx.aiWithTools()` and harness runs record into it
2148
+ * automatically; reasoner authors may also `record()` custom entries. Its
2149
+ * serialized summary is attached to the execution's terminal report.
2150
+ */
2151
+ costTracker;
1824
2152
  /**
1825
2153
  * AbortSignal that fires when the control plane cancels this execution
1826
2154
  * (per-execution cancel, the bottom-up cancel-tree endpoint, or any
@@ -1830,6 +2158,12 @@ var ReasonerContext = class {
1830
2158
  * CPU loops, check `ctx.signal.aborted` periodically and throw.
1831
2159
  */
1832
2160
  signal;
2161
+ /**
2162
+ * Trigger context populated when the reasoner was invoked by an inbound
2163
+ * webhook event or cron schedule. `undefined` for direct calls via
2164
+ * `app.call(...)` or HTTP POST without a dispatcher envelope.
2165
+ */
2166
+ trigger;
1833
2167
  constructor(params) {
1834
2168
  this.input = params.input;
1835
2169
  this.executionId = params.executionId;
@@ -1852,6 +2186,8 @@ var ReasonerContext = class {
1852
2186
  this.workflow = params.workflow;
1853
2187
  this.did = params.did;
1854
2188
  this.signal = params.signal ?? new AbortController().signal;
2189
+ this.costTracker = params.costTracker ?? ExecutionContext.getCurrent()?.costTracker ?? new CostTracker();
2190
+ this.trigger = params.trigger;
1855
2191
  }
1856
2192
  ai(prompt, options) {
1857
2193
  if (options?.tools) {
@@ -1875,6 +2211,7 @@ var ReasonerContext = class {
1875
2211
  maxTurns: options.maxTurns ?? config.maxTurns ?? 10,
1876
2212
  maxToolCalls: options.maxToolCalls ?? config.maxToolCalls ?? 25
1877
2213
  };
2214
+ const modelChoice = typeof this.aiClient.resolveModelChoice === "function" ? this.aiClient.resolveModelChoice(options) : void 0;
1878
2215
  return executeToolCallLoop(
1879
2216
  this.agent,
1880
2217
  prompt,
@@ -1882,7 +2219,8 @@ var ReasonerContext = class {
1882
2219
  mergedConfig,
1883
2220
  needsLazyHydration,
1884
2221
  () => this.aiClient.getModel(options),
1885
- options
2222
+ options,
2223
+ modelChoice
1886
2224
  );
1887
2225
  }
1888
2226
  aiStream(prompt, options) {
@@ -1947,7 +2285,8 @@ function getCurrentContext() {
1947
2285
  aiClient: agent.getAIClient(),
1948
2286
  memory: agent.getMemoryInterface(metadata),
1949
2287
  workflow: agent.getWorkflowReporter(metadata),
1950
- did: agent.getDidInterface(metadata, input)
2288
+ did: agent.getDidInterface(metadata, input),
2289
+ costTracker: execution.costTracker
1951
2290
  });
1952
2291
  }
1953
2292
 
@@ -2165,7 +2504,7 @@ var StatelessRateLimiter = class {
2165
2504
  }
2166
2505
  }
2167
2506
  async _sleep(delaySeconds) {
2168
- await new Promise((resolve2) => setTimeout(resolve2, delaySeconds * 1e3));
2507
+ await new Promise((resolve3) => setTimeout(resolve3, delaySeconds * 1e3));
2169
2508
  }
2170
2509
  _now() {
2171
2510
  return Date.now() / 1e3;
@@ -2209,6 +2548,26 @@ function toError(error) {
2209
2548
 
2210
2549
  // src/ai/AIClient.ts
2211
2550
  init_openrouterAttribution();
2551
+
2552
+ // src/ai/openrouterUsage.ts
2553
+ function withOpenRouterUsageInclude(baseFetch) {
2554
+ return (input, init) => {
2555
+ const impl = ((...args) => globalThis.fetch(...args));
2556
+ try {
2557
+ if (init && typeof init.body === "string") {
2558
+ const parsed = JSON.parse(init.body);
2559
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && parsed.usage === void 0) {
2560
+ parsed.usage = { include: true };
2561
+ return impl(input, { ...init, body: JSON.stringify(parsed) });
2562
+ }
2563
+ }
2564
+ } catch {
2565
+ }
2566
+ return impl(input, init);
2567
+ };
2568
+ }
2569
+
2570
+ // src/ai/AIClient.ts
2212
2571
  function repairJsonText(text2) {
2213
2572
  let cleaned = text2.trim();
2214
2573
  const codeBlockMatch = cleaned.match(/```(?:json)?\s*([\s\S]*?)```/);
@@ -2243,6 +2602,7 @@ var AIClient = class {
2243
2602
  };
2244
2603
  }
2245
2604
  async generate(prompt, options = {}) {
2605
+ const { provider, modelName } = this.resolveModelChoice(options);
2246
2606
  const model = this.buildModel(options);
2247
2607
  if (options.schema) {
2248
2608
  const schema = options.schema;
@@ -2257,6 +2617,7 @@ var AIClient = class {
2257
2617
  experimental_repairText: async ({ text: text2 }) => repairJsonText(text2)
2258
2618
  });
2259
2619
  const response2 = await this.withRateLimitRetry(call2);
2620
+ recordAiSdkUsage({ source: response2, model: modelName, provider });
2260
2621
  return response2.object;
2261
2622
  }
2262
2623
  const call = async () => generateText({
@@ -2267,8 +2628,13 @@ var AIClient = class {
2267
2628
  maxOutputTokens: options.maxTokens ?? this.config.maxTokens
2268
2629
  });
2269
2630
  const response = await this.withRateLimitRetry(call);
2631
+ recordAiSdkUsage({ source: response, model: modelName, provider });
2270
2632
  return response.text;
2271
2633
  }
2634
+ // NOTE: stream() usage is deliberately NOT captured. The AI SDK's
2635
+ // streamResult.usage/.totalUsage promises "automatically consume the
2636
+ // stream": attaching to them would force full background consumption of a
2637
+ // stream the caller may abandon early, changing stream semantics.
2272
2638
  async stream(prompt, options = {}) {
2273
2639
  const model = this.buildModel(options);
2274
2640
  const streamResult = streamText({
@@ -2307,9 +2673,19 @@ var AIClient = class {
2307
2673
  getModel(options = {}) {
2308
2674
  return this.buildModel(options);
2309
2675
  }
2676
+ /**
2677
+ * Resolve the effective provider/model pair for a request without building
2678
+ * the model. Used by usage tracking to attribute token/cost entries to the
2679
+ * model actually called.
2680
+ */
2681
+ resolveModelChoice(options = {}) {
2682
+ return {
2683
+ provider: options.provider ?? this.config.provider ?? "openai",
2684
+ modelName: options.model ?? this.config.model ?? "gpt-4o"
2685
+ };
2686
+ }
2310
2687
  buildModel(options) {
2311
- const provider = options.provider ?? this.config.provider ?? "openai";
2312
- const modelName = options.model ?? this.config.model ?? "gpt-4o";
2688
+ const { provider, modelName } = this.resolveModelChoice(options);
2313
2689
  const openRouterHeaders = this.openRouterHeaders(provider, modelName);
2314
2690
  switch (provider) {
2315
2691
  case "anthropic": {
@@ -2365,7 +2741,8 @@ var AIClient = class {
2365
2741
  const openrouter = createOpenAI({
2366
2742
  apiKey: this.config.apiKey,
2367
2743
  baseURL: this.config.baseUrl ?? "https://openrouter.ai/api/v1",
2368
- headers: openRouterHeaders
2744
+ headers: openRouterHeaders,
2745
+ fetch: withOpenRouterUsageInclude()
2369
2746
  });
2370
2747
  return openrouter.chat(modelName);
2371
2748
  }
@@ -2382,7 +2759,7 @@ var AIClient = class {
2382
2759
  const openai = createOpenAI({
2383
2760
  apiKey: this.config.apiKey,
2384
2761
  baseURL: this.config.baseUrl,
2385
- ...openRouterHeaders ? { headers: openRouterHeaders } : {}
2762
+ ...openRouterHeaders ? { headers: openRouterHeaders, fetch: withOpenRouterUsageInclude() } : {}
2386
2763
  });
2387
2764
  return openai(modelName);
2388
2765
  }
@@ -3088,6 +3465,7 @@ var AgentFieldClient = class {
3088
3465
  if (payload.result !== void 0) body.result = wrapResult(payload.result);
3089
3466
  if (payload.error !== void 0) body.error = payload.error;
3090
3467
  if (payload.errorDetails !== void 0) body.error_details = payload.errorDetails;
3468
+ if (payload.usage !== void 0) body.usage = payload.usage;
3091
3469
  const bodyStr = JSON.stringify(body);
3092
3470
  const authHeaders = this.didAuthenticator.signRequest(Buffer.from(bodyStr));
3093
3471
  for (let attempt = 0; attempt < maxRetries; attempt++) {
@@ -3289,7 +3667,7 @@ var AgentFieldClient = class {
3289
3667
  }
3290
3668
  };
3291
3669
  function sleep2(ms) {
3292
- return new Promise((resolve2) => setTimeout(resolve2, ms));
3670
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
3293
3671
  }
3294
3672
  async function safePauseCallback(cb) {
3295
3673
  if (!cb) return;
@@ -4748,6 +5126,44 @@ var Agent = class {
4748
5126
  }
4749
5127
  return this;
4750
5128
  }
5129
+ /**
5130
+ * Sugar for registering an event-triggered reasoner.
5131
+ *
5132
+ * Equivalent to:
5133
+ * ```ts
5134
+ * app.reasoner(name, handler, { triggers: [eventTrigger(spec)] });
5135
+ * ```
5136
+ *
5137
+ * The reasoner name defaults to `handler.name` when not provided.
5138
+ */
5139
+ onEvent(spec, handler, options) {
5140
+ const name = spec.name || handler.name || `on_${spec.source}`;
5141
+ const { name: _discarded, ...triggerSpec } = spec;
5142
+ const binding = eventTrigger(triggerSpec);
5143
+ return this.reasoner(name, handler, {
5144
+ ...options,
5145
+ triggers: [...options?.triggers ?? [], binding]
5146
+ });
5147
+ }
5148
+ /**
5149
+ * Sugar for registering a schedule-triggered (cron) reasoner.
5150
+ *
5151
+ * Equivalent to:
5152
+ * ```ts
5153
+ * app.reasoner(name, handler, { triggers: [scheduleTrigger({ cron })] });
5154
+ * ```
5155
+ *
5156
+ * The reasoner name defaults to `handler.name` when not provided.
5157
+ */
5158
+ onSchedule(cron, handler, options) {
5159
+ const name = options?.name || handler.name || "on_schedule";
5160
+ const binding = scheduleTrigger({ cron, timezone: options?.timezone });
5161
+ const { name: _discarded, timezone: _tz, ...restOptions } = options ?? {};
5162
+ return this.reasoner(name, handler, {
5163
+ ...restOptions,
5164
+ triggers: [...restOptions?.triggers ?? [], binding]
5165
+ });
5166
+ }
4751
5167
  skill(name, handler, options) {
4752
5168
  this.skills.register(name, handler, options);
4753
5169
  if (options?.requireRealtimeValidation) {
@@ -4801,7 +5217,53 @@ var Agent = class {
4801
5217
  }
4802
5218
  async harness(prompt, options) {
4803
5219
  const runner = await this.getHarnessRunner();
4804
- return runner.run(prompt, options ?? {});
5220
+ const result = await runner.run(prompt, options ?? {});
5221
+ this.recordHarnessUsage(result, options);
5222
+ return result;
5223
+ }
5224
+ /**
5225
+ * Record a harness run's token/cost usage into the current execution's
5226
+ * tracker. Mirrors the Python SDK's `_record_harness_usage`: a no-op when
5227
+ * the harness reported neither tokens nor cost (the common case for
5228
+ * providers that don't expose usage) so empty entries are never emitted;
5229
+ * cost is threaded even when tokens are unknown, and vice versa. Never
5230
+ * throws — usage capture is best-effort.
5231
+ */
5232
+ recordHarnessUsage(result, options) {
5233
+ try {
5234
+ const current = ExecutionContext.getCurrent();
5235
+ const tracker = current?.costTracker;
5236
+ if (!tracker) return;
5237
+ const inputTokens = result.inputTokens ?? 0;
5238
+ const outputTokens = result.outputTokens ?? 0;
5239
+ const cacheRead = result.cacheReadTokens ?? 0;
5240
+ const cacheCreation = result.cacheCreationTokens ?? 0;
5241
+ const reportedTotal = result.totalTokens ?? 0;
5242
+ const cost = typeof result.costUsd === "number" && Number.isFinite(result.costUsd) ? result.costUsd : null;
5243
+ if (!inputTokens && !outputTokens && !cacheRead && !cacheCreation && !reportedTotal && cost === null) {
5244
+ return;
5245
+ }
5246
+ const providerName = options?.provider ?? this.config.harnessConfig?.provider;
5247
+ const harnessName = providerName ? String(providerName).replace(/-/g, "_") : null;
5248
+ const modelName = String(
5249
+ result.model ?? options?.model ?? this.config.harnessConfig?.model ?? providerName ?? "harness"
5250
+ );
5251
+ tracker.record({
5252
+ model: modelName,
5253
+ inputTokens,
5254
+ outputTokens,
5255
+ totalTokens: reportedTotal || inputTokens + outputTokens,
5256
+ costUsd: cost,
5257
+ reasonerName: current.metadata.reasonerId ?? null,
5258
+ source: "harness",
5259
+ provider: deriveProvider(modelName),
5260
+ harness: harnessName,
5261
+ cacheReadTokens: cacheRead,
5262
+ cacheCreationTokens: cacheCreation,
5263
+ costSource: cost !== null ? "provider" : null
5264
+ });
5265
+ } catch {
5266
+ }
4805
5267
  }
4806
5268
  getMemoryInterface(metadata) {
4807
5269
  const defaultScope = this.config.memoryConfig?.defaultScope ?? "workflow";
@@ -4918,9 +5380,9 @@ var Agent = class {
4918
5380
  try {
4919
5381
  const result = await Promise.race([
4920
5382
  future,
4921
- new Promise((resolve2) => {
5383
+ new Promise((resolve3) => {
4922
5384
  timer = setTimeout(() => {
4923
- resolve2(
5385
+ resolve3(
4924
5386
  new ApprovalResult({
4925
5387
  decision: "expired",
4926
5388
  feedback: "timed out waiting for approval",
@@ -4973,8 +5435,8 @@ var Agent = class {
4973
5435
  const port = this.config.port ?? 8001;
4974
5436
  const host = this.config.host ?? "0.0.0.0";
4975
5437
  await this.agentFieldClient.heartbeat("starting");
4976
- await new Promise((resolve2, reject) => {
4977
- this.server = this.app.listen(port, host, () => resolve2()).on("error", reject);
5438
+ await new Promise((resolve3, reject) => {
5439
+ this.server = this.app.listen(port, host, () => resolve3()).on("error", reject);
4978
5440
  });
4979
5441
  this.memoryEventClient.start();
4980
5442
  this.startHeartbeat();
@@ -4984,20 +5446,27 @@ var Agent = class {
4984
5446
  clearInterval(this.heartbeatTimer);
4985
5447
  }
4986
5448
  this.pauseManager.cancelAll();
4987
- await new Promise((resolve2, reject) => {
5449
+ await new Promise((resolve3, reject) => {
4988
5450
  this.server?.close((err) => {
4989
5451
  if (err) reject(err);
4990
- else resolve2();
5452
+ else resolve3();
4991
5453
  });
4992
5454
  });
4993
5455
  this.memoryEventClient.stop();
4994
5456
  }
4995
5457
  async call(target, input) {
4996
5458
  const { agentId, name } = this.parseTarget(target);
4997
- const parentMetadata = ExecutionContext.getCurrent()?.metadata;
5459
+ const parentContext = ExecutionContext.getCurrent();
5460
+ const parentMetadata = parentContext?.metadata;
4998
5461
  if (!agentId || agentId === this.config.nodeId) {
4999
5462
  const local = this.reasoners.get(name);
5000
5463
  if (!local) throw new Error(`Reasoner not found: ${name}`);
5464
+ const { input: unwrappedInput, triggerContext } = unwrapEnvelope(input);
5465
+ let resolvedInput = unwrappedInput;
5466
+ if (triggerContext) {
5467
+ const bindings = local.options?.triggers ?? [];
5468
+ resolvedInput = applyTriggerTransform(triggerContext, bindings, unwrappedInput);
5469
+ }
5001
5470
  const runId2 = parentMetadata?.runId ?? parentMetadata?.executionId ?? randomUUID();
5002
5471
  const rootWorkflowId2 = parentMetadata?.rootWorkflowId ?? parentMetadata?.workflowId ?? runId2;
5003
5472
  const metadata = {
@@ -5012,14 +5481,18 @@ var Agent = class {
5012
5481
  const dummyReq = {};
5013
5482
  const dummyRes = {};
5014
5483
  const execCtx = new ExecutionContext({
5015
- input,
5484
+ input: resolvedInput,
5016
5485
  metadata: {
5017
5486
  ...metadata,
5018
5487
  executionId: metadata.executionId ?? randomUUID()
5019
5488
  },
5020
5489
  req: dummyReq,
5021
5490
  res: dummyRes,
5022
- agent: this
5491
+ agent: this,
5492
+ // Nested local calls inherit the parent's cost tracker so their LLM /
5493
+ // harness usage rolls up into the parent execution's usage report
5494
+ // (entries still carry the child reasoner's name).
5495
+ costTracker: parentContext?.costTracker
5023
5496
  });
5024
5497
  const startTime = Date.now();
5025
5498
  this.executionLogger.system("agent.call.started", "Local agent call started", {
@@ -5069,7 +5542,7 @@ var Agent = class {
5069
5542
  try {
5070
5543
  const result = await local.handler(
5071
5544
  new ReasonerContext({
5072
- input,
5545
+ input: resolvedInput,
5073
5546
  executionId: execCtx.metadata.executionId,
5074
5547
  runId: execCtx.metadata.runId,
5075
5548
  sessionId: execCtx.metadata.sessionId,
@@ -5088,7 +5561,9 @@ var Agent = class {
5088
5561
  aiClient: this.aiClient,
5089
5562
  memory: this.getMemoryInterface(execCtx.metadata),
5090
5563
  workflow: this.getWorkflowReporter(execCtx.metadata),
5091
- did: this.getDidInterface(execCtx.metadata, input, name)
5564
+ did: this.getDidInterface(execCtx.metadata, resolvedInput, name),
5565
+ trigger: triggerContext,
5566
+ costTracker: execCtx.costTracker
5092
5567
  })
5093
5568
  );
5094
5569
  this.executionLogger.system("reasoner.completed", "Reasoner execution completed", {
@@ -5790,6 +6265,8 @@ var Agent = class {
5790
6265
  watchdog.catch(() => {
5791
6266
  });
5792
6267
  const completedAt = () => (/* @__PURE__ */ new Date()).toISOString();
6268
+ const usageTracker = new CostTracker();
6269
+ const usage = () => usageSummaryOrNull(usageTracker) ?? void 0;
5793
6270
  try {
5794
6271
  const result = await Promise.race([
5795
6272
  this.runReasoner(reasoner, {
@@ -5797,7 +6274,8 @@ var Agent = class {
5797
6274
  input: params.input,
5798
6275
  metadata: params.metadata,
5799
6276
  respond: false,
5800
- controller
6277
+ controller,
6278
+ costTracker: usageTracker
5801
6279
  }),
5802
6280
  watchdog
5803
6281
  ]);
@@ -5806,7 +6284,8 @@ var Agent = class {
5806
6284
  result,
5807
6285
  durationMs: Date.now() - start,
5808
6286
  completedAt: completedAt(),
5809
- reasoner: reasonerName
6287
+ reasoner: reasonerName,
6288
+ usage: usage()
5810
6289
  });
5811
6290
  } catch (err) {
5812
6291
  const durationMs = Date.now() - start;
@@ -5817,7 +6296,8 @@ var Agent = class {
5817
6296
  errorDetails: { reason: "reasoner_timeout" },
5818
6297
  durationMs,
5819
6298
  completedAt: completedAt(),
5820
- reasoner: reasonerName
6299
+ reasoner: reasonerName,
6300
+ usage: usage()
5821
6301
  });
5822
6302
  } else if (controller.signal.aborted) {
5823
6303
  await this.agentFieldClient.reportExecutionResult(executionId, {
@@ -5826,7 +6306,8 @@ var Agent = class {
5826
6306
  errorDetails: { reason: "cancelled" },
5827
6307
  durationMs,
5828
6308
  completedAt: completedAt(),
5829
- reasoner: reasonerName
6309
+ reasoner: reasonerName,
6310
+ usage: usage()
5830
6311
  });
5831
6312
  } else {
5832
6313
  await this.agentFieldClient.reportExecutionResult(executionId, {
@@ -5835,7 +6316,8 @@ var Agent = class {
5835
6316
  errorDetails: err?.responseData,
5836
6317
  durationMs,
5837
6318
  completedAt: completedAt(),
5838
- reasoner: reasonerName
6319
+ reasoner: reasonerName,
6320
+ usage: usage()
5839
6321
  });
5840
6322
  }
5841
6323
  } finally {
@@ -5851,12 +6333,20 @@ var Agent = class {
5851
6333
  rootWorkflowId: params.metadata.rootWorkflowId ?? params.metadata.workflowId ?? params.metadata.runId ?? params.metadata.executionId,
5852
6334
  reasonerId: params.metadata.reasonerId ?? params.targetName
5853
6335
  };
6336
+ const costTracker = params.costTracker ?? new CostTracker();
6337
+ const { input: unwrappedInput, triggerContext } = unwrapEnvelope(params.input);
6338
+ let resolvedInput = unwrappedInput;
6339
+ if (triggerContext) {
6340
+ const bindings = reasoner.options?.triggers ?? [];
6341
+ resolvedInput = applyTriggerTransform(triggerContext, bindings, unwrappedInput);
6342
+ }
5854
6343
  const execCtx = new ExecutionContext({
5855
- input: params.input,
6344
+ input: resolvedInput,
5856
6345
  metadata: executionMetadata,
5857
6346
  req,
5858
6347
  res,
5859
- agent: this
6348
+ agent: this,
6349
+ costTracker
5860
6350
  });
5861
6351
  const { controller, release } = this.cancelRegistry.register(
5862
6352
  executionMetadata.executionId,
@@ -5881,7 +6371,7 @@ var Agent = class {
5881
6371
  });
5882
6372
  try {
5883
6373
  const ctx = new ReasonerContext({
5884
- input: params.input,
6374
+ input: resolvedInput,
5885
6375
  executionId: executionMetadata.executionId,
5886
6376
  runId: executionMetadata.runId,
5887
6377
  sessionId: executionMetadata.sessionId,
@@ -5900,8 +6390,10 @@ var Agent = class {
5900
6390
  aiClient: this.aiClient,
5901
6391
  memory: this.getMemoryInterface(executionMetadata),
5902
6392
  workflow: this.getWorkflowReporter(executionMetadata),
5903
- did: this.getDidInterface(executionMetadata, params.input, params.targetName),
5904
- signal: controller.signal
6393
+ did: this.getDidInterface(executionMetadata, resolvedInput, params.targetName),
6394
+ signal: controller.signal,
6395
+ trigger: triggerContext,
6396
+ costTracker
5905
6397
  });
5906
6398
  const result = await reasoner.handler(ctx);
5907
6399
  this.executionLogger.system("reasoner.completed", "Reasoner execution completed", {
@@ -5920,7 +6412,7 @@ var Agent = class {
5920
6412
  rootWorkflowId: executionMetadata.rootWorkflowId
5921
6413
  });
5922
6414
  if (params.respond && params.res) {
5923
- params.res.json(result);
6415
+ params.res.json(attachUsageToSyncResult(result, costTracker));
5924
6416
  return;
5925
6417
  }
5926
6418
  return result;
@@ -6146,7 +6638,7 @@ var Agent = class {
6146
6638
  const timeoutMs = 5 * 60 * 1e3;
6147
6639
  const deadline = Date.now() + timeoutMs;
6148
6640
  while (Date.now() < deadline) {
6149
- await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
6641
+ await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
6150
6642
  try {
6151
6643
  const node = await this.agentFieldClient.getNode(this.config.nodeId);
6152
6644
  const status = node?.lifecycle_status;
@@ -7575,14 +8067,62 @@ var OpenRouterMediaProvider = class {
7575
8067
  }
7576
8068
  };
7577
8069
  function sleep3(ms) {
7578
- return new Promise((resolve2) => setTimeout(resolve2, ms));
8070
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
7579
8071
  }
7580
8072
 
7581
8073
  // src/harness/index.ts
7582
8074
  init_types();
7583
8075
  init_factory();
7584
8076
  init_runner();
8077
+ async function simulateTrigger(handler, options) {
8078
+ const body = options.body ?? {};
8079
+ const triggerContext = {
8080
+ triggerId: options.triggerId ?? `trg_sim_${randomUUID().slice(0, 12)}`,
8081
+ source: options.source,
8082
+ eventType: options.eventType ?? "",
8083
+ eventId: options.eventId ?? `evt_sim_${randomUUID().slice(0, 12)}`,
8084
+ idempotencyKey: options.idempotencyKey ?? `idem_sim_${randomUUID().slice(0, 12)}`,
8085
+ receivedAt: options.receivedAt ?? /* @__PURE__ */ new Date(),
8086
+ vcId: options.vcId
8087
+ };
8088
+ let resolvedInput = body;
8089
+ if (options.bindings && options.bindings.length > 0) {
8090
+ resolvedInput = applyTriggerTransform(triggerContext, options.bindings, body);
8091
+ }
8092
+ const ctx = {
8093
+ input: resolvedInput,
8094
+ trigger: triggerContext,
8095
+ executionId: `exec_sim_${randomUUID().slice(0, 12)}`
8096
+ };
8097
+ return handler(ctx);
8098
+ }
8099
+ async function simulateSchedule(handler, options) {
8100
+ return simulateTrigger(handler, {
8101
+ source: "cron",
8102
+ body: options?.cron ? { cron: options.cron } : {},
8103
+ eventType: "tick",
8104
+ receivedAt: options?.receivedAt,
8105
+ bindings: options?.bindings
8106
+ });
8107
+ }
8108
+ function loadFixture(source) {
8109
+ const dir = dirname(fileURLToPath(import.meta.url));
8110
+ const candidates = [
8111
+ resolve(dir, "fixtures", `${source}.json`),
8112
+ resolve(dir, "..", "src", "triggers", "fixtures", `${source}.json`)
8113
+ ];
8114
+ for (const fixturePath of candidates) {
8115
+ try {
8116
+ const content = readFileSync(fixturePath, "utf-8");
8117
+ return JSON.parse(content);
8118
+ } catch {
8119
+ }
8120
+ }
8121
+ throw new Error(
8122
+ `No fixture for source="${source}". Looked at ${candidates.join(", ")}`
8123
+ );
8124
+ }
7585
8125
 
7586
- export { ACTIVE_STATUSES, AIClient, Agent, AgentRouter, ApprovalClient, ApprovalResult, Audio, CANONICAL_STATUSES, DIDAuthenticator, DidClient, DidInterface, DidManager, ExecutionContext, ExecutionLogger, ExecutionStatus, File, HEADER_CALLER_DID, HEADER_DID_NONCE, HEADER_DID_SIGNATURE, HEADER_DID_TIMESTAMP, HarnessRunner, Image, JWE_ALG, JWE_ENC, KEY_AGREEMENT_TYPE, MediaProviderError, MediaRouter, MemoryClient, MemoryClientBase, MemoryEventClient, MemoryInterface, MultimodalResponse, OpenRouterMediaProvider, PauseClock, PauseManager, PayloadEncryptionError, RateLimitError, RealtimeSession, ReasonerContext, SUPPORTED_PROVIDERS, SUPPORTED_SESSION_TRANSPORTS, SessionTransportError, SkillContext, StatelessRateLimiter, TERMINAL_STATUSES, Text, Video, WorkflowReporter, audioFromBase64, audioFromBuffer, audioFromFile, audioFromUrl, buildProvider, buildSessionDefinition, buildToolConfig, capabilitiesToTools, capabilityToMetadataTool, capabilityToTool, createExecutionLogger, createHarnessResult, createMetrics, createMultimodalResponse, createRawResult, decrypt, decryptToString, encryptForDid, encryptToJwk, eventTrigger, executeToolCallLoop, extractKeyAgreementJwk, fileFromBase64, fileFromBuffer, fileFromPath, fileFromUrl, generateX25519KeyPair, getCurrentContext, getCurrentSkillContext, imageFromBase64, imageFromBuffer, imageFromFile, imageFromUrl, installApprovalWebhookRoute, isActive, isExecutionLogBatchPayload, isTerminal, normalizeExecutionLogEntry, normalizeSessionTransportValue, normalizeStatus, scheduleTrigger, serializeExecutionLogEntry, text, triggerToPayload, validateSessionTransport, videoFromBase64, videoFromBuffer, videoFromFile, videoFromUrl };
8126
+ export { ACTIVE_STATUSES, AIClient, Agent, AgentRouter, ApprovalClient, ApprovalResult, Audio, CANONICAL_STATUSES, CostTracker, DIDAuthenticator, DidClient, DidInterface, DidManager, ExecutionContext, ExecutionLogger, ExecutionStatus, File, HEADER_CALLER_DID, HEADER_DID_NONCE, HEADER_DID_SIGNATURE, HEADER_DID_TIMESTAMP, HarnessRunner, Image, JWE_ALG, JWE_ENC, KEY_AGREEMENT_TYPE, MediaProviderError, MediaRouter, MemoryClient, MemoryClientBase, MemoryEventClient, MemoryInterface, MultimodalResponse, OpenRouterMediaProvider, PauseClock, PauseManager, PayloadEncryptionError, RateLimitError, RealtimeSession, ReasonerContext, SUPPORTED_PROVIDERS, SUPPORTED_SESSION_TRANSPORTS, SessionTransportError, SkillContext, StatelessRateLimiter, TERMINAL_STATUSES, Text, USAGE_ENVELOPE_KEY, Video, WorkflowReporter, applyTriggerTransform, attachUsageToSyncResult, audioFromBase64, audioFromBuffer, audioFromFile, audioFromUrl, buildProvider, buildSessionDefinition, buildToolConfig, capabilitiesToTools, capabilityToMetadataTool, capabilityToTool, createExecutionLogger, createHarnessResult, createMetrics, createMultimodalResponse, createRawResult, decrypt, decryptToString, deriveProvider, encryptForDid, encryptToJwk, eventTrigger, executeToolCallLoop, extractKeyAgreementJwk, fileFromBase64, fileFromBuffer, fileFromPath, fileFromUrl, generateX25519KeyPair, getCurrentContext, getCurrentSkillContext, imageFromBase64, imageFromBuffer, imageFromFile, imageFromUrl, installApprovalWebhookRoute, isActive, isExecutionLogBatchPayload, isTerminal, isTriggerEnvelope, loadFixture, normalizeExecutionLogEntry, normalizeSessionTransportValue, normalizeStatus, scheduleTrigger, serializeExecutionLogEntry, simulateSchedule, simulateTrigger, text, triggerToPayload, unwrapEnvelope, usageSummaryOrNull, validateSessionTransport, videoFromBase64, videoFromBuffer, videoFromFile, videoFromUrl };
7587
8127
  //# sourceMappingURL=index.js.map
7588
8128
  //# sourceMappingURL=index.js.map