@mastra/client-js 1.57.0-alpha.0 → 1.57.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@ name: mastra-client-js
3
3
  description: Documentation for @mastra/client-js. Use when working with @mastra/client-js APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/client-js"
6
- version: "1.57.0-alpha.0"
6
+ version: "1.57.0-alpha.2"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.57.0-alpha.0",
2
+ "version": "1.57.0-alpha.2",
3
3
  "package": "@mastra/client-js",
4
4
  "exports": {},
5
5
  "modules": {}
@@ -177,7 +177,7 @@ Switches the active mode.
177
177
 
178
178
  ### `switchModel(modelId, options?)`
179
179
 
180
- Switches the session's active model and persists it as the current model for the active thread. Switching modes doesn't change it. Pass `{ thinkingLevel }` as the second argument to apply and persist a thinking level with the model.
180
+ Switches the session's active model and persists it as the current model for the active thread, independently of mode changes. Pass `{ thinkingLevel }` as the second argument to apply and persist a thinking level with the model.
181
181
 
182
182
  ```typescript
183
183
  await session.switchModel('anthropic/claude-sonnet-5-5', {
@@ -351,6 +351,22 @@ const { messages, initialHistoryPagination } = useChat({
351
351
 
352
352
  The subscription also exposes `abort(options?)` and `unsubscribe()`. `await subscription.abort({ clearPendingSignals: true })` requests an abort and clears pending signals without closing the subscription. Omit the flag to preserve pending input. `unsubscribe()` closes the subscription without aborting the run.
353
353
 
354
+ ### `abortRun(runId)`
355
+
356
+ Requests cancellation of a durable run by its ID, including a run without a memory thread. Cancelling the response body or aborting the client-wide `abortSignal` only disconnects the observer from durable execution. A per-call `abortSignal` passed to a streaming method requests cancellation for you. Use `abortRun()` to stop a run without that signal.
357
+
358
+ ```typescript
359
+ const runId = crypto.randomUUID()
360
+ const response = await agent.stream('Research this topic', { runId })
361
+
362
+ // When the user chooses Stop:
363
+ const { aborted } = await agent.abortRun(runId)
364
+ ```
365
+
366
+ **runId** (`string`): ID of the durable run to cancel.
367
+
368
+ Returns `{ aborted: boolean }`. A remote abort request is asynchronous, so `true` doesn't confirm that the worker has stopped. Unknown or completed runs return `false`. This method requires a durable agent and Agent execution permission. Run ownership checks apply. Runs with memory threads also require `memory:write` permission when fine-grained authorization is enabled. Non-durable agents return HTTP 501.
369
+
354
370
  ### `abortThread()`
355
371
 
356
372
  Requests cancellation of the thread's active run. Set `clearPendingSignals: true` to clear pending signals before aborting.
@@ -748,7 +764,7 @@ response.processDataStream({
748
764
 
749
765
  ### Shape client tool output for the model
750
766
 
751
- Client tools support `toModelOutput` to control what the model receives, including multimodal content such as images. Because client tools execute locally, the mapping also runs on the client after `execute` completes. The transformed output is sent back to the server alongside the raw result, so the raw result stays available for storage and application logic.
767
+ Client tools support `toModelOutput` to control what the model receives, including multimodal content such as images. Because client tools execute locally, the mapping also runs on the client after `execute` completes. The client sends both the transformed output and the raw result back to the server, keeping the raw result available for storage and application logic.
752
768
 
753
769
  ```typescript
754
770
  const screenshotTool = createTool({
@@ -130,7 +130,7 @@ Submitting to an experiment that has a target returns a `400`. Submitting after
130
130
 
131
131
  ## finalizeExperiment()
132
132
 
133
- Marks a caller-driven experiment completed. The server computes per-item counts from the persisted result rows, so the caller keeps no bookkeeping: `succeededCount` (at least one attempt without an error), `failedCount` (every attempt errored), and `skippedCount` (never submitted). Idempotent: finalizing an already-completed experiment returns the stored record.
133
+ Marks a caller-driven experiment completed. The server handles bookkeeping for the caller by computing per-item counts from the persisted result rows: `succeededCount` (at least one attempt without an error), `failedCount` (every attempt errored), and `skippedCount` (never submitted). Idempotent: finalizing an already-completed experiment returns the stored record.
134
134
 
135
135
  **datasetId** (`string`): ID of the dataset the experiment runs against.
136
136
 
package/dist/index.cjs CHANGED
@@ -1080,11 +1080,51 @@ function processClientTools(clientTools) {
1080
1080
  }));
