@mastra/client-js 1.20.1-alpha.0 → 1.21.0-alpha.10

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
@@ -10,6 +10,210 @@ import canonicalize from 'canonicalize';
10
10
  import { base64url, decodeProtectedHeader, compactVerify, importX509, importSPKI, importJWK } from 'jose';
11
11
 
12
12
  // src/resources/agent.ts
13
+
14
+ // src/observability/collector.ts
15
+ var TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
16
+ var ZERO_TRACE_ID = "00000000000000000000000000000000";
17
+ var ZERO_SPAN_ID = "0000000000000000";
18
+ var LEVEL_TO_SEVERITY_NUMBER = {
19
+ debug: 5,
20
+ info: 9,
21
+ warn: 13,
22
+ error: 17,
23
+ fatal: 21
24
+ };
25
+ var LEVEL_TO_SEVERITY_TEXT = {
26
+ debug: "DEBUG",
27
+ info: "INFO",
28
+ warn: "WARN",
29
+ error: "ERROR",
30
+ fatal: "FATAL"
31
+ };
32
+ function nowNs() {
33
+ return BigInt(Date.now()) * 1000000n;
34
+ }
35
+ function isValidTraceparentMatch(parsed) {
36
+ const version = parsed[1];
37
+ const traceId = parsed[2];
38
+ const spanId = parsed[3];
39
+ return version !== "ff" && traceId !== ZERO_TRACE_ID && spanId !== ZERO_SPAN_ID;
40
+ }
41
+ function randomSpanIdHex() {
42
+ if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.getRandomValues) {
43
+ const bytes = new Uint8Array(8);
44
+ globalThis.crypto.getRandomValues(bytes);
45
+ let out2 = "";
46
+ for (const b of bytes) out2 += b.toString(16).padStart(2, "0");
47
+ return out2;
48
+ }
49
+ let out = "";
50
+ for (let i = 0; i < 16; i++) out += Math.floor(Math.random() * 16).toString(16);
51
+ return out;
52
+ }
53
+ function attributesToOtlp(attrs) {
54
+ const out = [];
55
+ for (const [key, raw] of Object.entries(attrs)) {
56
+ if (raw === void 0 || raw === null) continue;
57
+ if (typeof raw === "string") {
58
+ out.push({ key, value: { stringValue: raw } });
59
+ } else if (typeof raw === "boolean") {
60
+ out.push({ key, value: { boolValue: raw } });
61
+ } else if (typeof raw === "number") {
62
+ if (Number.isInteger(raw)) {
63
+ out.push({ key, value: { intValue: raw } });
64
+ } else {
65
+ out.push({ key, value: { doubleValue: raw } });
66
+ }
67
+ } else {
68
+ try {
69
+ out.push({ key, value: { stringValue: JSON.stringify(raw) } });
70
+ } catch {
71
+ }
72
+ }
73
+ }
74
+ return out;
75
+ }
76
+ var ObservabilityCollectorImpl = class {
77
+ parentContext;
78
+ #traceId;
79
+ #rootSpanId;
80
+ #spans = [];
81
+ #logs = [];
82
+ #spanStack = [];
83
+ /** Wall-clock execution timing, captured by withContext. */
84
+ #executionStartMs;
85
+ #executionEndMs;
86
+ #flushed = false;
87
+ constructor(parentContext) {
88
+ this.parentContext = parentContext;
89
+ const parsed = TRACEPARENT_RE.exec(parentContext.traceparent);
90
+ if (!parsed || !isValidTraceparentMatch(parsed)) {
91
+ this.#traceId = ZERO_TRACE_ID;
92
+ this.#rootSpanId = ZERO_SPAN_ID;
93
+ return;
94
+ }
95
+ this.#traceId = parsed[2];
96
+ this.#rootSpanId = parsed[3];
97
+ }
98
+ async withContext(fn) {
99
+ const executionStartMs = this.#executionStartMs ?? Date.now();
100
+ this.#executionStartMs = executionStartMs;
101
+ this.#spanStack.push(this.#rootSpanId);
102
+ try {
103
+ return await fn();
104
+ } finally {
105
+ this.#spanStack.pop();
106
+ this.#executionEndMs = Date.now();
107
+ }
108
+ }
109
+ async span(name, fn, attributes) {
110
+ const spanId = randomSpanIdHex();
111
+ const spanStack = this.#getSpanStack();
112
+ const parentSpanId = spanStack[spanStack.length - 1] ?? this.#rootSpanId;
113
+ const startTimeNs = nowNs();
114
+ spanStack.push(spanId);
115
+ let statusCode = 1;
116
+ let statusMessage;
117
+ try {
118
+ return await fn();
119
+ } catch (err) {
120
+ statusCode = 2;
121
+ statusMessage = err instanceof Error ? err.message : String(err);
122
+ throw err;
123
+ } finally {
124
+ spanStack.pop();
125
+ this.#spans.push({
126
+ spanId,
127
+ parentSpanId,
128
+ traceId: this.#traceId,
129
+ name,
130
+ startTimeNs,
131
+ endTimeNs: nowNs(),
132
+ attributes: attributes ?? {},
133
+ statusCode,
134
+ statusMessage
135
+ });
136
+ }
137
+ }
138
+ log(level, message, data) {
139
+ const spanStack = this.#getSpanStack();
140
+ const spanId = spanStack[spanStack.length - 1] ?? this.#rootSpanId;
141
+ this.#logs.push({
142
+ spanId,
143
+ traceId: this.#traceId,
144
+ timestampNs: nowNs(),
145
+ level,
146
+ message,
147
+ data
148
+ });
149
+ }
150
+ #getSpanStack() {
151
+ return this.#spanStack;
152
+ }
153
+ flush() {
154
+ if (this.#flushed) {
155
+ return {};
156
+ }
157
+ this.#flushed = true;
158
+ const payload = {};
159
+ if (this.#executionStartMs !== void 0 && this.#executionEndMs !== void 0) {
160
+ payload.executionDurationMs = this.#executionEndMs - this.#executionStartMs;
161
+ }
162
+ if (this.#spans.length > 0) {
163
+ payload.spans = {
164
+ resourceSpans: [
165
+ {
166
+ scopeSpans: [
167
+ {
168
+ scope: { name: "@mastra/client-js/observability", version: "1" },
169
+ spans: this.#spans.map((s) => ({
170
+ traceId: s.traceId,
171
+ spanId: s.spanId,
172
+ parentSpanId: s.parentSpanId,
173
+ name: s.name,
174
+ // OTLP/JSON allows nanosecond timestamps as decimal
175
+ // strings to avoid JS number precision loss for
176
+ // uint64. Use string form.
177
+ startTimeUnixNano: s.startTimeNs.toString(),
178
+ endTimeUnixNano: s.endTimeNs.toString(),
179
+ attributes: attributesToOtlp(s.attributes),
180
+ status: { code: s.statusCode, ...s.statusMessage ? { message: s.statusMessage } : {} },
181
+ // OTLP requires `kind`; default to INTERNAL = 1.
182
+ kind: 1
183
+ }))
184
+ }
185
+ ]
186
+ }
187
+ ]
188
+ };
189
+ }
190
+ if (this.#logs.length > 0) {
191
+ payload.logs = {
192
+ resourceLogs: [
193
+ {
194
+ scopeLogs: [
195
+ {
196
+ scope: { name: "@mastra/client-js/observability", version: "1" },
197
+ logRecords: this.#logs.map((l) => ({
198
+ timeUnixNano: l.timestampNs.toString(),
199
+ observedTimeUnixNano: l.timestampNs.toString(),
200
+ severityNumber: LEVEL_TO_SEVERITY_NUMBER[l.level],
201
+ severityText: LEVEL_TO_SEVERITY_TEXT[l.level],
202
+ body: { stringValue: l.message },
203
+ traceId: l.traceId,
204
+ spanId: l.spanId,
205
+ attributes: l.data ? attributesToOtlp(l.data) : []
206
+ }))
207
+ }
208
+ ]
209
+ }
210
+ ]
211
+ };
212
+ }
213
+ return payload;
214
+ }
215
+ };
216
+ var createObservabilityCollector = (parentContext) => new ObservabilityCollectorImpl(parentContext);
13
217
  function normalizeRoutePath(path) {
14
218
  let normalized = path.trim();
15
219
  if (normalized.includes("..") || normalized.includes("?") || normalized.includes("#")) {
@@ -290,6 +494,67 @@ var BaseResource = class {
290
494
  };
291
495
 
292
496
  // src/resources/agent.ts
497
+ var SIGNAL_RUNTIME_OPTIONS_TTL_MS = 5 * 60 * 1e3;
498
+ var signalRuntimeOptionsByRunId = /* @__PURE__ */ new Map();
499
+ var latestSignalRuntimeOptionsByThread = /* @__PURE__ */ new Map();
500
+ var createSignalRuntimeOptionsEntry = (store, key, streamOptions) => {
501
+ const timeout = setTimeout(() => store.delete(key), SIGNAL_RUNTIME_OPTIONS_TTL_MS);
502
+ timeout.unref?.();
503
+ return { streamOptions, timeout };
504
+ };
505
+ var setSignalRuntimeOptionsEntry = (store, key, streamOptions) => {
506
+ const existing = store.get(key);
507
+ if (existing) clearTimeout(existing.timeout);
508
+ store.set(key, createSignalRuntimeOptionsEntry(store, key, streamOptions));
509
+ };
510
+ var deleteSignalRuntimeOptionsEntry = (store, key) => {
511
+ const existing = store.get(key);
512
+ if (existing) clearTimeout(existing.timeout);
513
+ store.delete(key);
514
+ };
515
+ var noopClientToolObserve = {
516
+ async span(_name, fn) {
517
+ return fn();
518
+ },
519
+ log() {
520
+ }
521
+ };
522
+ function getClientToolObservabilityContext(toolCall) {
523
+ const candidate = toolCall;
524
+ return candidate?.payload?.observability ?? candidate?.observability;
525
+ }
526
+ async function executeClientToolWithObservability({
527
+ clientTool,
528
+ args,
529
+ toolName,
530
+ parentContext,
531
+ executeContext
532
+ }) {
533
+ const collector = parentContext ? createObservabilityCollector(parentContext) : void 0;
534
+ const observe = collector ? {
535
+ span: collector.span.bind(collector),
536
+ log: collector.log.bind(collector)
537
+ } : noopClientToolObserve;
538
+ const runExecute = () => clientTool.execute(args, {
539
+ ...executeContext,
540
+ observe
541
+ });
542
+ const result = collector ? await collector.withContext(runExecute) : await runExecute();
543
+ if (!parentContext) {
544
+ return { result };
545
+ }
546
+ const flushed = collector?.flush();
547
+ if (flushed) {
548
+ flushed.toolName = toolName;
549
+ }
550
+ return {
551
+ result,
552
+ observability: {
553
+ parentContext,
554
+ ...flushed ? { payload: flushed } : {}
555
+ }
556
+ };
557
+ }
293
558
  async function executeToolCallAndRespond({
294
559
  response,
295
560
  params,
@@ -307,31 +572,39 @@ async function executeToolCallAndRespond({
307
572
  for (const toolCall of toolCalls) {
308
573
  const clientTool = params.clientTools?.[toolCall.payload.toolName];
309
574
  if (clientTool && clientTool.execute) {
310
- const result = await clientTool.execute(toolCall?.payload.args, {
311
- requestContext,
312
- tracingContext: { currentSpan: void 0 },
313
- agent: {
314
- agentId,
315
- messages: response.messages,
316
- toolCallId: toolCall?.payload.toolCallId,
317
- suspend: async () => {
318
- },
319
- threadId,
320
- resourceId
575
+ const { result, observability } = await executeClientToolWithObservability({
576
+ clientTool,
577
+ args: toolCall?.payload.args,
578
+ toolName: toolCall.payload.toolName,
579
+ parentContext: getClientToolObservabilityContext(toolCall),
580
+ executeContext: {
581
+ requestContext,
582
+ tracingContext: { currentSpan: void 0 },
583
+ agent: {
584
+ agentId,
585
+ messages: response.messages,
586
+ toolCallId: toolCall?.payload.toolCallId,
587
+ suspend: async () => {
588
+ },
589
+ threadId,
590
+ resourceId
591
+ }
321
592
  }
322
593
  });
594
+ const toolResultContent = {
595
+ type: "tool-result",
596
+ toolCallId: toolCall.payload.toolCallId,
597
+ toolName: toolCall.payload.toolName,
598
+ result
599
+ };
600
+ if (observability) {
601
+ toolResultContent.__mastraObservability = observability;
602
+ }
323
603
  const newMessages = [
324
604
  ...response.response.messages || [],
325
605
  {
326
606
  role: "tool",
327
- content: [
328
- {
329
- type: "tool-result",
330
- toolCallId: toolCall.payload.toolCallId,
331
- toolName: toolCall.payload.toolName,
332
- result
333
- }
334
- ]
607
+ content: [toolResultContent]
335
608
  }
336
609
  ];
337
610
  const updatedMessages = threadId ? newMessages : [...Array.isArray(params.messages) ? params.messages : [], ...newMessages];
@@ -436,6 +709,52 @@ var Agent = class extends BaseResource {
436
709
  const queryString = searchParams.toString();
437
710
  return queryString ? `${delimiter}${queryString}` : "";
438
711
  }
712
+ getSignalRuntimeRunKey(runId) {
713
+ return `${this.options.baseUrl}|${this.apiPrefix}|${this.agentId}|${runId}`;
714
+ }
715
+ getSignalRuntimeThreadKey({
716
+ resourceId,
717
+ threadId
718
+ }) {
719
+ if (!threadId) return void 0;
720
+ return `${this.options.baseUrl}|${this.apiPrefix}|${this.agentId}|${resourceId ?? ""}|${threadId}`;
721
+ }
722
+ setSignalRuntimeOptions({
723
+ runId,
724
+ resourceId,
725
+ threadId,
726
+ streamOptions
727
+ }) {
728
+ const threadKey = this.getSignalRuntimeThreadKey({ resourceId, threadId });
729
+ if (runId) {
730
+ setSignalRuntimeOptionsEntry(signalRuntimeOptionsByRunId, this.getSignalRuntimeRunKey(runId), streamOptions);
731
+ if (threadKey) deleteSignalRuntimeOptionsEntry(latestSignalRuntimeOptionsByThread, threadKey);
732
+ return;
733
+ }
734
+ if (threadKey) {
735
+ setSignalRuntimeOptionsEntry(latestSignalRuntimeOptionsByThread, threadKey, streamOptions);
736
+ }
737
+ }
738
+ getSignalRuntimeOptions({
739
+ runId,
740
+ resourceId,
741
+ threadId
742
+ }) {
743
+ if (runId) {
744
+ const runOptions = signalRuntimeOptionsByRunId.get(this.getSignalRuntimeRunKey(runId));
745
+ if (runOptions) return runOptions.streamOptions;
746
+ }
747
+ const threadKey = this.getSignalRuntimeThreadKey({ resourceId, threadId });
748
+ return threadKey ? latestSignalRuntimeOptionsByThread.get(threadKey)?.streamOptions : void 0;
749
+ }
750
+ deleteSignalRuntimeOptions(runId) {
751
+ if (!runId) return;
752
+ deleteSignalRuntimeOptionsEntry(signalRuntimeOptionsByRunId, this.getSignalRuntimeRunKey(runId));
753
+ }
754
+ deleteLatestSignalRuntimeOptions({ resourceId, threadId }) {
755
+ const threadKey = this.getSignalRuntimeThreadKey({ resourceId, threadId });
756
+ if (threadKey) deleteSignalRuntimeOptionsEntry(latestSignalRuntimeOptionsByThread, threadKey);
757
+ }
439
758
  /**
440
759
  * Retrieves details about the agent
441
760
  * @param requestContext - Optional request context to pass as query parameter
@@ -481,32 +800,227 @@ var Agent = class extends BaseResource {
481
800
  /**
482
801
  * @experimental Agent signals are experimental and may change in a future release.
483
802
  */
484
- sendSignal(params) {
485
- return this.request(`/agents/${this.agentId}/signals`, {
486
- method: "POST",
487
- body: params
488
- });
803
+ async sendSignal(params) {
804
+ const streamOptions = params.ifIdle?.streamOptions;
805
+ if (streamOptions) {
806
+ this.setSignalRuntimeOptions({
807
+ resourceId: params.resourceId,
808
+ threadId: params.threadId,
809
+ streamOptions
810
+ });
811
+ }
812
+ const body = params.ifIdle?.streamOptions ? {
813
+ ...params,
814
+ ifIdle: {
815
+ ...params.ifIdle,
816
+ streamOptions: {
817
+ ...params.ifIdle.streamOptions,
818
+ requestContext: parseClientRequestContext(params.ifIdle.streamOptions.requestContext),
819
+ clientTools: processClientTools(params.ifIdle.streamOptions.clientTools)
820
+ }
821
+ }
822
+ } : params;
823
+ let response;
824
+ try {
825
+ response = await this.request(`/agents/${this.agentId}/signals`, {
826
+ method: "POST",
827
+ body
828
+ });
829
+ } catch (error) {
830
+ if (streamOptions) {
831
+ this.deleteLatestSignalRuntimeOptions({ resourceId: params.resourceId, threadId: params.threadId });
832
+ }
833
+ throw error;
834
+ }
835
+ if (streamOptions) {
836
+ this.setSignalRuntimeOptions({
837
+ runId: response.runId,
838
+ resourceId: params.resourceId,
839
+ threadId: params.threadId,
840
+ streamOptions
841
+ });
842
+ }
843
+ return response;
489
844
  }
490
845
  /**
491
846
  * @experimental Agent signals are experimental and may change in a future release.
492
847
  */
493
848
  async subscribeToThread(params) {
494
- const streamResponse = await this.request(`/agents/${this.agentId}/threads/subscribe`, {
849
+ const { resourceId, threadId } = params;
850
+ const requestSubscription = () => this.request(`/agents/${this.agentId}/threads/subscribe`, {
495
851
  method: "POST",
496
- body: params,
852
+ body: { resourceId, threadId },
497
853
  stream: true
498
854
  });
855
+ const streamResponse = await requestSubscription();
499
856
  if (!streamResponse.body) {
500
857
  throw new Error("No response body");
501
858
  }
502
- streamResponse.processDataStream = async ({
503
- onChunk
504
- }) => {
505
- await processMastraStream({
506
- stream: streamResponse.body,
507
- onChunk,
508
- signal: this.options.abortSignal
509
- });
859
+ const agent = this;
860
+ streamResponse.processDataStream = async ({ onChunk, reconnect }) => {
861
+ const pendingToolCallsByRunId = /* @__PURE__ */ new Map();
862
+ const handleSubscribedChunk = async (chunk) => {
863
+ if (chunk.type === "tool-call") {
864
+ const payload = chunk.payload;
865
+ const toolCallId = payload?.toolCallId;
866
+ const toolName = payload?.toolName;
867
+ const runId2 = chunk.runId;
868
+ if (!toolCallId || !toolName || !runId2) return;
869
+ const pendingToolCalls2 = pendingToolCallsByRunId.get(runId2) ?? [];
870
+ pendingToolCalls2.push({ toolCallId, toolName, args: payload.args, observability: payload.observability });
871
+ pendingToolCallsByRunId.set(runId2, pendingToolCalls2);
872
+ return;
873
+ }
874
+ if (chunk.type !== "finish") return;
875
+ const runId = chunk.runId;
876
+ const finishPayload = chunk;
877
+ if (!runId) return;
878
+ if (finishPayload.payload?.stepResult?.reason !== "tool-calls") {
879
+ agent.deleteSignalRuntimeOptions(runId);
880
+ return;
881
+ }
882
+ const pendingToolCalls = pendingToolCallsByRunId.get(runId);
883
+ pendingToolCallsByRunId.delete(runId);
884
+ if (!pendingToolCalls?.length) {
885
+ agent.deleteSignalRuntimeOptions(runId);
886
+ return;
887
+ }
888
+ const activeRuntimeOptions = agent.getSignalRuntimeOptions({ runId, resourceId, threadId });
889
+ const activeClientTools = activeRuntimeOptions?.clientTools;
890
+ if (!activeClientTools) {
891
+ agent.deleteSignalRuntimeOptions(runId);
892
+ return;
893
+ }
894
+ const activeRequestContext = activeRuntimeOptions.requestContext;
895
+ const processedClientTools = processClientTools(activeClientTools);
896
+ const processedRequestContext = parseClientRequestContext(activeRequestContext);
897
+ const toolResultMessages = [];
898
+ for (const toolCall of pendingToolCalls) {
899
+ const clientTool = activeClientTools[toolCall.toolName];
900
+ if (!clientTool || typeof clientTool.execute !== "function") continue;
901
+ let result;
902
+ let observability;
903
+ try {
904
+ const execution = await executeClientToolWithObservability({
905
+ clientTool,
906
+ args: toolCall.args,
907
+ toolName: toolCall.toolName,
908
+ parentContext: toolCall.observability,
909
+ executeContext: {
910
+ requestContext: activeRequestContext,
911
+ tracingContext: { currentSpan: void 0 },
912
+ agent: {
913
+ agentId: agent.agentId,
914
+ messages: finishPayload.payload?.messages?.nonUser ?? [],
915
+ toolCallId: toolCall.toolCallId,
916
+ suspend: async () => {
917
+ },
918
+ threadId,
919
+ resourceId
920
+ }
921
+ }
922
+ });
923
+ result = execution.result;
924
+ observability = execution.observability;
925
+ } catch (error) {
926
+ result = { error: String(error) };
927
+ }
928
+ const toolResultContent = {
929
+ type: "tool-result",
930
+ toolCallId: toolCall.toolCallId,
931
+ toolName: toolCall.toolName,
932
+ result
933
+ };
934
+ if (observability) {
935
+ toolResultContent.__mastraObservability = observability;
936
+ }
937
+ await onChunk({
938
+ type: "tool-result",
939
+ runId,
940
+ payload: toolResultContent
941
+ });
942
+ toolResultMessages.push({
943
+ role: "tool",
944
+ content: [toolResultContent]
945
+ });
946
+ }
947
+ if (toolResultMessages.length === 0) {
948
+ agent.deleteSignalRuntimeOptions(runId);
949
+ return;
950
+ }
951
+ try {
952
+ const continuation = await agent.streamUntilIdle(
953
+ [...finishPayload.payload?.messages?.nonUser ?? [], ...toolResultMessages],
954
+ {
955
+ ...activeRuntimeOptions,
956
+ runId: v4(),
957
+ requestContext: processedRequestContext,
958
+ memory: threadId ? { thread: threadId, resource: resourceId } : void 0,
959
+ clientTools: processedClientTools
960
+ }
961
+ );
962
+ try {
963
+ void continuation.body?.cancel?.();
964
+ } catch {
965
+ }
966
+ } catch (error) {
967
+ console.error("Error running client-tool continuation:", error);
968
+ } finally {
969
+ agent.deleteSignalRuntimeOptions(runId);
970
+ }
971
+ };
972
+ const reconnectOptions = reconnect === true ? { maxRetries: Infinity, delayMs: 1e3 } : reconnect ? { maxRetries: reconnect.maxRetries ?? Infinity, delayMs: reconnect.delayMs ?? 1e3 } : null;
973
+ let response = streamResponse;
974
+ let attempts = 0;
975
+ const onChunkErrorSentinel = /* @__PURE__ */ Symbol("onChunkErrorSentinel");
976
+ const guardedOnChunk = async (chunk) => {
977
+ try {
978
+ await onChunk(chunk);
979
+ await handleSubscribedChunk(chunk);
980
+ } catch (cause) {
981
+ throw { [onChunkErrorSentinel]: true, cause };
982
+ }
983
+ };
984
+ while (true) {
985
+ if (!response.body) {
986
+ throw new Error("No response body");
987
+ }
988
+ try {
989
+ await processMastraStream({
990
+ stream: response.body,
991
+ onChunk: guardedOnChunk,
992
+ signal: this.options.abortSignal
993
+ });
994
+ } catch (error) {
995
+ if (typeof error === "object" && error !== null && error[onChunkErrorSentinel]) {
996
+ throw error.cause;
997
+ }
998
+ if (!reconnectOptions || this.options.abortSignal?.aborted || attempts >= reconnectOptions.maxRetries) {
999
+ throw error;
1000
+ }
1001
+ }
1002
+ if (!reconnectOptions || this.options.abortSignal?.aborted || attempts >= reconnectOptions.maxRetries) {
1003
+ return;
1004
+ }
1005
+ while (attempts < reconnectOptions.maxRetries) {
1006
+ attempts++;
1007
+ if (this.options.abortSignal?.aborted) {
1008
+ return;
1009
+ }
1010
+ await new Promise((resolve) => setTimeout(resolve, reconnectOptions.delayMs));
1011
+ if (this.options.abortSignal?.aborted) {
1012
+ return;
1013
+ }
1014
+ try {
1015
+ response = await requestSubscription();
1016
+ break;
1017
+ } catch (error) {
1018
+ if (this.options.abortSignal?.aborted || attempts >= reconnectOptions.maxRetries) {
1019
+ throw error;
1020
+ }
1021
+ }
1022
+ }
1023
+ }
510
1024
  };
511
1025
  return streamResponse;
512
1026
  }
@@ -654,17 +1168,23 @@ var Agent = class extends BaseResource {
654
1168
  for (const toolCall of toolCalls) {
655
1169
  const clientTool = params.clientTools?.[toolCall.toolName];
656
1170
  if (clientTool && clientTool.execute) {
657
- const result = await clientTool.execute(toolCall?.args, {
658
- requestContext,
659
- tracingContext: { currentSpan: void 0 },
660
- agent: {
661
- agentId: this.agentId,
662
- messages: response.messages,
663
- toolCallId: toolCall?.toolCallId,
664
- suspend: async () => {
665
- },
666
- threadId,
667
- resourceId
1171
+ const { result, observability } = await executeClientToolWithObservability({
1172
+ clientTool,
1173
+ args: toolCall?.args,
1174
+ toolName: toolCall.toolName,
1175
+ parentContext: getClientToolObservabilityContext(toolCall),
1176
+ executeContext: {
1177
+ requestContext,
1178
+ tracingContext: { currentSpan: void 0 },
1179
+ agent: {
1180
+ agentId: this.agentId,
1181
+ messages: response.messages,
1182
+ toolCallId: toolCall?.toolCallId,
1183
+ suspend: async () => {
1184
+ },
1185
+ threadId,
1186
+ resourceId
1187
+ }
668
1188
  }
669
1189
  });
670
1190
  const updatedMessages = [
@@ -676,7 +1196,8 @@ var Agent = class extends BaseResource {
676
1196
  type: "tool-result",
677
1197
  toolCallId: toolCall.toolCallId,
678
1198
  toolName: toolCall.toolName,
679
- result
1199
+ result,
1200
+ ...observability ? { __mastraObservability: observability } : {}
680
1201
  }
681
1202
  ]
682
1203
  }
@@ -876,14 +1397,17 @@ var Agent = class extends BaseResource {
876
1397
  text: "",
877
1398
  step,
878
1399
  toolName: value.toolName,
879
- index: message.toolInvocations.length
1400
+ index: message.toolInvocations.length,
1401
+ observability: getClientToolObservabilityContext(value)
880
1402
  };
1403
+ const observability = getClientToolObservabilityContext(value);
881
1404
  const invocation = {
882
1405
  state: "partial-call",
883
1406
  step,
884
1407
  toolCallId: value.toolCallId,
885
1408
  toolName: value.toolName,
886
- args: void 0
1409
+ args: void 0,
1410
+ ...observability ? { observability } : {}
887
1411
  };
888
1412
  message.toolInvocations.push(invocation);
889
1413
  updateToolInvocationPart(value.toolCallId, invocation);
@@ -898,7 +1422,8 @@ var Agent = class extends BaseResource {
898
1422
  step: partialToolCall.step,
899
1423
  toolCallId: value.toolCallId,
900
1424
  toolName: partialToolCall.toolName,
901
- args: partialArgs
1425
+ args: partialArgs,
1426
+ ...partialToolCall.observability ? { observability: partialToolCall.observability } : {}
902
1427
  };
903
1428
  message.toolInvocations[partialToolCall.index] = invocation;
904
1429
  updateToolInvocationPart(value.toolCallId, invocation);
@@ -1023,6 +1548,7 @@ var Agent = class extends BaseResource {
1023
1548
  update,
1024
1549
  onToolCall,
1025
1550
  onFinish,
1551
+ onStreamChunk,
1026
1552
  getCurrentDate = () => /* @__PURE__ */ new Date(),
1027
1553
  lastMessage
1028
1554
  }) {
@@ -1090,6 +1616,7 @@ var Agent = class extends BaseResource {
1090
1616
  // TODO: casting as any here because the stream types were all typed as any before in core.
1091
1617
  // but this is completely wrong and this fn is probably broken. Remove ":any" and you'll see a bunch of type errors
1092
1618
  onChunk: async (chunk) => {
1619
+ onStreamChunk?.(chunk);
1093
1620
  switch (chunk.type) {
1094
1621
  case "tripwire": {
1095
1622
  message.parts.push({
@@ -1191,6 +1718,7 @@ var Agent = class extends BaseResource {
1191
1718
  execUpdate();
1192
1719
  }
1193
1720
  }
1721
+ break;
1194
1722
  }
1195
1723
  case "tool-call-input-streaming-start": {
1196
1724
  if (message.toolInvocations == null) {
@@ -1200,14 +1728,17 @@ var Agent = class extends BaseResource {
1200
1728
  text: "",
1201
1729
  step,
1202
1730
  toolName: chunk.payload.toolName,
1203
- index: message.toolInvocations.length
1731
+ index: message.toolInvocations.length,
1732
+ observability: getClientToolObservabilityContext(chunk)
1204
1733
  };
1734
+ const observability = getClientToolObservabilityContext(chunk);
1205
1735
  const invocation = {
1206
1736
  state: "partial-call",
1207
1737
  step,
1208
1738
  toolCallId: chunk.payload.toolCallId,
1209
1739
  toolName: chunk.payload.toolName,
1210
- args: chunk.payload.args
1740
+ args: chunk.payload.args,
1741
+ ...observability ? { observability } : {}
1211
1742
  };
1212
1743
  message.toolInvocations.push(invocation);
1213
1744
  updateToolInvocationPart(chunk.payload.toolCallId, invocation);
@@ -1223,7 +1754,8 @@ var Agent = class extends BaseResource {
1223
1754
  step: partialToolCall.step,
1224
1755
  toolCallId: chunk.payload.toolCallId,
1225
1756
  toolName: partialToolCall.toolName,
1226
- args: partialArgs
1757
+ args: partialArgs,
1758
+ ...partialToolCall.observability ? { observability: partialToolCall.observability } : {}
1227
1759
  };
1228
1760
  message.toolInvocations[partialToolCall.index] = invocation;
1229
1761
  updateToolInvocationPart(chunk.payload.toolCallId, invocation);
@@ -1303,6 +1835,7 @@ var Agent = class extends BaseResource {
1303
1835
  try {
1304
1836
  let toolCalls = [];
1305
1837
  let messages = [];
1838
+ let streamRunId = processedParams.runId;
1306
1839
  const [streamForController, streamForProcessing] = response.body.tee();
1307
1840
  const pipePromise = streamForController.pipeTo(
1308
1841
  new WritableStream({
@@ -1342,27 +1875,90 @@ var Agent = class extends BaseResource {
1342
1875
  if (finishReason === "tool-calls") {
1343
1876
  const toolCall = [...message?.parts ?? []].reverse().find((part) => part.type === "tool-invocation")?.toolInvocation;
1344
1877
  if (toolCall) {
1345
- toolCalls.push(toolCall);
1878
+ const toolInvocationWithMetadata = message?.toolInvocations?.find(
1879
+ (invocation) => invocation.toolCallId === toolCall.toolCallId
1880
+ );
1881
+ toolCalls.push({
1882
+ ...toolInvocationWithMetadata,
1883
+ ...toolCall,
1884
+ ...!getClientToolObservabilityContext(toolCall) && toolInvocationWithMetadata ? { observability: getClientToolObservabilityContext(toolInvocationWithMetadata) } : {}
1885
+ });
1346
1886
  }
1347
1887
  let shouldExecuteClientTool = false;
1348
1888
  for (const toolCall2 of toolCalls) {
1349
1889
  const clientTool = processedParams.clientTools?.[toolCall2.toolName];
1350
1890
  if (clientTool && clientTool.execute) {
1351
1891
  shouldExecuteClientTool = true;
1352
- const result = await clientTool.execute(toolCall2?.args, {
1353
- requestContext: processedParams.requestContext,
1354
- // TODO: Pass proper tracing context when client-js supports tracing
1355
- tracingContext: { currentSpan: void 0 },
1356
- agent: {
1357
- agentId: this.agentId,
1358
- messages: response.messages,
1359
- toolCallId: toolCall2?.toolCallId,
1360
- suspend: async () => {
1361
- },
1362
- threadId,
1363
- resourceId
1364
- }
1365
- });
1892
+ const runId = streamRunId ?? toolCall2.toolCallId;
1893
+ let result;
1894
+ let observability;
1895
+ let synthetic;
1896
+ try {
1897
+ ({ result, observability } = await executeClientToolWithObservability({
1898
+ clientTool,
1899
+ args: toolCall2?.args,
1900
+ toolName: toolCall2.toolName,
1901
+ parentContext: getClientToolObservabilityContext(toolCall2),
1902
+ executeContext: {
1903
+ requestContext: processedParams.requestContext,
1904
+ tracingContext: { currentSpan: void 0 },
1905
+ agent: {
1906
+ agentId: this.agentId,
1907
+ messages: response.messages,
1908
+ toolCallId: toolCall2?.toolCallId,
1909
+ suspend: async () => {
1910
+ },
1911
+ threadId,
1912
+ resourceId
1913
+ }
1914
+ }
1915
+ }));
1916
+ synthetic = {
1917
+ type: "tool-result",
1918
+ runId,
1919
+ from: "AGENT",
1920
+ payload: {
1921
+ toolCallId: toolCall2.toolCallId,
1922
+ toolName: toolCall2.toolName,
1923
+ result,
1924
+ isError: false,
1925
+ providerExecuted: false
1926
+ }
1927
+ };
1928
+ } catch (error) {
1929
+ synthetic = {
1930
+ type: "tool-error",
1931
+ runId,
1932
+ from: "AGENT",
1933
+ payload: {
1934
+ toolCallId: toolCall2.toolCallId,
1935
+ toolName: toolCall2.toolName,
1936
+ error,
1937
+ args: toolCall2?.args,
1938
+ providerExecuted: false
1939
+ }
1940
+ };
1941
+ result = { error: error instanceof Error ? error.message : String(error) };
1942
+ }
1943
+ try {
1944
+ await pipePromise;
1945
+ } catch {
1946
+ }
1947
+ try {
1948
+ const errorForSerialization = synthetic.type === "tool-error" ? synthetic.payload.error : void 0;
1949
+ const serializedError = errorForSerialization instanceof Error ? {
1950
+ name: errorForSerialization.name,
1951
+ message: errorForSerialization.message,
1952
+ stack: errorForSerialization.stack
1953
+ } : errorForSerialization;
1954
+ const payloadForWire = synthetic.type === "tool-error" ? { ...synthetic, payload: { ...synthetic.payload, error: serializedError } } : synthetic;
1955
+ const sseLine = `data: ${JSON.stringify(payloadForWire)}
1956
+
1957
+ `;
1958
+ controller.enqueue(new TextEncoder().encode(sseLine));
1959
+ } catch (enqueueErr) {
1960
+ console.error("Failed to enqueue synthetic tool-result chunk:", enqueueErr);
1961
+ }
1366
1962
  const lastMessageRaw = messages[messages.length - 1];
1367
1963
  const lastMessage = lastMessageRaw != null ? JSON.parse(JSON.stringify(lastMessageRaw)) : void 0;
1368
1964
  const toolInvocationPart = lastMessage?.parts?.find(
@@ -1372,7 +1968,8 @@ var Agent = class extends BaseResource {
1372
1968
  toolInvocationPart.toolInvocation = {
1373
1969
  ...toolInvocationPart.toolInvocation,
1374
1970
  state: "result",
1375
- result
1971
+ result,
1972
+ ...observability ? { __mastraObservability: observability } : {}
1376
1973
  };
1377
1974
  }
1378
1975
  const toolInvocation = lastMessage?.toolInvocations?.find(
@@ -1381,6 +1978,9 @@ var Agent = class extends BaseResource {
1381
1978
  if (toolInvocation) {
1382
1979
  toolInvocation.state = "result";
1383
1980
  toolInvocation.result = result;
1981
+ if (observability) {
1982
+ toolInvocation.__mastraObservability = observability;
1983
+ }
1384
1984
  }
1385
1985
  const newMessages = lastMessage != null ? [...messages.filter((m) => m.id !== lastMessage.id), lastMessage] : [...messages];
1386
1986
  const updatedMessages = threadId ? newMessages : [...Array.isArray(processedParams.messages) ? processedParams.messages : [], ...newMessages];
@@ -1408,6 +2008,11 @@ var Agent = class extends BaseResource {
1408
2008
  controller.close();
1409
2009
  }
1410
2010
  },
2011
+ onStreamChunk: (chunk) => {
2012
+ if (!streamRunId && typeof chunk.runId === "string") {
2013
+ streamRunId = chunk.runId;
2014
+ }
2015
+ },
1411
2016
  lastMessage: void 0
1412
2017
  }).catch(async (error) => {
1413
2018
  console.error("Error processing stream response:", error);
@@ -1815,23 +2420,35 @@ var Agent = class extends BaseResource {
1815
2420
  if (finishReason === "tool-calls") {
1816
2421
  const toolCall = [...message?.parts ?? []].reverse().find((part) => part.type === "tool-invocation")?.toolInvocation;
1817
2422
  if (toolCall) {
1818
- toolCalls.push(toolCall);
2423
+ const toolInvocationWithMetadata = message?.toolInvocations?.find(
2424
+ (invocation) => invocation.toolCallId === toolCall.toolCallId
2425
+ );
2426
+ toolCalls.push({
2427
+ ...toolInvocationWithMetadata,
2428
+ ...toolCall,
2429
+ ...!getClientToolObservabilityContext(toolCall) && toolInvocationWithMetadata ? { observability: getClientToolObservabilityContext(toolInvocationWithMetadata) } : {}
2430
+ });
1819
2431
  }
1820
2432
  for (const toolCall2 of toolCalls) {
1821
2433
  const clientTool = processedParams.clientTools?.[toolCall2.toolName];
1822
2434
  if (clientTool && clientTool.execute) {
1823
- const result = await clientTool.execute(toolCall2?.args, {
1824
- requestContext: processedParams.requestContext,
1825
- // TODO: Pass proper tracing context when client-js supports tracing
1826
- tracingContext: { currentSpan: void 0 },
1827
- agent: {
1828
- agentId: this.agentId,
1829
- messages: response.messages,
1830
- toolCallId: toolCall2?.toolCallId,
1831
- suspend: async () => {
1832
- },
1833
- threadId,
1834
- resourceId
2435
+ const { result, observability } = await executeClientToolWithObservability({
2436
+ clientTool,
2437
+ args: toolCall2?.args,
2438
+ toolName: toolCall2.toolName,
2439
+ parentContext: getClientToolObservabilityContext(toolCall2),
2440
+ executeContext: {
2441
+ requestContext: processedParams.requestContext,
2442
+ tracingContext: { currentSpan: void 0 },
2443
+ agent: {
2444
+ agentId: this.agentId,
2445
+ messages: response.messages,
2446
+ toolCallId: toolCall2?.toolCallId,
2447
+ suspend: async () => {
2448
+ },
2449
+ threadId,
2450
+ resourceId
2451
+ }
1835
2452
  }
1836
2453
  });
1837
2454
  const lastMessage = JSON.parse(JSON.stringify(messages[messages.length - 1]));
@@ -1842,7 +2459,8 @@ var Agent = class extends BaseResource {
1842
2459
  toolInvocationPart.toolInvocation = {
1843
2460
  ...toolInvocationPart.toolInvocation,
1844
2461
  state: "result",
1845
- result
2462
+ result,
2463
+ ...observability ? { __mastraObservability: observability } : {}
1846
2464
  };
1847
2465
  }
1848
2466
  const toolInvocation = lastMessage?.toolInvocations?.find(
@@ -1851,6 +2469,9 @@ var Agent = class extends BaseResource {
1851
2469
  if (toolInvocation) {
1852
2470
  toolInvocation.state = "result";
1853
2471
  toolInvocation.result = result;
2472
+ if (observability) {
2473
+ toolInvocation.__mastraObservability = observability;
2474
+ }
1854
2475
  }
1855
2476
  const writer = writable.getWriter();
1856
2477
  try {