@sentrial/sdk 0.4.2 → 0.4.4

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
@@ -364,8 +364,18 @@ function wrapOpenAI(client, options = {}) {
364
364
  const completionTokens = response.usage?.completion_tokens ?? 0;
365
365
  const totalTokens = response.usage?.total_tokens ?? 0;
366
366
  let outputContent = "";
367
- if (response.choices?.[0]?.message?.content) {
368
- outputContent = response.choices[0].message.content;
367
+ const toolCalls = [];
368
+ const msg = response.choices?.[0]?.message;
369
+ if (msg?.content) {
370
+ outputContent = msg.content;
371
+ }
372
+ if (msg?.tool_calls) {
373
+ for (const tc of msg.tool_calls) {
374
+ toolCalls.push({
375
+ name: tc.function?.name ?? "unknown",
376
+ arguments: tc.function?.arguments ?? "{}"
377
+ });
378
+ }
369
379
  }
370
380
  const cost = calculateOpenAICost({ model, inputTokens: promptTokens, outputTokens: completionTokens });
371
381
  trackLLMCall({
@@ -373,6 +383,7 @@ function wrapOpenAI(client, options = {}) {
373
383
  model,
374
384
  messages,
375
385
  output: outputContent,
386
+ toolCalls,
376
387
  promptTokens,
377
388
  completionTokens,
378
389
  totalTokens,
@@ -427,10 +438,16 @@ function wrapAnthropic(client, options = {}) {
427
438
  const completionTokens = response.usage?.output_tokens ?? 0;
428
439
  const totalTokens = promptTokens + completionTokens;
429
440
  let outputContent = "";
441
+ const toolCalls = [];
430
442
  if (response.content) {
431
443
  for (const block of response.content) {
432
444
  if (block.type === "text") {
433
445
  outputContent += block.text;
446
+ } else if (block.type === "tool_use") {
447
+ toolCalls.push({
448
+ name: block.name ?? "unknown",
449
+ arguments: JSON.stringify(block.input ?? {})
450
+ });
434
451
  }
435
452
  }
436
453
  }
@@ -441,6 +458,7 @@ function wrapAnthropic(client, options = {}) {
441
458
  model,
442
459
  messages: fullMessages,
443
460
  output: outputContent,
461
+ toolCalls,
444
462
  promptTokens,
445
463
  completionTokens,
446
464
  totalTokens,
@@ -554,6 +572,7 @@ function wrapLLM(client, provider) {
554
572
  function wrapOpenAIStream(stream, ctx) {
555
573
  let fullContent = "";
556
574
  let usage = null;
575
+ const toolCallMap = /* @__PURE__ */ new Map();
557
576
  let tracked = false;
558
577
  const originalIterator = stream[Symbol.asyncIterator]?.bind(stream);
559
578
  if (!originalIterator) return stream;
@@ -570,6 +589,7 @@ function wrapOpenAIStream(stream, ctx) {
570
589
  model: ctx.model,
571
590
  messages: ctx.messages,
572
591
  output: fullContent,
592
+ toolCalls: Array.from(toolCallMap.values()),
573
593
  promptTokens,
574
594
  completionTokens,
575
595
  totalTokens,
@@ -588,8 +608,17 @@ function wrapOpenAIStream(stream, ctx) {
588
608
  const result = await iter.next();
589
609
  if (!result.done) {
590
610
  const chunk = result.value;
591
- const delta = chunk.choices?.[0]?.delta?.content;
592
- if (delta) fullContent += delta;
611
+ const delta = chunk.choices?.[0]?.delta;
612
+ if (delta?.content) fullContent += delta.content;
613
+ if (delta?.tool_calls) {
614
+ for (const tc of delta.tool_calls) {
615
+ const idx = tc.index ?? 0;
616
+ const existing = toolCallMap.get(idx) ?? { name: "", arguments: "" };
617
+ if (tc.function?.name) existing.name = tc.function.name;
618
+ if (tc.function?.arguments) existing.arguments += tc.function.arguments;
619
+ toolCallMap.set(idx, existing);
620
+ }
621
+ }
593
622
  if (chunk.usage) usage = chunk.usage;
594
623
  } else {
595
624
  trackResult();
@@ -614,6 +643,8 @@ function wrapAnthropicStream(stream, ctx) {
614
643
  let fullContent = "";
615
644
  let inputTokens = 0;
616
645
  let outputTokens = 0;
646
+ const toolCallsById = /* @__PURE__ */ new Map();
647
+ let currentBlockIdx = -1;
617
648
  let tracked = false;
618
649
  const originalIterator = stream[Symbol.asyncIterator]?.bind(stream);
619
650
  if (!originalIterator) return stream;
@@ -629,6 +660,7 @@ function wrapAnthropicStream(stream, ctx) {
629
660
  model: ctx.model,
630
661
  messages: fullMessages,
631
662
  output: fullContent,
663
+ toolCalls: Array.from(toolCallsById.values()),
632
664
  promptTokens: inputTokens,
633
665
  completionTokens: outputTokens,
634
666
  totalTokens,
@@ -647,8 +679,26 @@ function wrapAnthropicStream(stream, ctx) {
647
679
  const result = await iter.next();
648
680
  if (!result.done) {
649
681
  const event = result.value;
650
- if (event.type === "content_block_delta" && event.delta?.text) {
651
- fullContent += event.delta.text;
682
+ if (event.type === "content_block_start") {
683
+ currentBlockIdx = event.index ?? currentBlockIdx + 1;
684
+ if (event.content_block?.type === "tool_use") {
685
+ toolCallsById.set(currentBlockIdx, {
686
+ name: event.content_block.name ?? "unknown",
687
+ arguments: ""
688
+ });
689
+ }
690
+ }
691
+ if (event.type === "content_block_delta") {
692
+ if (event.delta?.text) {
693
+ fullContent += event.delta.text;
694
+ }
695
+ if (event.delta?.type === "input_json_delta" && event.delta?.partial_json) {
696
+ const idx = event.index ?? currentBlockIdx;
697
+ const existing = toolCallsById.get(idx);
698
+ if (existing) {
699
+ existing.arguments += event.delta.partial_json;
700
+ }
701
+ }
652
702
  }
653
703
  if (event.type === "message_start" && event.message?.usage) {
654
704
  inputTokens = event.message.usage.input_tokens ?? 0;
@@ -682,6 +732,18 @@ function trackLLMCall(params) {
682
732
  if (!sessionId && !params.trackWithoutSession) {
683
733
  return;
684
734
  }
735
+ const toolOutput = {
736
+ content: params.output,
737
+ tokens: {
738
+ prompt: params.promptTokens,
739
+ completion: params.completionTokens,
740
+ total: params.totalTokens
741
+ },
742
+ cost_usd: params.cost
743
+ };
744
+ if (params.toolCalls && params.toolCalls.length > 0) {
745
+ toolOutput.tool_calls = params.toolCalls;
746
+ }
685
747
  if (sessionId) {
686
748
  client.trackToolCall({
687
749
  sessionId,
@@ -691,15 +753,7 @@ function trackLLMCall(params) {
691
753
  model: params.model,
692
754
  provider: params.provider
693
755
  },
694
- toolOutput: {
695
- content: params.output,
696
- tokens: {
697
- prompt: params.promptTokens,
698
- completion: params.completionTokens,
699
- total: params.totalTokens
700
- },
701
- cost_usd: params.cost
702
- },
756
+ toolOutput,
703
757
  reasoning: `LLM call to ${params.provider} ${params.model}`,
704
758
  estimatedCost: params.cost,
705
759
  tokenCount: params.totalTokens,
@@ -728,15 +782,7 @@ function trackLLMCall(params) {
728
782
  model: params.model,
729
783
  provider: params.provider
730
784
  },
731
- toolOutput: {
732
- content: params.output,
733
- tokens: {
734
- prompt: params.promptTokens,
735
- completion: params.completionTokens,
736
- total: params.totalTokens
737
- },
738
- cost_usd: params.cost
739
- },
785
+ toolOutput,
740
786
  estimatedCost: params.cost,
741
787
  tokenCount: params.totalTokens,
742
788
  metadata: {
@@ -1519,6 +1565,7 @@ function getClient() {
1519
1565
  }
1520
1566
  function configure(config) {
1521
1567
  defaultClient = new SentrialClient(config);
1568
+ setDefaultClient(defaultClient);
1522
1569
  }
1523
1570
  function begin(params) {
1524
1571
  return getClient().begin(params);
@@ -1537,6 +1584,10 @@ var sentrial = {
1537
1584
  };
1538
1585
 
1539
1586
  // src/vercel.ts
1587
+ function resolveStringOrFn(value, fallback) {
1588
+ if (typeof value === "function") return value() ?? fallback;
1589
+ return value ?? fallback;
1590
+ }
1540
1591
  var _defaultClient2 = null;
1541
1592
  var _globalConfig = {};
1542
1593
  function configureVercel(config) {
@@ -1545,6 +1596,7 @@ function configureVercel(config) {
1545
1596
  apiUrl: config.apiUrl,
1546
1597
  failSilently: config.failSilently ?? true
1547
1598
  });
1599
+ setDefaultClient(_defaultClient2);
1548
1600
  _globalConfig = {
1549
1601
  defaultAgent: config.defaultAgent,
1550
1602
  userId: config.userId,
@@ -1559,7 +1611,8 @@ function getClient2() {
1559
1611
  }
1560
1612
  function extractModelInfo(model) {
1561
1613
  const modelId = model.modelId || model.id || "unknown";
1562
- const provider = model.provider || guessProvider(modelId);
1614
+ const rawProvider = model.provider || "";
1615
+ const provider = rawProvider.split(".")[0] || guessProvider(modelId);
1563
1616
  return { modelId, provider };
1564
1617
  }
1565
1618
  function guessProvider(modelId) {
@@ -1593,17 +1646,38 @@ function calculateCostForCall(provider, modelId, promptTokens, completionTokens)
1593
1646
  return 0;
1594
1647
  }
1595
1648
  }
1649
+ function extractContentText(content) {
1650
+ if (typeof content === "string") return content;
1651
+ if (Array.isArray(content)) {
1652
+ const textParts = content.filter((part) => part && part.type === "text" && typeof part.text === "string").map((part) => part.text);
1653
+ if (textParts.length > 0) return textParts.join("\n");
1654
+ }
1655
+ return JSON.stringify(content);
1656
+ }
1596
1657
  function extractInput(params) {
1597
1658
  if (params.prompt) return params.prompt;
1598
1659
  if (params.messages && params.messages.length > 0) {
1599
1660
  const lastUserMessage = [...params.messages].reverse().find((m) => m.role === "user");
1600
1661
  if (lastUserMessage) {
1601
- return typeof lastUserMessage.content === "string" ? lastUserMessage.content : JSON.stringify(lastUserMessage.content);
1662
+ return extractContentText(lastUserMessage.content);
1663
+ }
1664
+ const lastNonSystem = [...params.messages].reverse().find((m) => m.role !== "system");
1665
+ if (lastNonSystem) {
1666
+ return extractContentText(lastNonSystem.content);
1602
1667
  }
1603
- return JSON.stringify(params.messages);
1668
+ return "";
1604
1669
  }
1605
1670
  return "";
1606
1671
  }
1672
+ function normalizeUsage(usage) {
1673
+ if (!usage) return void 0;
1674
+ const u = usage;
1675
+ const promptTokens = (u.inputTokens ?? u.promptTokens ?? 0) || 0;
1676
+ const completionTokens = (u.outputTokens ?? u.completionTokens ?? 0) || 0;
1677
+ const totalTokens = (u.totalTokens ?? promptTokens + completionTokens) || 0;
1678
+ if (promptTokens === 0 && completionTokens === 0 && totalTokens === 0) return void 0;
1679
+ return { promptTokens, completionTokens, totalTokens };
1680
+ }
1607
1681
  function wrapTools(tools, sessionId, client) {
1608
1682
  if (!tools) return void 0;
1609
1683
  const wrappedTools = {};
@@ -1656,34 +1730,37 @@ function wrapToolsAsync(tools, sessionPromise, client) {
1656
1730
  ...tool,
1657
1731
  execute: async (...args) => {
1658
1732
  const startTime = Date.now();
1659
- const sid = await sessionPromise;
1660
1733
  try {
1661
1734
  const result = await originalExecute(...args);
1662
1735
  const durationMs = Date.now() - startTime;
1663
- if (sid) {
1664
- client.trackToolCall({
1665
- sessionId: sid,
1666
- toolName,
1667
- toolInput: args[0],
1668
- toolOutput: result,
1669
- reasoning: `Tool executed in ${durationMs}ms`
1670
- }).catch(() => {
1671
- });
1672
- }
1736
+ sessionPromise.then((sid) => {
1737
+ if (sid) {
1738
+ client.trackToolCall({
1739
+ sessionId: sid,
1740
+ toolName,
1741
+ toolInput: args[0],
1742
+ toolOutput: result,
1743
+ reasoning: `Tool executed in ${durationMs}ms`
1744
+ }).catch(() => {
1745
+ });
1746
+ }
1747
+ });
1673
1748
  return result;
1674
1749
  } catch (error) {
1675
1750
  const durationMs = Date.now() - startTime;
1676
- if (sid) {
1677
- client.trackToolCall({
1678
- sessionId: sid,
1679
- toolName,
1680
- toolInput: args[0],
1681
- toolOutput: {},
1682
- toolError: { message: error instanceof Error ? error.message : "Unknown error" },
1683
- reasoning: `Tool failed after ${durationMs}ms`
1684
- }).catch(() => {
1685
- });
1686
- }
1751
+ sessionPromise.then((sid) => {
1752
+ if (sid) {
1753
+ client.trackToolCall({
1754
+ sessionId: sid,
1755
+ toolName,
1756
+ toolInput: args[0],
1757
+ toolOutput: {},
1758
+ toolError: { message: error instanceof Error ? error.message : "Unknown error" },
1759
+ reasoning: `Tool failed after ${durationMs}ms`
1760
+ }).catch(() => {
1761
+ });
1762
+ }
1763
+ });
1687
1764
  throw error;
1688
1765
  }
1689
1766
  }
@@ -1702,8 +1779,8 @@ function wrapGenerateText(originalFn, client, config) {
1702
1779
  const sessionId = await client.createSession({
1703
1780
  name: `generateText: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
1704
1781
  agentName: config.defaultAgent ?? "vercel-ai-sdk",
1705
- userId: config.userId ?? "anonymous",
1706
- convoId: config.convoId,
1782
+ userId: resolveStringOrFn(config.userId, "anonymous"),
1783
+ convoId: resolveStringOrFn(config.convoId),
1707
1784
  metadata: {
1708
1785
  model: modelId,
1709
1786
  provider,
@@ -1723,44 +1800,50 @@ function wrapGenerateText(originalFn, client, config) {
1723
1800
  const result = await originalFn(wrappedParams);
1724
1801
  const durationMs = Date.now() - startTime;
1725
1802
  const resolvedModelId = result.response?.modelId || modelId;
1726
- const promptTokens = result.usage?.promptTokens ?? 0;
1727
- const completionTokens = result.usage?.completionTokens ?? 0;
1728
- const totalTokens = result.usage?.totalTokens ?? promptTokens + completionTokens;
1803
+ const usage = normalizeUsage(result.usage);
1804
+ const promptTokens = usage?.promptTokens ?? 0;
1805
+ const completionTokens = usage?.completionTokens ?? 0;
1806
+ const totalTokens = usage?.totalTokens ?? 0;
1729
1807
  const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
1730
1808
  const steps = result.steps;
1731
1809
  if (steps && steps.length >= 1) {
1732
- const stepPromises = steps.map(
1733
- (step, i) => client.trackEvent({
1810
+ const stepPromises = steps.map((step) => {
1811
+ const su = normalizeUsage(step.usage);
1812
+ return client.trackToolCall({
1734
1813
  sessionId,
1735
- eventType: "llm_call",
1736
- eventData: {
1737
- model: resolvedModelId,
1738
- provider,
1739
- step: i + 1,
1740
- total_steps: steps.length,
1741
- prompt_tokens: step.usage?.promptTokens ?? 0,
1742
- completion_tokens: step.usage?.completionTokens ?? 0,
1743
- total_tokens: step.usage?.totalTokens ?? 0,
1744
- finish_reason: step.finishReason,
1814
+ toolName: `llm:${provider}:${resolvedModelId}`,
1815
+ toolInput: { finishReason: step.finishReason },
1816
+ toolOutput: {
1817
+ text: step.text?.slice(0, 500),
1818
+ tokens: {
1819
+ prompt: su?.promptTokens ?? 0,
1820
+ completion: su?.completionTokens ?? 0
1821
+ }
1822
+ },
1823
+ estimatedCost: calculateCostForCall(provider, resolvedModelId, su?.promptTokens ?? 0, su?.completionTokens ?? 0),
1824
+ tokenCount: su?.totalTokens,
1825
+ metadata: {
1745
1826
  tool_calls: step.toolCalls?.map((tc) => tc.toolName)
1746
1827
  }
1747
1828
  }).catch(() => {
1748
- })
1749
- );
1829
+ });
1830
+ });
1750
1831
  await Promise.all(stepPromises);
1751
1832
  } else {
1752
- await client.trackEvent({
1833
+ await client.trackToolCall({
1753
1834
  sessionId,
1754
- eventType: "llm_call",
1755
- eventData: {
1756
- model: resolvedModelId,
1757
- provider,
1758
- prompt_tokens: promptTokens,
1759
- completion_tokens: completionTokens,
1760
- total_tokens: totalTokens,
1761
- finish_reason: result.finishReason,
1835
+ toolName: `llm:${provider}:${resolvedModelId}`,
1836
+ toolInput: { finishReason: result.finishReason },
1837
+ toolOutput: {
1838
+ text: result.text?.slice(0, 500),
1839
+ tokens: { prompt: promptTokens, completion: completionTokens }
1840
+ },
1841
+ estimatedCost: cost,
1842
+ tokenCount: totalTokens,
1843
+ metadata: {
1762
1844
  tool_calls: result.toolCalls?.map((tc) => tc.toolName)
1763
1845
  }
1846
+ }).catch(() => {
1764
1847
  });
1765
1848
  }
1766
1849
  await client.completeSession({
@@ -1797,13 +1880,18 @@ function wrapStreamText(originalFn, client, config) {
1797
1880
  const { modelId, provider } = extractModelInfo(params.model);
1798
1881
  const input = extractInput(params);
1799
1882
  let sessionId = null;
1800
- const sessionPromise = (async () => {
1883
+ let sessionCompleted = false;
1884
+ let resolveSessionReady;
1885
+ const sessionReady = new Promise((resolve) => {
1886
+ resolveSessionReady = resolve;
1887
+ });
1888
+ (async () => {
1801
1889
  try {
1802
1890
  const id = await client.createSession({
1803
1891
  name: `streamText: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
1804
1892
  agentName: config.defaultAgent ?? "vercel-ai-sdk",
1805
- userId: config.userId ?? "anonymous",
1806
- convoId: config.convoId,
1893
+ userId: resolveStringOrFn(config.userId, "anonymous"),
1894
+ convoId: resolveStringOrFn(config.convoId),
1807
1895
  metadata: {
1808
1896
  model: modelId,
1809
1897
  provider,
@@ -1815,143 +1903,122 @@ function wrapStreamText(originalFn, client, config) {
1815
1903
  client.setInput(id, input).catch(() => {
1816
1904
  });
1817
1905
  }
1818
- return id;
1819
1906
  } catch {
1820
- return null;
1907
+ sessionId = null;
1821
1908
  }
1909
+ resolveSessionReady();
1822
1910
  })();
1911
+ const userOnStepFinish = params.onStepFinish;
1912
+ const userOnFinish = params.onFinish;
1913
+ const userOnError = params.onError;
1823
1914
  const wrappedParams = {
1824
1915
  ...params,
1825
- tools: params.tools ? wrapToolsAsync(params.tools, sessionPromise, client) : void 0
1826
- };
1827
- const result = originalFn(wrappedParams);
1828
- const originalTextStream = result.textStream;
1829
- let fullText = "";
1830
- let tracked = false;
1831
- async function trackCompletion(text, error) {
1832
- if (tracked) return;
1833
- tracked = true;
1834
- const durationMs = Date.now() - startTime;
1835
- const sid = sessionId || await sessionPromise;
1836
- if (!sid) return;
1837
- if (error) {
1838
- await client.trackError({
1839
- sessionId: sid,
1840
- errorType: error.name || "Error",
1841
- errorMessage: error.message || "Unknown error"
1842
- }).catch(() => {
1843
- });
1844
- await client.completeSession({
1845
- sessionId: sid,
1846
- success: false,
1847
- failureReason: error.message || "Unknown error",
1848
- durationMs
1849
- }).catch(() => {
1850
- });
1851
- return;
1852
- }
1853
- let resolvedModelId = modelId;
1854
- try {
1855
- const resp = result.response ? await result.response : void 0;
1856
- if (resp?.modelId) resolvedModelId = resp.modelId;
1857
- } catch {
1858
- }
1859
- let usage;
1860
- try {
1861
- usage = result.usage ? await result.usage : void 0;
1862
- } catch {
1863
- }
1864
- let steps;
1865
- try {
1866
- steps = result.steps ? await result.steps : void 0;
1867
- } catch {
1868
- }
1869
- if (steps && steps.length >= 1) {
1870
- let totalPrompt = 0, totalCompletion = 0;
1871
- const stepPromises = steps.map((step, i) => {
1872
- const sp = step.usage?.promptTokens ?? 0;
1873
- const sc = step.usage?.completionTokens ?? 0;
1874
- totalPrompt += sp;
1875
- totalCompletion += sc;
1876
- return client.trackEvent({
1877
- sessionId: sid,
1878
- eventType: "llm_call",
1879
- eventData: {
1880
- model: resolvedModelId,
1881
- provider,
1882
- step: i + 1,
1883
- total_steps: steps.length,
1884
- prompt_tokens: sp,
1885
- completion_tokens: sc,
1886
- total_tokens: step.usage?.totalTokens ?? 0,
1887
- finish_reason: step.finishReason,
1916
+ tools: params.tools ? wrapToolsAsync(params.tools, sessionReady.then(() => sessionId), client) : void 0,
1917
+ onStepFinish: async (step) => {
1918
+ await sessionReady;
1919
+ if (sessionId) {
1920
+ const su = normalizeUsage(step.usage);
1921
+ const resolvedStepModel = step.response?.modelId ?? modelId;
1922
+ client.trackToolCall({
1923
+ sessionId,
1924
+ toolName: `llm:${provider}:${resolvedStepModel}`,
1925
+ toolInput: { finishReason: step.finishReason },
1926
+ toolOutput: {
1927
+ text: step.text?.slice(0, 500),
1928
+ tokens: {
1929
+ prompt: su?.promptTokens ?? 0,
1930
+ completion: su?.completionTokens ?? 0
1931
+ }
1932
+ },
1933
+ estimatedCost: calculateCostForCall(provider, resolvedStepModel, su?.promptTokens ?? 0, su?.completionTokens ?? 0),
1934
+ tokenCount: su?.totalTokens,
1935
+ metadata: {
1888
1936
  tool_calls: step.toolCalls?.map((tc) => tc.toolName)
1889
1937
  }
1890
1938
  }).catch(() => {
1891
1939
  });
1892
- });
1893
- await Promise.all(stepPromises);
1894
- const promptTokens = usage?.promptTokens ?? totalPrompt;
1895
- const completionTokens = usage?.completionTokens ?? totalCompletion;
1896
- const totalTokens = usage?.totalTokens ?? promptTokens + completionTokens;
1897
- const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
1898
- await client.completeSession({
1899
- sessionId: sid,
1900
- success: true,
1901
- output: text,
1902
- durationMs,
1903
- estimatedCost: cost,
1904
- promptTokens,
1905
- completionTokens,
1906
- totalTokens
1907
- }).catch(() => {
1908
- });
1909
- } else {
1910
- const promptTokens = usage?.promptTokens ?? 0;
1911
- const completionTokens = usage?.completionTokens ?? 0;
1912
- const totalTokens = usage?.totalTokens ?? promptTokens + completionTokens;
1913
- const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
1914
- await client.trackEvent({
1915
- sessionId: sid,
1916
- eventType: "llm_call",
1917
- eventData: {
1918
- model: resolvedModelId,
1919
- provider,
1920
- prompt_tokens: promptTokens,
1921
- completion_tokens: completionTokens,
1922
- total_tokens: totalTokens
1940
+ }
1941
+ if (userOnStepFinish) {
1942
+ try {
1943
+ userOnStepFinish(step);
1944
+ } catch {
1945
+ }
1946
+ }
1947
+ },
1948
+ onFinish: async (event) => {
1949
+ await sessionReady;
1950
+ if (sessionId && !sessionCompleted) {
1951
+ sessionCompleted = true;
1952
+ const durationMs = Date.now() - startTime;
1953
+ const usage = normalizeUsage(event.totalUsage ?? event.usage);
1954
+ const resolvedModelId = event.response?.modelId ?? modelId;
1955
+ const text = event.text ?? "";
1956
+ const promptTokens = usage?.promptTokens ?? 0;
1957
+ const completionTokens = usage?.completionTokens ?? 0;
1958
+ const totalTokens = usage?.totalTokens ?? promptTokens + completionTokens;
1959
+ const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
1960
+ await client.completeSession({
1961
+ sessionId,
1962
+ success: true,
1963
+ output: text,
1964
+ durationMs,
1965
+ estimatedCost: cost,
1966
+ promptTokens,
1967
+ completionTokens,
1968
+ totalTokens
1969
+ }).catch(() => {
1970
+ });
1971
+ }
1972
+ if (userOnFinish) {
1973
+ try {
1974
+ userOnFinish(event);
1975
+ } catch {
1976
+ }
1977
+ }
1978
+ },
1979
+ onError: async (event) => {
1980
+ await sessionReady;
1981
+ if (sessionId && !sessionCompleted) {
1982
+ sessionCompleted = true;
1983
+ const durationMs = Date.now() - startTime;
1984
+ const msg = event.error?.message ?? "Unknown error";
1985
+ await client.trackError({
1986
+ sessionId,
1987
+ errorType: event.error?.name ?? "Error",
1988
+ errorMessage: msg
1989
+ }).catch(() => {
1990
+ });
1991
+ await client.completeSession({
1992
+ sessionId,
1993
+ success: false,
1994
+ failureReason: msg,
1995
+ durationMs
1996
+ }).catch(() => {
1997
+ });
1998
+ }
1999
+ if (userOnError) {
2000
+ try {
2001
+ userOnError(event);
2002
+ } catch {
1923
2003
  }
1924
- }).catch(() => {
1925
- });
1926
- await client.completeSession({
1927
- sessionId: sid,
1928
- success: true,
1929
- output: text,
1930
- durationMs,
1931
- estimatedCost: cost,
1932
- promptTokens,
1933
- completionTokens,
1934
- totalTokens
1935
- }).catch(() => {
1936
- });
1937
- }
1938
- }
1939
- result.textStream = (async function* () {
1940
- try {
1941
- for await (const chunk of originalTextStream) {
1942
- fullText += chunk;
1943
- yield chunk;
1944
2004
  }
1945
- await trackCompletion(fullText);
1946
- } catch (error) {
1947
- await trackCompletion(
1948
- fullText,
1949
- error instanceof Error ? error : new Error(String(error))
1950
- );
1951
- throw error;
1952
2005
  }
1953
- })();
1954
- return result;
2006
+ };
2007
+ try {
2008
+ return originalFn(wrappedParams);
2009
+ } catch (error) {
2010
+ sessionReady.then(() => {
2011
+ if (sessionId) {
2012
+ const durationMs = Date.now() - startTime;
2013
+ const msg = error instanceof Error ? error.message : String(error);
2014
+ client.trackError({ sessionId, errorType: error instanceof Error ? error.name : "Error", errorMessage: msg }).catch(() => {
2015
+ });
2016
+ client.completeSession({ sessionId, success: false, failureReason: msg, durationMs }).catch(() => {
2017
+ });
2018
+ }
2019
+ });
2020
+ throw error;
2021
+ }
1955
2022
  };
1956
2023
  }
1957
2024
  function wrapGenerateObject(originalFn, client, config) {
@@ -1962,8 +2029,8 @@ function wrapGenerateObject(originalFn, client, config) {
1962
2029
  const sessionId = await client.createSession({
1963
2030
  name: `generateObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
1964
2031
  agentName: config.defaultAgent ?? "vercel-ai-sdk",
1965
- userId: config.userId ?? "anonymous",
1966
- convoId: config.convoId,
2032
+ userId: resolveStringOrFn(config.userId, "anonymous"),
2033
+ convoId: resolveStringOrFn(config.convoId),
1967
2034
  metadata: {
1968
2035
  model: modelId,
1969
2036
  provider,
@@ -1978,20 +2045,21 @@ function wrapGenerateObject(originalFn, client, config) {
1978
2045
  const result = await originalFn(params);
1979
2046
  const durationMs = Date.now() - startTime;
1980
2047
  const resolvedModelId = result.response?.modelId || modelId;
1981
- const promptTokens = result.usage?.promptTokens ?? 0;
1982
- const completionTokens = result.usage?.completionTokens ?? 0;
1983
- const totalTokens = result.usage?.totalTokens ?? promptTokens + completionTokens;
2048
+ const usage = normalizeUsage(result.usage);
2049
+ const promptTokens = usage?.promptTokens ?? 0;
2050
+ const completionTokens = usage?.completionTokens ?? 0;
2051
+ const totalTokens = usage?.totalTokens ?? 0;
1984
2052
  const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
1985
- await client.trackEvent({
2053
+ await client.trackToolCall({
1986
2054
  sessionId,
1987
- eventType: "llm_call",
1988
- eventData: {
1989
- model: resolvedModelId,
1990
- provider,
1991
- prompt_tokens: promptTokens,
1992
- completion_tokens: completionTokens,
1993
- total_tokens: totalTokens
1994
- }
2055
+ toolName: `llm:${provider}:${resolvedModelId}`,
2056
+ toolInput: { function: "generateObject" },
2057
+ toolOutput: {
2058
+ object: result.object,
2059
+ tokens: { prompt: promptTokens, completion: completionTokens }
2060
+ },
2061
+ estimatedCost: cost,
2062
+ tokenCount: totalTokens
1995
2063
  }).catch(() => {
1996
2064
  });
1997
2065
  await client.completeSession({
@@ -2027,99 +2095,131 @@ function wrapStreamObject(originalFn, client, config) {
2027
2095
  const startTime = Date.now();
2028
2096
  const { modelId, provider } = extractModelInfo(params.model);
2029
2097
  const input = extractInput(params);
2030
- const sessionPromise = (async () => {
2098
+ let sessionId = null;
2099
+ let resolveSessionReady;
2100
+ const sessionReady = new Promise((resolve) => {
2101
+ resolveSessionReady = resolve;
2102
+ });
2103
+ (async () => {
2031
2104
  try {
2032
2105
  const id = await client.createSession({
2033
2106
  name: `streamObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
2034
2107
  agentName: config.defaultAgent ?? "vercel-ai-sdk",
2035
- userId: config.userId ?? "anonymous",
2036
- convoId: config.convoId,
2108
+ userId: resolveStringOrFn(config.userId, "anonymous"),
2109
+ convoId: resolveStringOrFn(config.convoId),
2037
2110
  metadata: {
2038
2111
  model: modelId,
2039
2112
  provider,
2040
2113
  function: "streamObject"
2041
2114
  }
2042
2115
  });
2116
+ sessionId = id;
2043
2117
  if (id) {
2044
2118
  client.setInput(id, input).catch(() => {
2045
2119
  });
2046
2120
  }
2047
- return id;
2048
2121
  } catch {
2049
- return null;
2122
+ sessionId = null;
2050
2123
  }
2124
+ resolveSessionReady();
2051
2125
  })();
2052
- const result = originalFn(params);
2053
- async function completeStreamObject(obj, error) {
2054
- const durationMs = Date.now() - startTime;
2055
- const sid = await sessionPromise;
2056
- if (!sid) return;
2057
- if (error) {
2058
- await client.trackError({
2059
- sessionId: sid,
2060
- errorType: error.name || "Error",
2061
- errorMessage: error.message || "Unknown error"
2062
- }).catch(() => {
2063
- });
2064
- await client.completeSession({
2065
- sessionId: sid,
2066
- success: false,
2067
- failureReason: error.message || "Unknown error",
2068
- durationMs
2069
- }).catch(() => {
2070
- });
2071
- return;
2072
- }
2073
- let usage;
2074
- try {
2075
- usage = result.usage ? await result.usage : void 0;
2076
- } catch {
2126
+ const userOnFinish = params.onFinish;
2127
+ const userOnError = params.onError;
2128
+ const wrappedParams = {
2129
+ ...params,
2130
+ onFinish: async (event) => {
2131
+ await sessionReady;
2132
+ if (sessionId) {
2133
+ const durationMs = Date.now() - startTime;
2134
+ const usage = normalizeUsage(event.usage);
2135
+ const resolvedModelId = event.response?.modelId ?? modelId;
2136
+ const promptTokens = usage?.promptTokens ?? 0;
2137
+ const completionTokens = usage?.completionTokens ?? 0;
2138
+ const totalTokens = usage?.totalTokens ?? 0;
2139
+ const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
2140
+ if (event.error) {
2141
+ const errMsg = event.error instanceof Error ? event.error.message : String(event.error);
2142
+ await client.trackError({
2143
+ sessionId,
2144
+ errorType: "SchemaValidationError",
2145
+ errorMessage: errMsg
2146
+ }).catch(() => {
2147
+ });
2148
+ }
2149
+ await client.trackToolCall({
2150
+ sessionId,
2151
+ toolName: `llm:${provider}:${resolvedModelId}`,
2152
+ toolInput: { function: "streamObject" },
2153
+ toolOutput: {
2154
+ object: event.object,
2155
+ tokens: { prompt: promptTokens, completion: completionTokens }
2156
+ },
2157
+ estimatedCost: cost,
2158
+ tokenCount: totalTokens
2159
+ }).catch(() => {
2160
+ });
2161
+ await client.completeSession({
2162
+ sessionId,
2163
+ success: !event.error,
2164
+ output: event.object != null ? JSON.stringify(event.object) : void 0,
2165
+ failureReason: event.error ? String(event.error) : void 0,
2166
+ durationMs,
2167
+ estimatedCost: cost,
2168
+ promptTokens,
2169
+ completionTokens,
2170
+ totalTokens
2171
+ }).catch(() => {
2172
+ });
2173
+ }
2174
+ if (userOnFinish) {
2175
+ try {
2176
+ userOnFinish(event);
2177
+ } catch {
2178
+ }
2179
+ }
2180
+ },
2181
+ onError: async (event) => {
2182
+ await sessionReady;
2183
+ if (sessionId) {
2184
+ const durationMs = Date.now() - startTime;
2185
+ const msg = event.error?.message ?? "Unknown error";
2186
+ await client.trackError({
2187
+ sessionId,
2188
+ errorType: event.error?.name ?? "Error",
2189
+ errorMessage: msg
2190
+ }).catch(() => {
2191
+ });
2192
+ await client.completeSession({
2193
+ sessionId,
2194
+ success: false,
2195
+ failureReason: msg,
2196
+ durationMs
2197
+ }).catch(() => {
2198
+ });
2199
+ }
2200
+ if (userOnError) {
2201
+ try {
2202
+ userOnError(event);
2203
+ } catch {
2204
+ }
2205
+ }
2077
2206
  }
2078
- const promptTokens = usage?.promptTokens ?? 0;
2079
- const completionTokens = usage?.completionTokens ?? 0;
2080
- const totalTokens = usage?.totalTokens ?? promptTokens + completionTokens;
2081
- const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
2082
- await client.trackEvent({
2083
- sessionId: sid,
2084
- eventType: "llm_call",
2085
- eventData: {
2086
- model: modelId,
2087
- provider,
2088
- prompt_tokens: promptTokens,
2089
- completion_tokens: completionTokens,
2090
- total_tokens: totalTokens
2207
+ };
2208
+ try {
2209
+ return originalFn(wrappedParams);
2210
+ } catch (error) {
2211
+ sessionReady.then(() => {
2212
+ if (sessionId) {
2213
+ const durationMs = Date.now() - startTime;
2214
+ const msg = error instanceof Error ? error.message : String(error);
2215
+ client.trackError({ sessionId, errorType: error instanceof Error ? error.name : "Error", errorMessage: msg }).catch(() => {
2216
+ });
2217
+ client.completeSession({ sessionId, success: false, failureReason: msg, durationMs }).catch(() => {
2218
+ });
2091
2219
  }
2092
- }).catch(() => {
2093
- });
2094
- await client.completeSession({
2095
- sessionId: sid,
2096
- success: true,
2097
- output: JSON.stringify(obj),
2098
- durationMs,
2099
- estimatedCost: cost,
2100
- promptTokens,
2101
- completionTokens,
2102
- totalTokens
2103
- }).catch(() => {
2104
- });
2105
- }
2106
- if (result.object) {
2107
- const originalObjectPromise = result.object;
2108
- result.object = originalObjectPromise.then(async (obj) => {
2109
- await completeStreamObject(obj);
2110
- return obj;
2111
- }).catch(async (error) => {
2112
- await completeStreamObject(void 0, error instanceof Error ? error : new Error(String(error)));
2113
- throw error;
2114
- });
2115
- } else if (result.usage) {
2116
- result.usage.then(async () => {
2117
- await completeStreamObject(void 0);
2118
- }).catch(async (error) => {
2119
- await completeStreamObject(void 0, error instanceof Error ? error : new Error(String(error)));
2120
2220
  });
2221
+ throw error;
2121
2222
  }
2122
- return result;
2123
2223
  };
2124
2224
  }
2125
2225
  function wrapAISDK(ai, options) {