1081
1081
  }
1082
1082
  //#endregion
1083
+ //#region src/utils/sse-event-splitter.ts
1084
+ /** Split SSE events without rescanning or copying an unfinished event on each read. */
1085
+ function createSseEventSplitter() {
1086
+ let parts = [];
1087
+ let pendingNewline = false;
1088
+ return (text, isFinal = false) => {
1089
+ const events = [];
1090
+ let start = 0;
1091
+ if (text) {
1092
+ if (pendingNewline) {
1093
+ if (text.startsWith("\n")) {
1094
+ events.push(parts.join(""));
1095
+ parts = [];
1096
+ start = 1;
1097
+ } else parts.push("\n");
1098
+ pendingNewline = false;
1099
+ }
1100
+ let separator;
1101
+ while ((separator = text.indexOf("\n\n", start)) !== -1) {
1102
+ parts.push(text.slice(start, separator));
1103
+ events.push(parts.join(""));
1104
+ parts = [];
1105
+ start = separator + 2;
1106
+ }
1107
+ if (start < text.length) {
1108
+ pendingNewline = text.endsWith("\n");
1109
+ const end = pendingNewline ? text.length - 1 : text.length;
1110
+ if (start < end) parts.push(text.slice(start, end));
1111
+ }
1112
+ }
1113
+ if (isFinal) {
1114
+ if (pendingNewline) parts.push("\n");
1115
+ events.push(parts.join(""));
1116
+ parts = [];
1117
+ pendingNewline = false;
1118
+ }
1119
+ return events;
1120
+ };
1121
+ }
1122
+ //#endregion
1083
1123
  //#region src/utils/process-mastra-stream.ts
