@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.cjs CHANGED
@@ -437,8 +437,18 @@ function wrapOpenAI(client, options = {}) {
437
437
  const completionTokens = response.usage?.completion_tokens ?? 0;
438
438
  const totalTokens = response.usage?.total_tokens ?? 0;
439
439
  let outputContent = "";
440
- if (response.choices?.[0]?.message?.content) {
441
- outputContent = response.choices[0].message.content;
440
+ const toolCalls = [];
441
+ const msg = response.choices?.[0]?.message;
442
+ if (msg?.content) {
443
+ outputContent = msg.content;
444
+ }
445
+ if (msg?.tool_calls) {
446
+ for (const tc of msg.tool_calls) {
447
+ toolCalls.push({
448
+ name: tc.function?.name ?? "unknown",
449
+ arguments: tc.function?.arguments ?? "{}"
450
+ });
451
+ }
442
452
  }
443
453
  const cost = calculateOpenAICost({ model, inputTokens: promptTokens, outputTokens: completionTokens });
444
454
  trackLLMCall({
@@ -446,6 +456,7 @@ function wrapOpenAI(client, options = {}) {
446
456
  model,
447
457
  messages,
448
458
  output: outputContent,
459
+ toolCalls,
449
460
  promptTokens,
450
461
  completionTokens,
451
462
  totalTokens,
@@ -500,10 +511,16 @@ function wrapAnthropic(client, options = {}) {
500
511
  const completionTokens = response.usage?.output_tokens ?? 0;
501
512
  const totalTokens = promptTokens + completionTokens;
502
513
  let outputContent = "";
514
+ const toolCalls = [];
503
515
  if (response.content) {
504
516
  for (const block of response.content) {
505
517
  if (block.type === "text") {
506
518
  outputContent += block.text;
519
+ } else if (block.type === "tool_use") {
520
+ toolCalls.push({
521
+ name: block.name ?? "unknown",
522
+ arguments: JSON.stringify(block.input ?? {})
523
+ });
507
524
  }
508
525
  }
509
526
  }
@@ -514,6 +531,7 @@ function wrapAnthropic(client, options = {}) {
514
531
  model,
515
532
  messages: fullMessages,
516
533
  output: outputContent,
534
+ toolCalls,
517
535
  promptTokens,
518
536
  completionTokens,
519
537
  totalTokens,
@@ -627,6 +645,7 @@ function wrapLLM(client, provider) {
627
645
  function wrapOpenAIStream(stream, ctx) {
628
646
  let fullContent = "";
629
647
  let usage = null;
648
+ const toolCallMap = /* @__PURE__ */ new Map();
630
649
  let tracked = false;
631
650
  const originalIterator = stream[Symbol.asyncIterator]?.bind(stream);
632
651
  if (!originalIterator) return stream;
@@ -643,6 +662,7 @@ function wrapOpenAIStream(stream, ctx) {
643
662
  model: ctx.model,
644
663
  messages: ctx.messages,
645
664
  output: fullContent,
665
+ toolCalls: Array.from(toolCallMap.values()),
646
666
  promptTokens,
647
667
  completionTokens,
648
668
  totalTokens,
@@ -661,8 +681,17 @@ function wrapOpenAIStream(stream, ctx) {
661
681
  const result = await iter.next();
662
682
  if (!result.done) {
663
683
  const chunk = result.value;
664
- const delta = chunk.choices?.[0]?.delta?.content;
665
- if (delta) fullContent += delta;
684
+ const delta = chunk.choices?.[0]?.delta;
685
+ if (delta?.content) fullContent += delta.content;
686
+ if (delta?.tool_calls) {
687
+ for (const tc of delta.tool_calls) {
688
+ const idx = tc.index ?? 0;
689
+ const existing = toolCallMap.get(idx) ?? { name: "", arguments: "" };
690
+ if (tc.function?.name) existing.name = tc.function.name;
691
+ if (tc.function?.arguments) existing.arguments += tc.function.arguments;
692
+ toolCallMap.set(idx, existing);
693
+ }
694
+ }
666
695
  if (chunk.usage) usage = chunk.usage;
667
696
  } else {
668
697
  trackResult();
@@ -687,6 +716,8 @@ function wrapAnthropicStream(stream, ctx) {
687
716
  let fullContent = "";
688
717
  let inputTokens = 0;
689
718
  let outputTokens = 0;
719
+ const toolCallsById = /* @__PURE__ */ new Map();
720
+ let currentBlockIdx = -1;
690
721
  let tracked = false;
691
722
  const originalIterator = stream[Symbol.asyncIterator]?.bind(stream);
692
723
  if (!originalIterator) return stream;
@@ -702,6 +733,7 @@ function wrapAnthropicStream(stream, ctx) {
702
733
  model: ctx.model,
703
734
  messages: fullMessages,
704
735
  output: fullContent,
736
+ toolCalls: Array.from(toolCallsById.values()),
705
737
  promptTokens: inputTokens,
706
738
  completionTokens: outputTokens,
707
739
  totalTokens,
@@ -720,8 +752,26 @@ function wrapAnthropicStream(stream, ctx) {
720
752
  const result = await iter.next();
721
753
  if (!result.done) {
722
754
  const event = result.value;
723
- if (event.type === "content_block_delta" && event.delta?.text) {
724
- fullContent += event.delta.text;
755
+ if (event.type === "content_block_start") {
756
+ currentBlockIdx = event.index ?? currentBlockIdx + 1;
757
+ if (event.content_block?.type === "tool_use") {
758
+ toolCallsById.set(currentBlockIdx, {
759
+ name: event.content_block.name ?? "unknown",
760
+ arguments: ""
761
+ });
762
+ }
763
+ }
764
+ if (event.type === "content_block_delta") {
765
+ if (event.delta?.text) {
766
+ fullContent += event.delta.text;
767
+ }
768
+ if (event.delta?.type === "input_json_delta" && event.delta?.partial_json) {
769
+ const idx = event.index ?? currentBlockIdx;
770
+ const existing = toolCallsById.get(idx);
771
+ if (existing) {
772
+ existing.arguments += event.delta.partial_json;
773
+ }
774
+ }
725
775
  }
726
776
  if (event.type === "message_start" && event.message?.usage) {
727
777
  inputTokens = event.message.usage.input_tokens ?? 0;
@@ -755,6 +805,18 @@ function trackLLMCall(params) {
755
805
  if (!sessionId && !params.trackWithoutSession) {
756
806
  return;
757
807
  }
808
+ const toolOutput = {
809
+ content: params.output,
810
+ tokens: {
811
+ prompt: params.promptTokens,
812
+ completion: params.completionTokens,
813
+ total: params.totalTokens
814
+ },
815
+ cost_usd: params.cost
816
+ };
817
+ if (params.toolCalls && params.toolCalls.length > 0) {
818
+ toolOutput.tool_calls = params.toolCalls;
819
+ }
758
820
  if (sessionId) {
759
821
  client.trackToolCall({
760
822
  sessionId,
@@ -764,15 +826,7 @@ function trackLLMCall(params) {
764
826
  model: params.model,
765
827
  provider: params.provider
766
828
  },
767
- toolOutput: {
768
- content: params.output,
769
- tokens: {
770
- prompt: params.promptTokens,
771
- completion: params.completionTokens,
772
- total: params.totalTokens
773
- },
774
- cost_usd: params.cost
775
- },
829
+ toolOutput,
776
830
  reasoning: `LLM call to ${params.provider} ${params.model}`,
777
831
  estimatedCost: params.cost,
778
832
  tokenCount: params.totalTokens,
@@ -801,15 +855,7 @@ function trackLLMCall(params) {
801
855
  model: params.model,
802
856
  provider: params.provider
803
857
  },
804
- toolOutput: {
805
- content: params.output,
806
- tokens: {
807
- prompt: params.promptTokens,
808
- completion: params.completionTokens,
809
- total: params.totalTokens
810
- },
811
- cost_usd: params.cost
812
- },
858
+ toolOutput,
813
859
  estimatedCost: params.cost,
814
860
  tokenCount: params.totalTokens,
815
861
  metadata: {
@@ -1592,6 +1638,7 @@ function getClient() {
1592
1638
  }
1593
1639
  function configure(config) {
1594
1640
  defaultClient = new SentrialClient(config);
1641
+ setDefaultClient(defaultClient);
1595
1642
  }
1596
1643
  function begin(params) {
1597
1644
  return getClient().begin(params);
@@ -1610,6 +1657,10 @@ var sentrial = {
1610
1657
  };
1611
1658
 
1612
1659
  // src/vercel.ts
1660
+ function resolveStringOrFn(value, fallback) {
1661
+ if (typeof value === "function") return value() ?? fallback;
1662
+ return value ?? fallback;
1663
+ }
1613
1664
  var _defaultClient2 = null;
1614
1665
  var _globalConfig = {};
1615
1666
  function configureVercel(config) {
@@ -1618,6 +1669,7 @@ function configureVercel(config) {
1618
1669
  apiUrl: config.apiUrl,
1619
1670
  failSilently: config.failSilently ?? true
1620
1671
  });
1672
+ setDefaultClient(_defaultClient2);
1621
1673
  _globalConfig = {
1622
1674
  defaultAgent: config.defaultAgent,
1623
1675
  userId: config.userId,
@@ -1632,7 +1684,8 @@ function getClient2() {
1632
1684
  }
1633
1685
  function extractModelInfo(model) {
1634
1686
  const modelId = model.modelId || model.id || "unknown";
1635
- const provider = model.provider || guessProvider(modelId);
1687
+ const rawProvider = model.provider || "";
1688
+ const provider = rawProvider.split(".")[0] || guessProvider(modelId);
1636
1689
  return { modelId, provider };
1637
1690
  }
1638
1691
  function guessProvider(modelId) {
@@ -1666,17 +1719,38 @@ function calculateCostForCall(provider, modelId, promptTokens, completionTokens)
1666
1719
  return 0;
1667
1720
  }
1668
1721
  }
1722
+ function extractContentText(content) {
1723
+ if (typeof content === "string") return content;
1724
+ if (Array.isArray(content)) {
1725
+ const textParts = content.filter((part) => part && part.type === "text" && typeof part.text === "string").map((part) => part.text);
1726
+ if (textParts.length > 0) return textParts.join("\n");
1727
+ }
1728
+ return JSON.stringify(content);
1729
+ }
1669
1730
  function extractInput(params) {
1670
1731
  if (params.prompt) return params.prompt;
1671
1732
  if (params.messages && params.messages.length > 0) {
1672
1733
  const lastUserMessage = [...params.messages].reverse().find((m) => m.role === "user");
1673
1734
  if (lastUserMessage) {
1674
- return typeof lastUserMessage.content === "string" ? lastUserMessage.content : JSON.stringify(lastUserMessage.content);
1735
+ return extractContentText(lastUserMessage.content);
1736
+ }
1737
+ const lastNonSystem = [...params.messages].reverse().find((m) => m.role !== "system");
1738
+ if (lastNonSystem) {
1739
+ return extractContentText(lastNonSystem.content);
1675
1740
  }
1676
- return JSON.stringify(params.messages);
1741
+ return "";
1677
1742
  }
1678
1743
  return "";
1679
1744
  }
1745
+ function normalizeUsage(usage) {
1746
+ if (!usage) return void 0;
1747
+ const u = usage;
1748
+ const promptTokens = (u.inputTokens ?? u.promptTokens ?? 0) || 0;
1749
+ const completionTokens = (u.outputTokens ?? u.completionTokens ?? 0) || 0;
1750
+ const totalTokens = (u.totalTokens ?? promptTokens + completionTokens) || 0;
1751
+ if (promptTokens === 0 && completionTokens === 0 && totalTokens === 0) return void 0;
1752
+ return { promptTokens, completionTokens, totalTokens };
1753
+ }
1680
1754
  function wrapTools(tools, sessionId, client) {
1681
1755
  if (!tools) return void 0;
1682
1756
  const wrappedTools = {};
@@ -1729,34 +1803,37 @@ function wrapToolsAsync(tools, sessionPromise, client) {
1729
1803
  ...tool,
1730
1804
  execute: async (...args) => {
1731
1805
  const startTime = Date.now();
1732
- const sid = await sessionPromise;
1733
1806
  try {
1734
1807
  const result = await originalExecute(...args);
1735
1808
  const durationMs = Date.now() - startTime;
1736
- if (sid) {
1737
- client.trackToolCall({
1738
- sessionId: sid,
1739
- toolName,
1740
- toolInput: args[0],
1741
- toolOutput: result,
1742
- reasoning: `Tool executed in ${durationMs}ms`
1743
- }).catch(() => {
1744
- });
1745
- }
1809
+ sessionPromise.then((sid) => {
1810
+ if (sid) {
1811
+ client.trackToolCall({
1812
+ sessionId: sid,
1813
+ toolName,
1814
+ toolInput: args[0],
1815
+ toolOutput: result,
1816
+ reasoning: `Tool executed in ${durationMs}ms`
1817
+ }).catch(() => {
1818
+ });
1819
+ }
1820
+ });
1746
1821
  return result;
1747
1822
  } catch (error) {
1748
1823
  const durationMs = Date.now() - startTime;
1749
- if (sid) {
1750
- client.trackToolCall({
1751
- sessionId: sid,
1752
- toolName,
1753
- toolInput: args[0],
1754
- toolOutput: {},
1755
- toolError: { message: error instanceof Error ? error.message : "Unknown error" },
1756
- reasoning: `Tool failed after ${durationMs}ms`
1757
- }).catch(() => {
1758
- });
1759
- }
1824
+ sessionPromise.then((sid) => {
1825
+ if (sid) {
1826
+ client.trackToolCall({
1827
+ sessionId: sid,
1828
+ toolName,
1829
+ toolInput: args[0],
1830
+ toolOutput: {},
1831
+ toolError: { message: error instanceof Error ? error.message : "Unknown error" },
1832
+ reasoning: `Tool failed after ${durationMs}ms`
1833
+ }).catch(() => {
1834
+ });
1835
+ }
1836
+ });
1760
1837
  throw error;
1761
1838
  }
1762
1839
  }
@@ -1775,8 +1852,8 @@ function wrapGenerateText(originalFn, client, config) {
1775
1852
  const sessionId = await client.createSession({
1776
1853
  name: `generateText: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
1777
1854
  agentName: config.defaultAgent ?? "vercel-ai-sdk",
1778
- userId: config.userId ?? "anonymous",
1779
- convoId: config.convoId,
1855
+ userId: resolveStringOrFn(config.userId, "anonymous"),
1856
+ convoId: resolveStringOrFn(config.convoId),
1780
1857
  metadata: {
1781
1858
  model: modelId,
1782
1859
  provider,
@@ -1796,44 +1873,50 @@ function wrapGenerateText(originalFn, client, config) {
1796
1873
  const result = await originalFn(wrappedParams);
1797
1874
  const durationMs = Date.now() - startTime;
1798
1875
  const resolvedModelId = result.response?.modelId || modelId;
1799
- const promptTokens = result.usage?.promptTokens ?? 0;
1800
- const completionTokens = result.usage?.completionTokens ?? 0;
1801
- const totalTokens = result.usage?.totalTokens ?? promptTokens + completionTokens;
1876
+ const usage = normalizeUsage(result.usage);
1877
+ const promptTokens = usage?.promptTokens ?? 0;
1878
+ const completionTokens = usage?.completionTokens ?? 0;
1879
+ const totalTokens = usage?.totalTokens ?? 0;
1802
1880
  const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
1803
1881
  const steps = result.steps;
1804
1882
  if (steps && steps.length >= 1) {
1805
- const stepPromises = steps.map(
1806
- (step, i) => client.trackEvent({
1883
+ const stepPromises = steps.map((step) => {
1884
+ const su = normalizeUsage(step.usage);
1885
+ return client.trackToolCall({
1807
1886
  sessionId,
1808
- eventType: "llm_call",
1809
- eventData: {
1810
- model: resolvedModelId,
1811
- provider,
1812
- step: i + 1,
1813
- total_steps: steps.length,
1814
- prompt_tokens: step.usage?.promptTokens ?? 0,
1815
- completion_tokens: step.usage?.completionTokens ?? 0,
1816
- total_tokens: step.usage?.totalTokens ?? 0,
1817
- finish_reason: step.finishReason,
1887
+ toolName: `llm:${provider}:${resolvedModelId}`,
1888
+ toolInput: { finishReason: step.finishReason },
1889
+ toolOutput: {
1890
+ text: step.text?.slice(0, 500),
1891
+ tokens: {
1892
+ prompt: su?.promptTokens ?? 0,
1893
+ completion: su?.completionTokens ?? 0
1894
+ }
1895
+ },
1896
+ estimatedCost: calculateCostForCall(provider, resolvedModelId, su?.promptTokens ?? 0, su?.completionTokens ?? 0),
1897
+ tokenCount: su?.totalTokens,
1898
+ metadata: {
1818
1899
  tool_calls: step.toolCalls?.map((tc) => tc.toolName)
1819
1900
  }
1820
1901
  }).catch(() => {
1821
- })
1822
- );
1902
+ });
1903
+ });
1823
1904
  await Promise.all(stepPromises);
1824
1905
  } else {
1825
- await client.trackEvent({
1906
+ await client.trackToolCall({
1826
1907
  sessionId,
1827
- eventType: "llm_call",
1828
- eventData: {
1829
- model: resolvedModelId,
1830
- provider,
1831
- prompt_tokens: promptTokens,
1832
- completion_tokens: completionTokens,
1833
- total_tokens: totalTokens,
1834
- finish_reason: result.finishReason,
1908
+ toolName: `llm:${provider}:${resolvedModelId}`,
1909
+ toolInput: { finishReason: result.finishReason },
1910
+ toolOutput: {
1911
+ text: result.text?.slice(0, 500),
1912
+ tokens: { prompt: promptTokens, completion: completionTokens }
1913
+ },
1914
+ estimatedCost: cost,
1915
+ tokenCount: totalTokens,
1916
+ metadata: {
1835
1917
  tool_calls: result.toolCalls?.map((tc) => tc.toolName)
1836
1918
  }
1919
+ }).catch(() => {
1837
1920
  });
1838
1921
  }
1839
1922
  await client.completeSession({
@@ -1870,13 +1953,18 @@ function wrapStreamText(originalFn, client, config) {
1870
1953
  const { modelId, provider } = extractModelInfo(params.model);
1871
1954
  const input = extractInput(params);
1872
1955
  let sessionId = null;
1873
- const sessionPromise = (async () => {
1956
+ let sessionCompleted = false;
1957
+ let resolveSessionReady;
1958
+ const sessionReady = new Promise((resolve) => {
1959
+ resolveSessionReady = resolve;
1960
+ });
1961
+ (async () => {
1874
1962
  try {
1875
1963
  const id = await client.createSession({
1876
1964
  name: `streamText: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
1877
1965
  agentName: config.defaultAgent ?? "vercel-ai-sdk",
1878
- userId: config.userId ?? "anonymous",
1879
- convoId: config.convoId,
1966
+ userId: resolveStringOrFn(config.userId, "anonymous"),
1967
+ convoId: resolveStringOrFn(config.convoId),
1880
1968
  metadata: {
1881
1969
  model: modelId,
1882
1970
  provider,
@@ -1888,143 +1976,122 @@ function wrapStreamText(originalFn, client, config) {
1888
1976
  client.setInput(id, input).catch(() => {
1889
1977
  });
1890
1978
  }
1891
- return id;
1892
1979
  } catch {
1893
- return null;
1980
+ sessionId = null;
1894
1981
  }
1982
+ resolveSessionReady();
1895
1983
  })();
1984
+ const userOnStepFinish = params.onStepFinish;
1985
+ const userOnFinish = params.onFinish;
1986
+ const userOnError = params.onError;
1896
1987
  const wrappedParams = {
1897
1988
  ...params,
1898
- tools: params.tools ? wrapToolsAsync(params.tools, sessionPromise, client) : void 0
1899
- };
1900
- const result = originalFn(wrappedParams);
1901
- const originalTextStream = result.textStream;
1902
- let fullText = "";
1903
- let tracked = false;
1904
- async function trackCompletion(text, error) {
1905
- if (tracked) return;
1906
- tracked = true;
1907
- const durationMs = Date.now() - startTime;
1908
- const sid = sessionId || await sessionPromise;
1909
- if (!sid) return;
1910
- if (error) {
1911
- await client.trackError({
1912
- sessionId: sid,
1913
- errorType: error.name || "Error",
1914
- errorMessage: error.message || "Unknown error"
1915
- }).catch(() => {
1916
- });
1917
- await client.completeSession({
1918
- sessionId: sid,
1919
- success: false,
1920
- failureReason: error.message || "Unknown error",
1921
- durationMs
1922
- }).catch(() => {
1923
- });
1924
- return;
1925
- }
1926
- let resolvedModelId = modelId;
1927
- try {
1928
- const resp = result.response ? await result.response : void 0;
1929
- if (resp?.modelId) resolvedModelId = resp.modelId;
1930
- } catch {
1931
- }
1932
- let usage;
1933
- try {
1934
- usage = result.usage ? await result.usage : void 0;
1935
- } catch {
1936
- }
1937
- let steps;
1938
- try {
1939
- steps = result.steps ? await result.steps : void 0;
1940
- } catch {
1941
- }
1942
- if (steps && steps.length >= 1) {
1943
- let totalPrompt = 0, totalCompletion = 0;
1944
- const stepPromises = steps.map((step, i) => {
1945
- const sp = step.usage?.promptTokens ?? 0;
1946
- const sc = step.usage?.completionTokens ?? 0;
1947
- totalPrompt += sp;
1948
- totalCompletion += sc;
1949
- return client.trackEvent({
1950
- sessionId: sid,
1951
- eventType: "llm_call",
1952
- eventData: {
1953
- model: resolvedModelId,
1954
- provider,
1955
- step: i + 1,
1956
- total_steps: steps.length,
1957
- prompt_tokens: sp,
1958
- completion_tokens: sc,
1959
- total_tokens: step.usage?.totalTokens ?? 0,
1960
- finish_reason: step.finishReason,
1989
+ tools: params.tools ? wrapToolsAsync(params.tools, sessionReady.then(() => sessionId), client) : void 0,
1990
+ onStepFinish: async (step) => {
1991
+ await sessionReady;
1992
+ if (sessionId) {
1993
+ const su = normalizeUsage(step.usage);
1994
+ const resolvedStepModel = step.response?.modelId ?? modelId;
1995
+ client.trackToolCall({
1996
+ sessionId,
1997
+ toolName: `llm:${provider}:${resolvedStepModel}`,
1998
+ toolInput: { finishReason: step.finishReason },
1999
+ toolOutput: {
2000
+ text: step.text?.slice(0, 500),
2001
+ tokens: {
2002
+ prompt: su?.promptTokens ?? 0,
2003
+ completion: su?.completionTokens ?? 0
2004
+ }
2005
+ },
2006
+ estimatedCost: calculateCostForCall(provider, resolvedStepModel, su?.promptTokens ?? 0, su?.completionTokens ?? 0),
2007
+ tokenCount: su?.totalTokens,
2008
+ metadata: {
1961
2009
  tool_calls: step.toolCalls?.map((tc) => tc.toolName)
1962
2010
  }
1963
2011
  }).catch(() => {
1964
2012
  });
1965
- });
1966
- await Promise.all(stepPromises);
1967
- const promptTokens = usage?.promptTokens ?? totalPrompt;
1968
- const completionTokens = usage?.completionTokens ?? totalCompletion;
1969
- const totalTokens = usage?.totalTokens ?? promptTokens + completionTokens;
1970
- const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
1971
- await client.completeSession({
1972
- sessionId: sid,
1973
- success: true,
1974
- output: text,
1975
- durationMs,
1976
- estimatedCost: cost,
1977
- promptTokens,
1978
- completionTokens,
1979
- totalTokens
1980
- }).catch(() => {
1981
- });
1982
- } else {
1983
- const promptTokens = usage?.promptTokens ?? 0;
1984
- const completionTokens = usage?.completionTokens ?? 0;
1985
- const totalTokens = usage?.totalTokens ?? promptTokens + completionTokens;
1986
- const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
1987
- await client.trackEvent({
1988
- sessionId: sid,
1989
- eventType: "llm_call",
1990
- eventData: {
1991
- model: resolvedModelId,
1992
- provider,
1993
- prompt_tokens: promptTokens,
1994
- completion_tokens: completionTokens,
1995
- total_tokens: totalTokens
2013
+ }
2014
+ if (userOnStepFinish) {
2015
+ try {
2016
+ userOnStepFinish(step);
2017
+ } catch {
2018
+ }
2019
+ }
2020
+ },
2021
+ onFinish: async (event) => {
2022
+ await sessionReady;
2023
+ if (sessionId && !sessionCompleted) {
2024
+ sessionCompleted = true;
2025
+ const durationMs = Date.now() - startTime;
2026
+ const usage = normalizeUsage(event.totalUsage ?? event.usage);
2027
+ const resolvedModelId = event.response?.modelId ?? modelId;
2028
+ const text = event.text ?? "";
2029
+ const promptTokens = usage?.promptTokens ?? 0;
2030
+ const completionTokens = usage?.completionTokens ?? 0;
2031
+ const totalTokens = usage?.totalTokens ?? promptTokens + completionTokens;
2032
+ const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
2033
+ await client.completeSession({
2034
+ sessionId,
2035
+ success: true,
2036
+ output: text,
2037
+ durationMs,
2038
+ estimatedCost: cost,
2039
+ promptTokens,
2040
+ completionTokens,
2041
+ totalTokens
2042
+ }).catch(() => {
2043
+ });
2044
+ }
2045
+ if (userOnFinish) {
2046
+ try {
2047
+ userOnFinish(event);
2048
+ } catch {
2049
+ }
2050
+ }
2051
+ },
2052
+ onError: async (event) => {
2053
+ await sessionReady;
2054
+ if (sessionId && !sessionCompleted) {
2055
+ sessionCompleted = true;
2056
+ const durationMs = Date.now() - startTime;
2057
+ const msg = event.error?.message ?? "Unknown error";
2058
+ await client.trackError({
2059
+ sessionId,
2060
+ errorType: event.error?.name ?? "Error",
2061
+ errorMessage: msg
2062
+ }).catch(() => {
2063
+ });
2064
+ await client.completeSession({
2065
+ sessionId,
2066
+ success: false,
2067
+ failureReason: msg,
2068
+ durationMs
2069
+ }).catch(() => {
2070
+ });
2071
+ }
2072
+ if (userOnError) {
2073
+ try {
2074
+ userOnError(event);
2075
+ } catch {
1996
2076
  }
1997
- }).catch(() => {
1998
- });
1999
- await client.completeSession({
2000
- sessionId: sid,
2001
- success: true,
2002
- output: text,
2003
- durationMs,
2004
- estimatedCost: cost,
2005
- promptTokens,
2006
- completionTokens,
2007
- totalTokens
2008
- }).catch(() => {
2009
- });
2010
- }
2011
- }
2012
- result.textStream = (async function* () {
2013
- try {
2014
- for await (const chunk of originalTextStream) {
2015
- fullText += chunk;
2016
- yield chunk;
2017
2077
  }
2018
- await trackCompletion(fullText);
2019
- } catch (error) {
2020
- await trackCompletion(
2021
- fullText,
2022
- error instanceof Error ? error : new Error(String(error))
2023
- );
2024
- throw error;
2025
2078
  }
2026
- })();
2027
- return result;
2079
+ };
2080
+ try {
2081
+ return originalFn(wrappedParams);
2082
+ } catch (error) {
2083
+ sessionReady.then(() => {
2084
+ if (sessionId) {
2085
+ const durationMs = Date.now() - startTime;
2086
+ const msg = error instanceof Error ? error.message : String(error);
2087
+ client.trackError({ sessionId, errorType: error instanceof Error ? error.name : "Error", errorMessage: msg }).catch(() => {
2088
+ });
2089
+ client.completeSession({ sessionId, success: false, failureReason: msg, durationMs }).catch(() => {
2090
+ });
2091
+ }
2092
+ });
2093
+ throw error;
2094
+ }
2028
2095
  };
2029
2096
  }
2030
2097
  function wrapGenerateObject(originalFn, client, config) {
@@ -2035,8 +2102,8 @@ function wrapGenerateObject(originalFn, client, config) {
2035
2102
  const sessionId = await client.createSession({
2036
2103
  name: `generateObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
2037
2104
  agentName: config.defaultAgent ?? "vercel-ai-sdk",
2038
- userId: config.userId ?? "anonymous",
2039
- convoId: config.convoId,
2105
+ userId: resolveStringOrFn(config.userId, "anonymous"),
2106
+ convoId: resolveStringOrFn(config.convoId),
2040
2107
  metadata: {
2041
2108
  model: modelId,
2042
2109
  provider,
@@ -2051,20 +2118,21 @@ function wrapGenerateObject(originalFn, client, config) {
2051
2118
  const result = await originalFn(params);
2052
2119
  const durationMs = Date.now() - startTime;
2053
2120
  const resolvedModelId = result.response?.modelId || modelId;
2054
- const promptTokens = result.usage?.promptTokens ?? 0;
2055
- const completionTokens = result.usage?.completionTokens ?? 0;
2056
- const totalTokens = result.usage?.totalTokens ?? promptTokens + completionTokens;
2121
+ const usage = normalizeUsage(result.usage);
2122
+ const promptTokens = usage?.promptTokens ?? 0;
2123
+ const completionTokens = usage?.completionTokens ?? 0;
2124
+ const totalTokens = usage?.totalTokens ?? 0;
2057
2125
  const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
2058
- await client.trackEvent({
2126
+ await client.trackToolCall({
2059
2127
  sessionId,
2060
- eventType: "llm_call",
2061
- eventData: {
2062
- model: resolvedModelId,
2063
- provider,
2064
- prompt_tokens: promptTokens,
2065
- completion_tokens: completionTokens,
2066
- total_tokens: totalTokens
2067
- }
2128
+ toolName: `llm:${provider}:${resolvedModelId}`,
2129
+ toolInput: { function: "generateObject" },
2130
+ toolOutput: {
2131
+ object: result.object,
2132
+ tokens: { prompt: promptTokens, completion: completionTokens }
2133
+ },
2134
+ estimatedCost: cost,
2135
+ tokenCount: totalTokens
2068
2136
  }).catch(() => {
2069
2137
  });
2070
2138
  await client.completeSession({
@@ -2100,99 +2168,131 @@ function wrapStreamObject(originalFn, client, config) {
2100
2168
  const startTime = Date.now();
2101
2169
  const { modelId, provider } = extractModelInfo(params.model);
2102
2170
  const input = extractInput(params);
2103
- const sessionPromise = (async () => {
2171
+ let sessionId = null;
2172
+ let resolveSessionReady;
2173
+ const sessionReady = new Promise((resolve) => {
2174
+ resolveSessionReady = resolve;
2175
+ });
2176
+ (async () => {
2104
2177
  try {
2105
2178
  const id = await client.createSession({
2106
2179
  name: `streamObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
2107
2180
  agentName: config.defaultAgent ?? "vercel-ai-sdk",
2108
- userId: config.userId ?? "anonymous",
2109
- convoId: config.convoId,
2181
+ userId: resolveStringOrFn(config.userId, "anonymous"),
2182
+ convoId: resolveStringOrFn(config.convoId),
2110
2183
  metadata: {
2111
2184
  model: modelId,
2112
2185
  provider,
2113
2186
  function: "streamObject"
2114
2187
  }
2115
2188
  });
2189
+ sessionId = id;
2116
2190
  if (id) {
2117
2191
  client.setInput(id, input).catch(() => {
2118
2192
  });
2119
2193
  }
2120
- return id;
2121
2194
  } catch {
2122
- return null;
2195
+ sessionId = null;
2123
2196
  }
2197
+ resolveSessionReady();
2124
2198
  })();
2125
- const result = originalFn(params);
2126
- async function completeStreamObject(obj, error) {
2127
- const durationMs = Date.now() - startTime;
2128
- const sid = await sessionPromise;
2129
- if (!sid) return;
2130
- if (error) {
2131
- await client.trackError({
2132
- sessionId: sid,
2133
- errorType: error.name || "Error",
2134
- errorMessage: error.message || "Unknown error"
2135
- }).catch(() => {
2136
- });
2137
- await client.completeSession({
2138
- sessionId: sid,
2139
- success: false,
2140
- failureReason: error.message || "Unknown error",
2141
- durationMs
2142
- }).catch(() => {
2143
- });
2144
- return;
2145
- }
2146
- let usage;
2147
- try {
2148
- usage = result.usage ? await result.usage : void 0;
2149
- } catch {
2199
+ const userOnFinish = params.onFinish;
2200
+ const userOnError = params.onError;
2201
+ const wrappedParams = {
2202
+ ...params,
2203
+ onFinish: async (event) => {
2204
+ await sessionReady;
2205
+ if (sessionId) {
2206
+ const durationMs = Date.now() - startTime;
2207
+ const usage = normalizeUsage(event.usage);
2208
+ const resolvedModelId = event.response?.modelId ?? modelId;
2209
+ const promptTokens = usage?.promptTokens ?? 0;
2210
+ const completionTokens = usage?.completionTokens ?? 0;
2211
+ const totalTokens = usage?.totalTokens ?? 0;
2212
+ const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
2213
+ if (event.error) {
2214
+ const errMsg = event.error instanceof Error ? event.error.message : String(event.error);
2215
+ await client.trackError({
2216
+ sessionId,
2217
+ errorType: "SchemaValidationError",
2218
+ errorMessage: errMsg
2219
+ }).catch(() => {
2220
+ });
2221
+ }
2222
+ await client.trackToolCall({
2223
+ sessionId,
2224
+ toolName: `llm:${provider}:${resolvedModelId}`,
2225
+ toolInput: { function: "streamObject" },
2226
+ toolOutput: {
2227
+ object: event.object,
2228
+ tokens: { prompt: promptTokens, completion: completionTokens }
2229
+ },
2230
+ estimatedCost: cost,
2231
+ tokenCount: totalTokens
2232
+ }).catch(() => {
2233
+ });
2234
+ await client.completeSession({
2235
+ sessionId,
2236
+ success: !event.error,
2237
+ output: event.object != null ? JSON.stringify(event.object) : void 0,
2238
+ failureReason: event.error ? String(event.error) : void 0,
2239
+ durationMs,
2240
+ estimatedCost: cost,
2241
+ promptTokens,
2242
+ completionTokens,
2243
+ totalTokens
2244
+ }).catch(() => {
2245
+ });
2246
+ }
2247
+ if (userOnFinish) {
2248
+ try {
2249
+ userOnFinish(event);
2250
+ } catch {
2251
+ }
2252
+ }
2253
+ },
2254
+ onError: async (event) => {
2255
+ await sessionReady;
2256
+ if (sessionId) {
2257
+ const durationMs = Date.now() - startTime;
2258
+ const msg = event.error?.message ?? "Unknown error";
2259
+ await client.trackError({
2260
+ sessionId,
2261
+ errorType: event.error?.name ?? "Error",
2262
+ errorMessage: msg
2263
+ }).catch(() => {
2264
+ });
2265
+ await client.completeSession({
2266
+ sessionId,
2267
+ success: false,
2268
+ failureReason: msg,
2269
+ durationMs
2270
+ }).catch(() => {
2271
+ });
2272
+ }
2273
+ if (userOnError) {
2274
+ try {
2275
+ userOnError(event);
2276
+ } catch {
2277
+ }
2278
+ }
2150
2279
  }
2151
- const promptTokens = usage?.promptTokens ?? 0;
2152
- const completionTokens = usage?.completionTokens ?? 0;
2153
- const totalTokens = usage?.totalTokens ?? promptTokens + completionTokens;
2154
- const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
2155
- await client.trackEvent({
2156
- sessionId: sid,
2157
- eventType: "llm_call",
2158
- eventData: {
2159
- model: modelId,
2160
- provider,
2161
- prompt_tokens: promptTokens,
2162
- completion_tokens: completionTokens,
2163
- total_tokens: totalTokens
2280
+ };
2281
+ try {
2282
+ return originalFn(wrappedParams);
2283
+ } catch (error) {
2284
+ sessionReady.then(() => {
2285
+ if (sessionId) {
2286
+ const durationMs = Date.now() - startTime;
2287
+ const msg = error instanceof Error ? error.message : String(error);
2288
+ client.trackError({ sessionId, errorType: error instanceof Error ? error.name : "Error", errorMessage: msg }).catch(() => {
2289
+ });
2290
+ client.completeSession({ sessionId, success: false, failureReason: msg, durationMs }).catch(() => {
2291
+ });
2164
2292
  }
2165
- }).catch(() => {
2166
- });
2167
- await client.completeSession({
2168
- sessionId: sid,
2169
- success: true,
2170
- output: JSON.stringify(obj),
2171
- durationMs,
2172
- estimatedCost: cost,
2173
- promptTokens,
2174
- completionTokens,
2175
- totalTokens
2176
- }).catch(() => {
2177
- });
2178
- }
2179
- if (result.object) {
2180
- const originalObjectPromise = result.object;
2181
- result.object = originalObjectPromise.then(async (obj) => {
2182
- await completeStreamObject(obj);
2183
- return obj;
2184
- }).catch(async (error) => {
2185
- await completeStreamObject(void 0, error instanceof Error ? error : new Error(String(error)));
2186
- throw error;
2187
- });
2188
- } else if (result.usage) {
2189
- result.usage.then(async () => {
2190
- await completeStreamObject(void 0);
2191
- }).catch(async (error) => {
2192
- await completeStreamObject(void 0, error instanceof Error ? error : new Error(String(error)));
2193
2293
  });
2294
+ throw error;
2194
2295
  }
2195
- return result;
2196
2296
  };
2197
2297
  }
2198
2298
  function wrapAISDK(ai, options) {