@agentfield/sdk 0.1.111-rc.1 → 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
@@ -155,14 +155,14 @@ function getZodConverter() {
155
155
  }
156
156
  return zodConverter;
157
157
  }
158
- function isRecord(value) {
158
+ function isRecord2(value) {
159
159
  return typeof value === "object" && value !== null && !Array.isArray(value);
160
160
  }
161
161
  function hasJsonSchema(value) {
162
- return isRecord(value) && typeof value.jsonSchema === "function";
162
+ return isRecord2(value) && typeof value.jsonSchema === "function";
163
163
  }
164
164
  function hasParse(value) {
165
- return isRecord(value) && typeof value.parse === "function";
165
+ return isRecord2(value) && typeof value.parse === "function";
166
166
  }
167
167
  function estimateTokens(text2) {
168
168
  return Math.floor(text2.length / 4);
@@ -191,7 +191,7 @@ function getSchemaPath(cwd) {
191
191
  return path2__default.join(cwd, SCHEMA_FILENAME);
192
192
  }
193
193
  function schemaToJsonSchema(schema) {
194
- if (isRecord(schema)) {
194
+ if (isRecord2(schema)) {
195
195
  if ("type" in schema || "properties" in schema || "$schema" in schema) {
196
196
  return schema;
197
197
  }
@@ -339,7 +339,7 @@ var claude_exports = {};
339
339
  __export(claude_exports, {
340
340
  ClaudeCodeProvider: () => ClaudeCodeProvider
341
341
  });
342
- function isRecord2(value) {
342
+ function isRecord3(value) {
343
343
  return typeof value === "object" && value !== null;
344
344
  }
345
345
  function getString(record, key) {
@@ -383,10 +383,16 @@ var init_claude = __esm({
383
383
  let totalCost;
384
384
  let numTurns = 0;
385
385
  let sessionId = "";
386
+ let usageObj;
387
+ let inputTokens;
388
+ let outputTokens;
389
+ let cacheReadTokens;
390
+ let cacheCreationTokens;
391
+ let modelName;
386
392
  const startApi = Date.now();
387
393
  try {
388
394
  for await (const msg of sdk.query({ prompt, options: agentOptions })) {
389
- const msgObj = isRecord2(msg) ? msg : { raw: String(msg) };
395
+ const msgObj = isRecord3(msg) ? msg : { raw: String(msg) };
390
396
  messages.push(msgObj);
391
397
  const msgType = getString(msgObj, "type") ?? "";
392
398
  if (msgType === "result") {
@@ -405,16 +411,28 @@ var init_claude = __esm({
405
411
  }
406
412
  const turns = getNumber(msgObj, "num_turns");
407
413
  numTurns = turns === void 0 ? messages.length : Math.trunc(turns);
408
- } 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;
409
427
  let content = msgObj.content;
410
- if (content === void 0 && isRecord2(msgObj.message)) {
428
+ if (content === void 0 && isRecord3(msgObj.message)) {
411
429
  content = msgObj.message.content;
412
430
  }
413
431
  if (typeof content === "string") {
414
432
  resultText = content;
415
433
  } else if (Array.isArray(content)) {
416
434
  for (const block of content) {
417
- if (isRecord2(block) && block.type === "text" && typeof block.text === "string") {
435
+ if (isRecord3(block) && block.type === "text" && typeof block.text === "string") {
418
436
  resultText = block.text;
419
437
  }
420
438
  }
@@ -428,7 +446,13 @@ var init_claude = __esm({
428
446
  durationApiMs: Date.now() - startApi,
429
447
  numTurns,
430
448
  totalCostUsd: totalCost,
431
- sessionId
449
+ sessionId,
450
+ usage: usageObj,
451
+ inputTokens,
452
+ outputTokens,
453
+ cacheReadTokens,
454
+ cacheCreationTokens,
455
+ model: modelName
432
456
  }),
433
457
  isError: false
434
458
  });
@@ -842,6 +866,10 @@ var runner_exports = {};
842
866
  __export(runner_exports, {
843
867
  HarnessRunner: () => HarnessRunner
844
868
  });
869
+ function tokenMetrics(raw) {
870
+ const { inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, totalTokens, model } = raw.metrics;
871
+ return { inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, totalTokens, model };
872
+ }
845
873
  var TRANSIENT_PATTERNS, HarnessRunner;
846
874
  var init_runner = __esm({
847
875
  "src/harness/runner.ts"() {
@@ -892,7 +920,8 @@ var init_runner = __esm({
892
920
  numTurns: raw.metrics.numTurns,
893
921
  durationMs: Date.now() - startTime,
894
922
  sessionId: raw.metrics.sessionId,
895
- messages: raw.messages
923
+ messages: raw.messages,
924
+ ...tokenMetrics(raw)
896
925
  });
897
926
  } finally {
898
927
  if (schema !== void 0) {
@@ -985,7 +1014,8 @@ var init_runner = __esm({
985
1014
  numTurns: raw.metrics.numTurns,
986
1015
  durationMs: Date.now() - startTime,
987
1016
  sessionId: raw.metrics.sessionId,
988
- messages: raw.messages
1017
+ messages: raw.messages,
1018
+ ...tokenMetrics(raw)
989
1019
  });
990
1020
  }
991
1021
  return createHarnessResult({
@@ -996,7 +1026,8 @@ var init_runner = __esm({
996
1026
  numTurns: raw.metrics.numTurns,
997
1027
  durationMs: Date.now() - startTime,
998
1028
  sessionId: raw.metrics.sessionId,
999
- messages: raw.messages
1029
+ messages: raw.messages,
1030
+ ...tokenMetrics(raw)
1000
1031
  });
1001
1032
  }
1002
1033
  async buildProvider(providerName, options) {
@@ -1553,6 +1584,134 @@ function applyTriggerTransform(triggerContext, bindings, input) {
1553
1584
  }
1554
1585
  return input;
1555
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
1556
1715
  var store = new AsyncLocalStorage();
1557
1716
  var ExecutionContext = class {
1558
1717
  input;
@@ -1560,12 +1719,21 @@ var ExecutionContext = class {
1560
1719
  req;
1561
1720
  res;
1562
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;
1563
1730
  constructor(params) {
1564
1731
  this.input = params.input;
1565
1732
  this.metadata = params.metadata;
1566
1733
  this.req = params.req;
1567
1734
  this.res = params.res;
1568
1735
  this.agent = params.agent;
1736
+ this.costTracker = params.costTracker ?? new CostTracker();
1569
1737
  }
1570
1738
  get logger() {
1571
1739
  return this.agent.getExecutionLogger();
@@ -1577,6 +1745,79 @@ var ExecutionContext = class {
1577
1745
  return store.getStore();
1578
1746
  }
1579
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
1580
1821
  var DEFAULT_MAX_TURNS = 10;
1581
1822
  var DEFAULT_MAX_TOOL_CALLS = 25;
1582
1823
  var DEFAULT_HEALTH_STATUS = void 0;
@@ -1794,9 +2035,15 @@ function wrapToolsWithObservability(toolMap, agent, trace, maxToolCalls, getCurr
1794
2035
  }
1795
2036
  return { tools: observableTools, getTotalCalls: () => totalCalls };
1796
2037
  }
1797
- async function executeToolCallLoop(agent, prompt, toolMap, config, needsLazyHydration, buildModel, options = {}) {
2038
+ async function executeToolCallLoop(agent, prompt, toolMap, config, needsLazyHydration, buildModel, options = {}, modelChoice) {
1798
2039
  const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
1799
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
+ };
1800
2047
  const trace = {
1801
2048
  calls: [],
1802
2049
  totalTurns: 0,
@@ -1823,6 +2070,7 @@ async function executeToolCallLoop(agent, prompt, toolMap, config, needsLazyHydr
1823
2070
  stopWhen: stepCountIs(1)
1824
2071
  // Stop after LLM's first response (tool selection)
1825
2072
  });
2073
+ recordLoopUsage(selectionResult);
1826
2074
  const selectedNames = /* @__PURE__ */ new Set();
1827
2075
  for (const step of selectionResult.steps) {
1828
2076
  for (const tc of step.toolCalls) {
@@ -1866,6 +2114,7 @@ async function executeToolCallLoop(agent, prompt, toolMap, config, needsLazyHydr
1866
2114
  trace.totalTurns = currentTurn;
1867
2115
  }
1868
2116
  });
2117
+ recordLoopUsage(result);
1869
2118
  trace.finalResponse = result.text;
1870
2119
  trace.totalTurns = result.steps.length;
1871
2120
  return { text: result.text, trace };
@@ -1893,6 +2142,13 @@ var ReasonerContext = class {
1893
2142
  memory;
1894
2143
  workflow;
1895
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;
1896
2152
  /**
1897
2153
  * AbortSignal that fires when the control plane cancels this execution
1898
2154
  * (per-execution cancel, the bottom-up cancel-tree endpoint, or any
@@ -1930,6 +2186,7 @@ var ReasonerContext = class {
1930
2186
  this.workflow = params.workflow;
1931
2187
  this.did = params.did;
1932
2188
  this.signal = params.signal ?? new AbortController().signal;
2189
+ this.costTracker = params.costTracker ?? ExecutionContext.getCurrent()?.costTracker ?? new CostTracker();
1933
2190
  this.trigger = params.trigger;
1934
2191
  }
1935
2192
  ai(prompt, options) {
@@ -1954,6 +2211,7 @@ var ReasonerContext = class {
1954
2211
  maxTurns: options.maxTurns ?? config.maxTurns ?? 10,
1955
2212
  maxToolCalls: options.maxToolCalls ?? config.maxToolCalls ?? 25
1956
2213
  };
2214
+ const modelChoice = typeof this.aiClient.resolveModelChoice === "function" ? this.aiClient.resolveModelChoice(options) : void 0;
1957
2215
  return executeToolCallLoop(
1958
2216
  this.agent,
1959
2217
  prompt,
@@ -1961,7 +2219,8 @@ var ReasonerContext = class {
1961
2219
  mergedConfig,
1962
2220
  needsLazyHydration,
1963
2221
  () => this.aiClient.getModel(options),
1964
- options
2222
+ options,
2223
+ modelChoice
1965
2224
  );
1966
2225
  }
1967
2226
  aiStream(prompt, options) {
@@ -2026,7 +2285,8 @@ function getCurrentContext() {
2026
2285
  aiClient: agent.getAIClient(),
2027
2286
  memory: agent.getMemoryInterface(metadata),
2028
2287
  workflow: agent.getWorkflowReporter(metadata),
2029
- did: agent.getDidInterface(metadata, input)
2288
+ did: agent.getDidInterface(metadata, input),
2289
+ costTracker: execution.costTracker
2030
2290
  });
2031
2291
  }
2032
2292
 
@@ -2288,6 +2548,26 @@ function toError(error) {
2288
2548
 
2289
2549
  // src/ai/AIClient.ts
2290
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
2291
2571
  function repairJsonText(text2) {
2292
2572
  let cleaned = text2.trim();
2293
2573
  const codeBlockMatch = cleaned.match(/```(?:json)?\s*([\s\S]*?)```/);
@@ -2322,6 +2602,7 @@ var AIClient = class {
2322
2602
  };
2323
2603
  }
2324
2604
  async generate(prompt, options = {}) {
2605
+ const { provider, modelName } = this.resolveModelChoice(options);
2325
2606
  const model = this.buildModel(options);
2326
2607
  if (options.schema) {
2327
2608
  const schema = options.schema;
@@ -2336,6 +2617,7 @@ var AIClient = class {
2336
2617
  experimental_repairText: async ({ text: text2 }) => repairJsonText(text2)
2337
2618
  });
2338
2619
  const response2 = await this.withRateLimitRetry(call2);
2620
+ recordAiSdkUsage({ source: response2, model: modelName, provider });
2339
2621
  return response2.object;
2340
2622
  }
2341
2623
  const call = async () => generateText({
@@ -2346,8 +2628,13 @@ var AIClient = class {
2346
2628
  maxOutputTokens: options.maxTokens ?? this.config.maxTokens
2347
2629
  });
2348
2630
  const response = await this.withRateLimitRetry(call);
2631
+ recordAiSdkUsage({ source: response, model: modelName, provider });
2349
2632
  return response.text;
2350
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.
2351
2638
  async stream(prompt, options = {}) {
2352
2639
  const model = this.buildModel(options);
2353
2640
  const streamResult = streamText({
@@ -2386,9 +2673,19 @@ var AIClient = class {
2386
2673
  getModel(options = {}) {
2387
2674
  return this.buildModel(options);
2388
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
+ }
2389
2687
  buildModel(options) {
2390
- const provider = options.provider ?? this.config.provider ?? "openai";
2391
- const modelName = options.model ?? this.config.model ?? "gpt-4o";
2688
+ const { provider, modelName } = this.resolveModelChoice(options);
2392
2689
  const openRouterHeaders = this.openRouterHeaders(provider, modelName);
2393
2690
  switch (provider) {
2394
2691
  case "anthropic": {
@@ -2444,7 +2741,8 @@ var AIClient = class {
2444
2741
  const openrouter = createOpenAI({
2445
2742
  apiKey: this.config.apiKey,
2446
2743
  baseURL: this.config.baseUrl ?? "https://openrouter.ai/api/v1",
2447
- headers: openRouterHeaders
2744
+ headers: openRouterHeaders,
2745
+ fetch: withOpenRouterUsageInclude()
2448
2746
  });
2449
2747
  return openrouter.chat(modelName);
2450
2748
  }
@@ -2461,7 +2759,7 @@ var AIClient = class {
2461
2759
  const openai = createOpenAI({
2462
2760
  apiKey: this.config.apiKey,
2463
2761
  baseURL: this.config.baseUrl,
2464
- ...openRouterHeaders ? { headers: openRouterHeaders } : {}
2762
+ ...openRouterHeaders ? { headers: openRouterHeaders, fetch: withOpenRouterUsageInclude() } : {}
2465
2763
  });
2466
2764
  return openai(modelName);
2467
2765
  }
@@ -3167,6 +3465,7 @@ var AgentFieldClient = class {
3167
3465
  if (payload.result !== void 0) body.result = wrapResult(payload.result);
3168
3466
  if (payload.error !== void 0) body.error = payload.error;
3169
3467
  if (payload.errorDetails !== void 0) body.error_details = payload.errorDetails;
3468
+ if (payload.usage !== void 0) body.usage = payload.usage;
3170
3469
  const bodyStr = JSON.stringify(body);
3171
3470
  const authHeaders = this.didAuthenticator.signRequest(Buffer.from(bodyStr));
3172
3471
  for (let attempt = 0; attempt < maxRetries; attempt++) {
@@ -4918,7 +5217,53 @@ var Agent = class {
4918
5217
  }
4919
5218
  async harness(prompt, options) {
4920
5219
  const runner = await this.getHarnessRunner();
4921
- 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
+ }
4922
5267
  }
4923
5268
  getMemoryInterface(metadata) {
4924
5269
  const defaultScope = this.config.memoryConfig?.defaultScope ?? "workflow";
@@ -5111,7 +5456,8 @@ var Agent = class {
5111
5456
  }
5112
5457
  async call(target, input) {
5113
5458
  const { agentId, name } = this.parseTarget(target);
5114
- const parentMetadata = ExecutionContext.getCurrent()?.metadata;
5459
+ const parentContext = ExecutionContext.getCurrent();
5460
+ const parentMetadata = parentContext?.metadata;
5115
5461
  if (!agentId || agentId === this.config.nodeId) {
5116
5462
  const local = this.reasoners.get(name);
5117
5463
  if (!local) throw new Error(`Reasoner not found: ${name}`);
@@ -5142,7 +5488,11 @@ var Agent = class {
5142
5488
  },
5143
5489
  req: dummyReq,
5144
5490
  res: dummyRes,
5145
- 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
5146
5496
  });
5147
5497
  const startTime = Date.now();
5148
5498
  this.executionLogger.system("agent.call.started", "Local agent call started", {
@@ -5212,7 +5562,8 @@ var Agent = class {
5212
5562
  memory: this.getMemoryInterface(execCtx.metadata),
5213
5563
  workflow: this.getWorkflowReporter(execCtx.metadata),
5214
5564
  did: this.getDidInterface(execCtx.metadata, resolvedInput, name),
5215
- trigger: triggerContext
5565
+ trigger: triggerContext,
5566
+ costTracker: execCtx.costTracker
5216
5567
  })
5217
5568
  );
5218
5569
  this.executionLogger.system("reasoner.completed", "Reasoner execution completed", {
@@ -5914,6 +6265,8 @@ var Agent = class {
5914
6265
  watchdog.catch(() => {
5915
6266
  });
5916
6267
  const completedAt = () => (/* @__PURE__ */ new Date()).toISOString();
6268
+ const usageTracker = new CostTracker();
6269
+ const usage = () => usageSummaryOrNull(usageTracker) ?? void 0;
5917
6270
  try {
5918
6271
  const result = await Promise.race([
5919
6272
  this.runReasoner(reasoner, {
@@ -5921,7 +6274,8 @@ var Agent = class {
5921
6274
  input: params.input,
5922
6275
  metadata: params.metadata,
5923
6276
  respond: false,
5924
- controller
6277
+ controller,
6278
+ costTracker: usageTracker
5925
6279
  }),
5926
6280
  watchdog
5927
6281
  ]);
@@ -5930,7 +6284,8 @@ var Agent = class {
5930
6284
  result,
5931
6285
  durationMs: Date.now() - start,
5932
6286
  completedAt: completedAt(),
5933
- reasoner: reasonerName
6287
+ reasoner: reasonerName,
6288
+ usage: usage()
5934
6289
  });
5935
6290
  } catch (err) {
5936
6291
  const durationMs = Date.now() - start;
@@ -5941,7 +6296,8 @@ var Agent = class {
5941
6296
  errorDetails: { reason: "reasoner_timeout" },
5942
6297
  durationMs,
5943
6298
  completedAt: completedAt(),
5944
- reasoner: reasonerName
6299
+ reasoner: reasonerName,
6300
+ usage: usage()
5945
6301
  });
5946
6302
  } else if (controller.signal.aborted) {
5947
6303
  await this.agentFieldClient.reportExecutionResult(executionId, {
@@ -5950,7 +6306,8 @@ var Agent = class {
5950
6306
  errorDetails: { reason: "cancelled" },
5951
6307
  durationMs,
5952
6308
  completedAt: completedAt(),
5953
- reasoner: reasonerName
6309
+ reasoner: reasonerName,
6310
+ usage: usage()
5954
6311
  });
5955
6312
  } else {
5956
6313
  await this.agentFieldClient.reportExecutionResult(executionId, {
@@ -5959,7 +6316,8 @@ var Agent = class {
5959
6316
  errorDetails: err?.responseData,
5960
6317
  durationMs,
5961
6318
  completedAt: completedAt(),
5962
- reasoner: reasonerName
6319
+ reasoner: reasonerName,
6320
+ usage: usage()
5963
6321
  });
5964
6322
  }
5965
6323
  } finally {
@@ -5975,6 +6333,7 @@ var Agent = class {
5975
6333
  rootWorkflowId: params.metadata.rootWorkflowId ?? params.metadata.workflowId ?? params.metadata.runId ?? params.metadata.executionId,
5976
6334
  reasonerId: params.metadata.reasonerId ?? params.targetName
5977
6335
  };
6336
+ const costTracker = params.costTracker ?? new CostTracker();
5978
6337
  const { input: unwrappedInput, triggerContext } = unwrapEnvelope(params.input);
5979
6338
  let resolvedInput = unwrappedInput;
5980
6339
  if (triggerContext) {
@@ -5986,7 +6345,8 @@ var Agent = class {
5986
6345
  metadata: executionMetadata,
5987
6346
  req,
5988
6347
  res,
5989
- agent: this
6348
+ agent: this,
6349
+ costTracker
5990
6350
  });
5991
6351
  const { controller, release } = this.cancelRegistry.register(
5992
6352
  executionMetadata.executionId,
@@ -6032,7 +6392,8 @@ var Agent = class {
6032
6392
  workflow: this.getWorkflowReporter(executionMetadata),
6033
6393
  did: this.getDidInterface(executionMetadata, resolvedInput, params.targetName),
6034
6394
  signal: controller.signal,
6035
- trigger: triggerContext
6395
+ trigger: triggerContext,
6396
+ costTracker
6036
6397
  });
6037
6398
  const result = await reasoner.handler(ctx);
6038
6399
  this.executionLogger.system("reasoner.completed", "Reasoner execution completed", {
@@ -6051,7 +6412,7 @@ var Agent = class {
6051
6412
  rootWorkflowId: executionMetadata.rootWorkflowId
6052
6413
  });
6053
6414
  if (params.respond && params.res) {
6054
- params.res.json(result);
6415
+ params.res.json(attachUsageToSyncResult(result, costTracker));
6055
6416
  return;
6056
6417
  }
6057
6418
  return result;
@@ -7762,6 +8123,6 @@ function loadFixture(source) {
7762
8123
  );
7763
8124
  }
7764
8125
 
7765
- 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, applyTriggerTransform, 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, isTriggerEnvelope, loadFixture, normalizeExecutionLogEntry, normalizeSessionTransportValue, normalizeStatus, scheduleTrigger, serializeExecutionLogEntry, simulateSchedule, simulateTrigger, text, triggerToPayload, unwrapEnvelope, 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 };
7766
8127
  //# sourceMappingURL=index.js.map
7767
8128
  //# sourceMappingURL=index.js.map