1084
1124
  async function sharedProcessMastraStream({ stream, onChunk, signal }) {
1085
1125
  const reader = stream.getReader();
1086
1126
  const decoder = new TextDecoder();
1087
- let buffer = "";
1127
+ const splitEvents = createSseEventSplitter();
1088
1128
  const abort = () => void reader.cancel();
1089
1129
  if (signal?.aborted) abort();
1090
1130
  else signal?.addEventListener("abort", abort, { once: true });
@@ -1092,9 +1132,7 @@ async function sharedProcessMastraStream({ stream, onChunk, signal }) {
1092
1132
  while (true) {
1093
1133
  const { done, value } = await reader.read();
1094
1134
  if (done) break;
1095
- buffer += decoder.decode(value, { stream: true });
1096
- const lines = buffer.split("\n\n");
1097
- buffer = lines.pop() || "";
1135
+ const lines = splitEvents(decoder.decode(value, { stream: true }));
1098
1136
  for (const line of lines) if (line.startsWith("data: ")) {
1099
1137
  const data = line.slice(6);
1100
1138
  if (data === "[DONE]") return;
@@ -1894,10 +1932,18 @@ var Agent = class extends BaseResource {
1894
1932
  };
1895
1933
  return streamResponse;
1896
1934
  }
1897
- async abortThread(params) {
1935
+ /** Stops execution of a run, including runs without a memory thread. */
1936
+ async abortRun(runId, options) {
1937
+ return this.request(`/agents/${this.agentId}/runs/${encodeURIComponent(runId)}/abort`, {
1938
+ method: "POST",
1939
+ signal: options?.signal
1940
+ });
1941
+ }
1942
+ async abortThread(params, options) {
1898
1943
  const { resourceId, threadId, clearPendingSignals, expectedRunId } = params;
1899
1944
  return this.request(`/agents/${this.agentId}/threads/abort`, {
1900
1945
  method: "POST",
1946
+ signal: options?.signal,
1901
1947
  body: {
1902
1948
  resourceId,
1903
1949
  threadId,
@@ -2631,10 +2677,11 @@ var Agent = class extends BaseResource {
2631
2677
  }
2632
2678
  /**
2633
2679
  * Creates the consumer-facing stream for the streaming methods. Cancelling the
2634
- * returned stream (or aborting `externalSignal`) aborts the underlying request
2635
- * and stops any client-tool continuations.
2680
+ * returned stream or the client-wide signal detaches the HTTP observer and
2681
+ * stops any client-tool continuations. Per-call execution cancellation is
2682
+ * handled separately by processStreamResponse.
2636
2683
  */
2637
- createCancellableStream(externalSignal) {
2684
+ createCancellableStream() {
2638
2685
  const abortController = new AbortController();
2639
2686
  let readableController;
2640
2687
  return {
@@ -2647,10 +2694,63 @@ var Agent = class extends BaseResource {
2647
2694
  }
2648
2695
  }),
2649
2696
  controller: readableController,
2650
- signal: mergeAbortSignals(abortController.signal, externalSignal, this.options.abortSignal)
2697
+ signal: mergeAbortSignals(abortController.signal, this.options.abortSignal)
2651
2698
  };
2652
2699
  }
2653
2700
  async processStreamResponse(processedParams, controller, route = "stream", signal) {
2701
+ const suppliedRunId = processedParams.runId;
2702
+ processedParams = {
2703
+ ...processedParams,
2704
+ runId: processedParams.runId ?? (0, _lukeed_uuid.v4)()
2705
+ };
2706
+ const callerSignal = processedParams.abortSignal;
2707
+ callerSignal?.throwIfAborted();
2708
+ const transportAbort = new AbortController();
2709
+ const cancellationAbort = new AbortController();
2710
+ const detachSignal = signal;
2711
+ signal = mergeAbortSignals(detachSignal, transportAbort.signal);
2712
+ let cancellationTimer;
2713
+ const stopCancellation = () => cancellationAbort.abort();
2714
+ const cancelExecution = () => {
2715
+ cancellationTimer = setTimeout(() => cancellationAbort.abort(/* @__PURE__ */ new Error("Agent run cancellation was not acknowledged")), 1e4);
2716
+ (async () => {
2717
+ try {
2718
+ while (true) {
2719
+ cancellationAbort.signal.throwIfAborted();
2720
+ if ((await this.abortRun(processedParams.runId, { signal: cancellationAbort.signal })).aborted) return;
2721
+ await new Promise((resolve, reject) => {
2722
+ const onAbort = () => {
2723
+ clearTimeout(timer);
2724
+ reject(cancellationAbort.signal.reason);
2725
+ };
2726
+ const timer = setTimeout(() => {
2727
+ cancellationAbort.signal.removeEventListener("abort", onAbort);
2728
+ resolve();
2729
+ }, 100);
2730
+ cancellationAbort.signal.addEventListener("abort", onAbort, { once: true });
2731
+ if (cancellationAbort.signal.aborted) onAbort();
2732
+ });
2733
+ }
2734
+ } catch (error) {
2735
+ if (!cancellationAbort.signal.aborted && !(error instanceof Error && "status" in error && (error.status === 404 || error.status === 501))) try {
2736
+ controller.error(error);
2737
+ } catch {}
2738
+ } finally {
2739
+ clearTimeout(cancellationTimer);
2740
+ transportAbort.abort(callerSignal?.reason);
2741
+ }
2742
+ })();
2743
+ };
2744
+ const cleanupCancellation = () => {
2745
+ callerSignal?.removeEventListener("abort", cancelExecution);
2746
+ detachSignal?.removeEventListener("abort", stopCancellation);
2747
+ clearTimeout(cancellationTimer);
2748
+ stopCancellation();
2749
+ };
2750
+ detachSignal?.addEventListener("abort", stopCancellation, { once: true });
2751
+ if (detachSignal?.aborted) stopCancellation();
2752
+ callerSignal?.addEventListener("abort", cancelExecution, { once: true });
2753
+ if (callerSignal?.aborted) cancelExecution();
2654
2754
  const { memory } = processedParams ?? {};
2655
2755
  const { resource, thread } = memory ?? {};
2656
2756
  const threadId = processedParams.threadId ?? (typeof thread === "string" ? thread : thread?.id);
@@ -2667,9 +2767,15 @@ var Agent = class extends BaseResource {
2667
2767
  body: requestBody,
2668
2768
  stream: true,
2669
2769
  signal
2770
+ }).catch((error) => {
2771
+ cleanupCancellation();
2772
+ throw error;
2670
2773
  });
2671
- if (!response.body) throw new Error("No response body");
2672
- const isAborted = () => Boolean(signal?.aborted);
2774
+ if (!response.body) {
2775
+ cleanupCancellation();
2776
+ throw new Error("No response body");
2777
+ }
2778
+ const isAborted = () => Boolean(signal?.aborted || callerSignal?.aborted);
2673
2779
  const isAbortError = (error) => isAborted() || error?.name === "AbortError";
2674
2780
  const safeClose = () => {
2675
2781
  try {
@@ -2682,18 +2788,16 @@ var Agent = class extends BaseResource {
2682
2788
  };
2683
2789
  try {
2684
2790
  let messages = [];
2685
- let streamRunId = processedParams.runId;
2791
+ let streamRunId = suppliedRunId;
2686
2792
  let finishedHistory;
2687
2793
  let suspended = false;
2688
2794
  const resumedToolCallIds = /* @__PURE__ */ new Set();
2689
2795
  const releaseOpenStream = streamRunId ? this.trackOpenStream(streamRunId) : () => {};
2690
2796
  const [streamForController, streamForProcessing] = response.body.tee();
2691
2797
  const decoder = new TextDecoder();
2692
- let pendingText = "";
2798
+ const splitEvents = createSseEventSplitter();
2693
2799
  const enqueueReadableText = (text, isFinal = false) => {
2694
- pendingText += text;
2695
- const lines = pendingText.split("\n\n");
2696
- pendingText = isFinal ? "" : lines.pop() ?? "";
2800
+ const lines = splitEvents(text, isFinal);
2697
2801
  for (const line of lines) {
2698
2802
  if (!line.startsWith("data: ") || !line.includes("\"type\":\"finish\"")) continue;
2699
2803
  try {
@@ -2900,10 +3004,12 @@ var Agent = class extends BaseResource {
2900
3004
  } catch {}
2901
3005
  safeClose();
2902
3006
  }).finally(() => {
3007
+ cleanupCancellation();
2903
3008
  signal?.removeEventListener("abort", onAbort);
2904
3009
  releaseOpenStream();
2905
3010
  });
2906
3011
  } catch (error) {
3012
+ cleanupCancellation();
2907
3013
  if (!isAbortError(error)) console.error("Error processing stream response:", error);
2908
3014
  }
2909
3015
  return response;
@@ -3001,7 +3107,7 @@ var Agent = class extends BaseResource {
3001
3107
  clientTools: processClientTools(params.clientToolsResolver?.() ?? params.clientTools),
3002
3108
  structuredOutput
3003
3109
  };
3004
- const { readable, controller: readableController, signal } = this.createCancellableStream(params.abortSignal);
3110
+ const { readable, controller: readableController, signal } = this.createCancellableStream();
3005
3111
  const response = await this.processStreamResponse(processedParams, readableController, "stream", signal);
3006
3112
  const streamResponse = new Response(readable, {
3007
3113
  status: response.status,
@@ -3032,7 +3138,7 @@ var Agent = class extends BaseResource {
3032
3138
  clientTools: processClientTools(params.clientToolsResolver?.() ?? params.clientTools),
3033
3139
  structuredOutput
3034
3140
  };
3035
- const { readable, controller: readableController, signal } = this.createCancellableStream(params.abortSignal);
3141
+ const { readable, controller: readableController, signal } = this.createCancellableStream();
3036
3142
  const response = await this.processStreamResponse(processedParams, readableController, "stream-until-idle", signal);
3037
3143
  const streamResponse = new Response(readable, {
3038
3144
  status: response.status,
@@ -3076,7 +3182,7 @@ var Agent = class extends BaseResource {
3076
3182
  requestContext: parseClientRequestContext(requestContext),
3077
3183
  clientTools: processClientTools(executableClientTools)
3078
3184
  };
3079
- const { readable, controller: readableController, signal } = this.createCancellableStream(params.abortSignal);
3185
+ const { readable, controller: readableController, signal } = this.createCancellableStream();
3080
3186
  const response = await this.processStreamResponse(processedParams, readableController, "approve-tool-call", signal).catch((error) => {
3081
3187
  releaseHold({ force: true });
3082
3188
  throw error;
@@ -3113,7 +3219,7 @@ var Agent = class extends BaseResource {
3113
3219
  requestContext: parseClientRequestContext(requestContext),
3114
3220
  clientTools: processClientTools(executableClientTools)
3115
3221
  };
3116
- const { readable, controller: readableController, signal } = this.createCancellableStream(params.abortSignal);
3222
+ const { readable, controller: readableController, signal } = this.createCancellableStream();
3117
3223
  const response = await this.processStreamResponse(processedParams, readableController, "decline-tool-call", signal).catch((error) => {
3118
3224
  releaseHold({ force: true });
3119
3225
  throw error;
@@ -3189,7 +3295,7 @@ var Agent = class extends BaseResource {
3189
3295
  schema: (0, _mastra_schema_compat_schema.standardSchemaToJSONSchema)((0, _mastra_schema_compat_schema.toStandardSchema)(options.structuredOutput.schema))
3190
3296
  } : void 0
3191
3297
  };
3192
- const { readable, controller: readableController, signal } = this.createCancellableStream(options.abortSignal);
3298
+ const { readable, controller: readableController, signal } = this.createCancellableStream();
3193
3299
  const response = await this.processStreamResponse(processedParams, readableController, "resume-stream", signal).catch((error) => {
3194
3300
  releaseHold({ force: true });
3195
3301
  throw error;
@@ -3258,7 +3364,7 @@ var Agent = class extends BaseResource {
3258
3364
  schema: (0, _mastra_schema_compat_schema.standardSchemaToJSONSchema)((0, _mastra_schema_compat_schema.toStandardSchema)(options.structuredOutput.schema))
3259
3365
  } : void 0
3260
3366
  };
3261
- const { readable, controller: readableController, signal } = this.createCancellableStream(options.abortSignal);
3367
+ const { readable, controller: readableController, signal } = this.createCancellableStream();
3262
3368
  const response = await this.processStreamResponse(processedParams, readableController, "resume-stream-until-idle", signal).catch((error) => {
3263
3369
  releaseHold({ force: true });
3264
3370
  throw error;
@@ -4416,7 +4522,7 @@ var Workflow = class extends BaseResource {
4416
4522
  */
4417
4523
  async createRun(params) {
4418
4524
  const searchParams = new URLSearchParams();
4419
- if (!!params?.runId) searchParams.set("runId", params.runId);
4525
+ if (params?.runId) searchParams.set("runId", params.runId);
4420
4526
  const res = await this.request(`/workflows/${this.workflowId}/create-run?${searchParams.toString()}`, {
4421
4527
  method: "POST",
4422
4528
  body: {
@@ -4974,7 +5080,7 @@ var AgentBuilder = class extends BaseResource {
4974
5080
  */
4975
5081
  async createRun(params) {
4976
5082
  const searchParams = new URLSearchParams();
4977
- if (!!params?.runId) searchParams.set("runId", params.runId);
5083
+ if (params?.runId) searchParams.set("runId", params.runId);
4978
5084
  const url = `/agent-builder/${this.actionId}/create-run${searchParams.toString() ? `?${searchParams.toString()}` : ""}`;
4979
5085
  return this.request(url, { method: "POST" });
4980
5086
  }