@mastra/server 1.57.0 → 1.58.0-alpha.1

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.
Files changed (109) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/dist/{a2a-uPi5sPwb.js → a2a-CHi9AJ0n.js} +598 -178
  3. package/dist/a2a-CHi9AJ0n.js.map +1 -0
  4. package/dist/{a2a-DKnwIgiw.cjs → a2a-D3ZYeXIx.cjs} +598 -178
  5. package/dist/a2a-D3ZYeXIx.cjs.map +1 -0
  6. package/dist/{api-schema-manifest-7-NC9Dyc.cjs → api-schema-manifest-J5hfk5ZZ.cjs} +2 -2
  7. package/dist/{api-schema-manifest-7-NC9Dyc.cjs.map → api-schema-manifest-J5hfk5ZZ.cjs.map} +1 -1
  8. package/dist/{api-schema-manifest-C5hpdIE_.js → api-schema-manifest-R8CsF6RX.js} +2 -2
  9. package/dist/{api-schema-manifest-C5hpdIE_.js.map → api-schema-manifest-R8CsF6RX.js.map} +1 -1
  10. package/dist/{datasets-Cvix7Rms.cjs → datasets-BMeFvFwT.cjs} +43 -1
  11. package/dist/datasets-BMeFvFwT.cjs.map +1 -0
  12. package/dist/{datasets-dhXnHLAL.js → datasets-BeBH9Seg.js} +38 -2
  13. package/dist/datasets-BeBH9Seg.js.map +1 -0
  14. package/dist/{dist-COHIL6fR.cjs → dist-DgY0llRv.cjs} +116 -28
  15. package/dist/dist-DgY0llRv.cjs.map +1 -0
  16. package/dist/{dist-DGqlUp0b.js → dist-IUXZtNBY.js} +116 -28
  17. package/dist/dist-IUXZtNBY.js.map +1 -0
  18. package/dist/docs/SKILL.md +1 -1
  19. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  20. package/dist/docs/references/docs-agents-a2a.md +36 -2
  21. package/dist/route-builder-CIk5k35M.js.map +1 -1
  22. package/dist/route-builder-CgLVYL1c.cjs.map +1 -1
  23. package/dist/{routes-hSY4wTeS.cjs → routes-BzR8iVj4.cjs} +2 -2
  24. package/dist/{routes-hSY4wTeS.cjs.map → routes-BzR8iVj4.cjs.map} +1 -1
  25. package/dist/{routes-CRoTKPvQ.js → routes-C7iRCN_l.js} +2 -2
  26. package/dist/{routes-CRoTKPvQ.js.map → routes-C7iRCN_l.js.map} +1 -1
  27. package/dist/server/a2a/store.cjs +26 -2
  28. package/dist/server/a2a/store.cjs.map +1 -1
  29. package/dist/server/a2a/store.d.ts +18 -2
  30. package/dist/server/a2a/store.d.ts.map +1 -1
  31. package/dist/server/a2a/store.js +26 -3
  32. package/dist/server/a2a/store.js.map +1 -1
  33. package/dist/server/a2a/task-state.d.ts +4 -0
  34. package/dist/server/a2a/task-state.d.ts.map +1 -0
  35. package/dist/server/a2a/tasks.d.ts +1 -1
  36. package/dist/server/a2a/tasks.d.ts.map +1 -1
  37. package/dist/server/handlers/a2a.cjs +1 -1
  38. package/dist/server/handlers/a2a.d.ts +3 -2
  39. package/dist/server/handlers/a2a.d.ts.map +1 -1
  40. package/dist/server/handlers/a2a.js +1 -1
  41. package/dist/server/handlers/agent-builder.cjs +1 -1
  42. package/dist/server/handlers/agent-builder.js +1 -1
  43. package/dist/server/handlers/agent-versions.cjs +6 -0
  44. package/dist/server/handlers/agent-versions.cjs.map +1 -1
  45. package/dist/server/handlers/agent-versions.d.ts.map +1 -1
  46. package/dist/server/handlers/agent-versions.js +6 -0
  47. package/dist/server/handlers/agent-versions.js.map +1 -1
  48. package/dist/server/handlers/datasets.cjs +26 -11
  49. package/dist/server/handlers/datasets.cjs.map +1 -1
  50. package/dist/server/handlers/datasets.d.ts +136 -0
  51. package/dist/server/handlers/datasets.d.ts.map +1 -1
  52. package/dist/server/handlers/datasets.js +26 -11
  53. package/dist/server/handlers/datasets.js.map +1 -1
  54. package/dist/server/handlers/mcp-client-versions.cjs +1 -1
  55. package/dist/server/handlers/mcp-client-versions.js +1 -1
  56. package/dist/server/handlers/observability.cjs +20 -9
  57. package/dist/server/handlers/observability.cjs.map +1 -1
  58. package/dist/server/handlers/observability.d.ts.map +1 -1
  59. package/dist/server/handlers/observability.js +20 -9
  60. package/dist/server/handlers/observability.js.map +1 -1
  61. package/dist/server/handlers/prompt-block-versions.cjs +1 -1
  62. package/dist/server/handlers/prompt-block-versions.js +1 -1
  63. package/dist/server/handlers/scorer-versions.cjs +1 -1
  64. package/dist/server/handlers/scorer-versions.js +1 -1
  65. package/dist/server/handlers/stored-agents.cjs +16 -1
  66. package/dist/server/handlers/stored-agents.cjs.map +1 -1
  67. package/dist/server/handlers/stored-agents.d.ts.map +1 -1
  68. package/dist/server/handlers/stored-agents.js +16 -1
  69. package/dist/server/handlers/stored-agents.js.map +1 -1
  70. package/dist/server/handlers/stored-mcp-clients.cjs +1 -1
  71. package/dist/server/handlers/stored-mcp-clients.js +1 -1
  72. package/dist/server/handlers/stored-prompt-blocks.cjs +1 -1
  73. package/dist/server/handlers/stored-prompt-blocks.js +1 -1
  74. package/dist/server/handlers/stored-scorers.cjs +1 -1
  75. package/dist/server/handlers/stored-scorers.js +1 -1
  76. package/dist/server/handlers/stored-skill-favorites.cjs +1 -1
  77. package/dist/server/handlers/stored-skill-favorites.js +1 -1
  78. package/dist/server/handlers/stored-skills.cjs +1 -1
  79. package/dist/server/handlers/stored-skills.js +1 -1
  80. package/dist/server/handlers/system.cjs +1 -1
  81. package/dist/server/handlers/system.js +1 -1
  82. package/dist/server/handlers/validate-agent-instructions.cjs +52 -0
  83. package/dist/server/handlers/validate-agent-instructions.cjs.map +1 -0
  84. package/dist/server/handlers/validate-agent-instructions.d.ts +13 -0
  85. package/dist/server/handlers/validate-agent-instructions.d.ts.map +1 -0
  86. package/dist/server/handlers/validate-agent-instructions.js +51 -0
  87. package/dist/server/handlers/validate-agent-instructions.js.map +1 -0
  88. package/dist/server/handlers/workflows.cjs +12 -0
  89. package/dist/server/handlers/workflows.cjs.map +1 -1
  90. package/dist/server/handlers/workflows.d.ts.map +1 -1
  91. package/dist/server/handlers/workflows.js +12 -0
  92. package/dist/server/handlers/workflows.js.map +1 -1
  93. package/dist/server/handlers.cjs +1 -1
  94. package/dist/server/handlers.js +1 -1
  95. package/dist/server/schemas/datasets.d.ts +53 -0
  96. package/dist/server/schemas/datasets.d.ts.map +1 -1
  97. package/dist/server/schemas/index.cjs +3 -2
  98. package/dist/server/schemas/index.js +3 -3
  99. package/dist/server/server-adapter/index.cjs +1 -1
  100. package/dist/server/server-adapter/index.js +1 -1
  101. package/dist/server/server-adapter/routes/datasets.d.ts +136 -0
  102. package/dist/server/server-adapter/routes/datasets.d.ts.map +1 -1
  103. package/package.json +5 -5
  104. package/dist/a2a-DKnwIgiw.cjs.map +0 -1
  105. package/dist/a2a-uPi5sPwb.js.map +0 -1
  106. package/dist/datasets-Cvix7Rms.cjs.map +0 -1
  107. package/dist/datasets-dhXnHLAL.js.map +0 -1
  108. package/dist/dist-COHIL6fR.cjs.map +0 -1
  109. package/dist/dist-DGqlUp0b.js.map +0 -1
@@ -2,6 +2,7 @@ import { i as __toESM, n as __exportAll, t as __commonJSMin } from "./rolldown-r
2
2
  import { n as createRoute } from "./route-builder-CIk5k35M.js";
3
3
  import { r as convertInstructionsToString } from "./utils-BUK03Frl.js";
4
4
  import { getAgentFromSystem } from "./server/handlers/agents.js";
5
+ import { TaskStoreVersionConflictError } from "./server/a2a/store.js";
5
6
  import { a as agentExecutionResponseSchema, i as agentExecutionBodySchema, r as agentCardResponseSchema, t as a2aAgentIdPathParams } from "./a2a-CnU9zaGR.js";
6
7
  import { getPublicOrigin } from "./server/handlers/auth.js";
7
8
  import { z } from "zod/v4";
@@ -115,7 +116,7 @@ function createErrorResponse(id, error) {
115
116
  };
116
117
  }
117
118
  function createSuccessResponse(id, result) {
118
- if (!id) throw MastraA2AError.internalError("Cannot create success response for null ID.");
119
+ if (id === null) throw MastraA2AError.internalError("Cannot create success response for null ID.");
119
120
  return {
120
121
  jsonrpc: "2.0",
121
122
  id,
@@ -303,6 +304,14 @@ var InMemoryPushNotificationStore = class {
303
304
  }
304
305
  };
305
306
  //#endregion
307
+ //#region src/server/a2a/task-state.ts
308
+ function isTerminalTaskState(state) {
309
+ return state === "completed" || state === "failed" || state === "canceled" || state === "rejected";
310
+ }
311
+ function isInterruptedTaskState(state) {
312
+ return state === "input-required" || state === "auth-required";
313
+ }
314
+ //#endregion
306
315
  //#region src/server/a2a/tasks.ts
307
316
  function isTaskStatusUpdate(update) {
308
317
  return "state" in update && !("parts" in update);
@@ -338,54 +347,63 @@ function applyUpdateToTask(current, update) {
338
347
  return newTask;
339
348
  }
340
349
  async function loadOrCreateTask({ agentId, taskId, taskStore, message, contextId, metadata, logger }) {
341
- const data = await taskStore.load({
342
- agentId,
343
- taskId
344
- });
345
- if (!data) {
346
- const initialTask = {
347
- id: taskId,
348
- contextId: contextId || crypto.randomUUID(),
349
- status: {
350
- state: "submitted",
351
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
352
- message: void 0
353
- },
354
- artifacts: [],
355
- history: [message],
356
- metadata,
357
- kind: "task"
358
- };
359
- logger?.info(`[Task ${taskId}] Created new task.`);
360
- await taskStore.save({
350
+ for (let attempt = 0; attempt < 2; attempt++) {
351
+ const snapshot = taskStore.loadWithVersion({
361
352
  agentId,
362
- data: initialTask
353
+ taskId
363
354
  });
364
- return initialTask;
355
+ const data = snapshot?.task;
356
+ if (!data) {
357
+ const initialTask = {
358
+ id: taskId,
359
+ contextId: contextId || crypto.randomUUID(),
360
+ status: {
361
+ state: "submitted",
362
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
363
+ message: void 0
364
+ },
365
+ artifacts: [],
366
+ history: [message],
367
+ metadata,
368
+ kind: "task"
369
+ };
370
+ logger?.info(`[Task ${taskId}] Created new task.`);
371
+ try {
372
+ await taskStore.save({
373
+ agentId,
374
+ data: initialTask,
375
+ expectedVersion: 0
376
+ });
377
+ return initialTask;
378
+ } catch (error) {
379
+ if (error instanceof TaskStoreVersionConflictError) continue;
380
+ throw error;
381
+ }
382
+ }
383
+ logger?.info(`[Task ${taskId}] Loaded existing task.`);
384
+ const { status } = data;
385
+ if (isTerminalTaskState(status.state)) throw MastraA2AError.invalidRequest(`Task ${taskId} is in terminal state ${status.state} and cannot be restarted.`);
386
+ let updatedData = {
387
+ ...data,
388
+ history: [...data.history || [], message]
389
+ };
390
+ if (status.state === "input-required" || status.state === "auth-required") {
391
+ logger?.info(`[Task ${taskId}] Changing state from '${status.state}' to 'working'.`);
392
+ updatedData = applyUpdateToTask(updatedData, { state: "working" });
393
+ } else if (status.state === "working") logger?.warn(`[Task ${taskId}] Received message while already 'working'. Proceeding.`);
394
+ try {
395
+ await taskStore.save({
396
+ agentId,
397
+ data: updatedData,
398
+ expectedVersion: snapshot.version
399
+ });
400
+ return updatedData;
401
+ } catch (error) {
402
+ if (error instanceof TaskStoreVersionConflictError) continue;
403
+ throw error;
404
+ }
365
405
  }
366
- logger?.info(`[Task ${taskId}] Loaded existing task.`);
367
- let updatedData = data;
368
- updatedData.history = [...data.history || [], message];
369
- const { status } = data;
370
- if ([
371
- "completed",
372
- "failed",
373
- "canceled"
374
- ].includes(status.state)) {
375
- logger?.warn(`[Task ${taskId}] Received message for task in final state ${status.state}. Restarting.`);
376
- updatedData = applyUpdateToTask(updatedData, {
377
- state: "submitted",
378
- message: void 0
379
- });
380
- } else if (status.state === "input-required") {
381
- logger?.info(`[Task ${taskId}] Changing state from 'input-required' to 'working'.`);
382
- updatedData = applyUpdateToTask(updatedData, { state: "working" });
383
- } else if (status.state === "working") logger?.warn(`[Task ${taskId}] Received message while already 'working'. Proceeding.`);
384
- await taskStore.save({
385
- agentId,
386
- data: updatedData
387
- });
388
- return updatedData;
406
+ throw MastraA2AError.invalidRequest(`Task ${taskId} was updated concurrently. Retry the request.`);
389
407
  }
390
408
  //#endregion
391
409
  //#region src/server/handlers/a2a.ts
@@ -572,6 +590,176 @@ function createDataArtifactUpdate({ taskId, contextId, data, lastChunk }) {
572
590
  }
573
591
  };
574
592
  }
593
+ /**
594
+ * Task metadata keys that store the resume bookkeeping for a suspended agent
595
+ * run so a follow-up message for an `input-required` task can resume it.
596
+ */
597
+ const SUSPENDED_RUN_ID_METADATA_KEY = "suspendedRunId";
598
+ const SUSPENDED_TOOL_CALL_ID_METADATA_KEY = "suspendedToolCallId";
599
+ const SUSPENDED_REQUIRES_APPROVAL_METADATA_KEY = "suspendedRequiresApproval";
600
+ function toJsonSafe(value) {
601
+ if (value === void 0) return;
602
+ try {
603
+ return JSON.parse(JSON.stringify(value));
604
+ } catch {
605
+ return;
606
+ }
607
+ }
608
+ /**
609
+ * Builds the `input-required` status update for a suspended agent run.
610
+ * The status message carries a human-readable prompt plus a data part with
611
+ * the structured suspend payload and resume schema so A2A clients can
612
+ * render/collect the required input (HITL per the A2A spec).
613
+ */
614
+ function createInputRequiredStatusUpdate({ taskId, contextId, suspendPayload, resumeSchema, logger }) {
615
+ const extractMessage = (value) => {
616
+ if (!value || typeof value !== "object") return;
617
+ const messageValue = value.message;
618
+ return typeof messageValue === "string" && messageValue.length > 0 ? messageValue : void 0;
619
+ };
620
+ const promptText = extractMessage(suspendPayload) ?? extractMessage(suspendPayload?.suspendPayload) ?? "Additional input is required to continue this task.";
621
+ const safeSuspendPayload = toJsonSafe(suspendPayload);
622
+ const safeResumeSchema = toJsonSafe(resumeSchema);
623
+ if (suspendPayload !== void 0 && safeSuspendPayload === void 0) logger?.warn(`[Task ${taskId}] Suspend payload is not JSON-serializable and was omitted from the status message.`);
624
+ if (resumeSchema !== void 0 && safeResumeSchema === void 0) logger?.warn(`[Task ${taskId}] Resume schema is not JSON-serializable and was omitted from the status message.`);
625
+ const data = {
626
+ ...safeSuspendPayload !== void 0 ? { suspendPayload: safeSuspendPayload } : {},
627
+ ...safeResumeSchema !== void 0 ? { resumeSchema: safeResumeSchema } : {}
628
+ };
629
+ return {
630
+ state: "input-required",
631
+ message: {
632
+ messageId: crypto.randomUUID(),
633
+ kind: "message",
634
+ role: "agent",
635
+ taskId,
636
+ contextId,
637
+ parts: [{
638
+ kind: "text",
639
+ text: promptText
640
+ }, ...Object.keys(data).length > 0 ? [{
641
+ kind: "data",
642
+ data
643
+ }] : []]
644
+ }
645
+ };
646
+ }
647
+ /**
648
+ * Extracts resume data from a follow-up message for an `input-required` task.
649
+ * Prefers a structured data part; falls back to parsing the text as JSON
650
+ * (the Mastra A2A client serializes structured resume data as JSON text),
651
+ * and finally to the raw text.
652
+ */
653
+ function extractResumeData(message) {
654
+ const dataPart = message.parts.find((part) => part.kind === "data");
655
+ if (dataPart && "data" in dataPart) return dataPart.data;
656
+ const text = message.parts.filter((part) => part.kind === "text").map((part) => part.text).join("\n").trim();
657
+ if (!text) return;
658
+ try {
659
+ return JSON.parse(text);
660
+ } catch {
661
+ return text;
662
+ }
663
+ }
664
+ function getSuspendedRunId(task) {
665
+ const value = task?.metadata?.[SUSPENDED_RUN_ID_METADATA_KEY];
666
+ return typeof value === "string" ? value : void 0;
667
+ }
668
+ /**
669
+ * Approval suspensions (`requireApproval`) carry `{ toolCallId, toolName, args, resumeSchema }`
670
+ * without a nested `suspendPayload`, while `suspend()` suspensions include one
671
+ * (see `ToolCallApprovalPayload` / `ToolCallSuspendedPayload` in @mastra/core).
672
+ */
673
+ function isApprovalSuspension(suspendPayload) {
674
+ if (!suspendPayload || typeof suspendPayload !== "object") return false;
675
+ const payload = suspendPayload;
676
+ return typeof payload.toolCallId === "string" && payload.suspendPayload === void 0;
677
+ }
678
+ const APPROVAL_AFFIRMATIVE_PATTERN = /^(y|yes|approve|approved|ok|okay|confirm|confirmed|true)[.!]?$/i;
679
+ const APPROVAL_NEGATIVE_PATTERN = /^(n|no|decline|declined|deny|denied|reject|rejected|false)[.!]?$/i;
680
+ /**
681
+ * Approval resumes are driven by `resumeData.approved` in the agentic loop, so
682
+ * plain-text replies from A2A clients ("yes", "no") are coerced to the
683
+ * `{ approved }` shape. Unrecognized values pass through unchanged.
684
+ */
685
+ function normalizeResumeData(resumeData, requiresApproval) {
686
+ if (!requiresApproval || typeof resumeData !== "string") return resumeData;
687
+ const text = resumeData.trim();
688
+ if (APPROVAL_AFFIRMATIVE_PATTERN.test(text)) return { approved: true };
689
+ if (APPROVAL_NEGATIVE_PATTERN.test(text)) return { approved: false };
690
+ return resumeData;
691
+ }
692
+ /**
693
+ * Marks a task `input-required` for a suspended agent run and records the
694
+ * resume bookkeeping (runId, toolCallId, approval flag) in task metadata.
695
+ * Shared by the send and stream paths so both report identical suspensions.
696
+ */
697
+ function applySuspensionToTask({ task, suspendPayload, resumeSchema, runId, logger }) {
698
+ const nextTask = applyUpdateToTask(task, createInputRequiredStatusUpdate({
699
+ taskId: task.id,
700
+ contextId: task.contextId,
701
+ suspendPayload,
702
+ resumeSchema,
703
+ logger
704
+ }));
705
+ const payload = suspendPayload;
706
+ nextTask.metadata = {
707
+ ...clearSuspensionMetadata(nextTask.metadata),
708
+ [SUSPENDED_RUN_ID_METADATA_KEY]: runId,
709
+ ...typeof payload?.toolCallId === "string" ? { [SUSPENDED_TOOL_CALL_ID_METADATA_KEY]: payload.toolCallId } : {},
710
+ ...isApprovalSuspension(suspendPayload) ? { [SUSPENDED_REQUIRES_APPROVAL_METADATA_KEY]: true } : {}
711
+ };
712
+ return nextTask;
713
+ }
714
+ /** Removes the suspension bookkeeping from task metadata once the run completes. */
715
+ function clearSuspensionMetadata(metadata) {
716
+ const { [SUSPENDED_RUN_ID_METADATA_KEY]: _runId, [SUSPENDED_TOOL_CALL_ID_METADATA_KEY]: _toolCallId, [SUSPENDED_REQUIRES_APPROVAL_METADATA_KEY]: _requiresApproval, ...rest } = metadata ?? {};
717
+ return rest;
718
+ }
719
+ /**
720
+ * Claims an interrupted task for resume by transitioning it to `working`.
721
+ * `loadWithVersion` and the body of `InMemoryTaskStore.save` both execute
722
+ * synchronously, so two concurrent follow-up messages cannot both claim (and
723
+ * double-resume) the same suspended run.
724
+ */
725
+ async function claimInterruptedTaskResume({ taskStore, agentId, taskId }) {
726
+ const snapshot = taskStore.loadWithVersion({
727
+ agentId,
728
+ taskId
729
+ });
730
+ if (snapshot?.task.status.state !== "input-required" && snapshot?.task.status.state !== "auth-required") return;
731
+ const task = snapshot.task;
732
+ const toolCallId = task.metadata?.[SUSPENDED_TOOL_CALL_ID_METADATA_KEY];
733
+ const claim = {
734
+ runId: getSuspendedRunId(task) ?? taskId,
735
+ ...typeof toolCallId === "string" ? { toolCallId } : {},
736
+ requiresApproval: task.metadata?.[SUSPENDED_REQUIRES_APPROVAL_METADATA_KEY] === true
737
+ };
738
+ try {
739
+ await taskStore.save({
740
+ agentId,
741
+ data: applyUpdateToTask(task, { state: "working" }),
742
+ expectedVersion: snapshot.version
743
+ });
744
+ } catch (error) {
745
+ if (error instanceof TaskStoreVersionConflictError) return;
746
+ throw error;
747
+ }
748
+ return claim;
749
+ }
750
+ async function waitForClaimedResume({ taskStore, agentId, taskId }) {
751
+ let snapshot = taskStore.loadWithVersion({
752
+ agentId,
753
+ taskId
754
+ });
755
+ if (!snapshot) throw MastraA2AError.taskNotFound(taskId);
756
+ while (snapshot.task.status.state === "working") snapshot = await taskStore.waitForNextUpdate({
757
+ agentId,
758
+ taskId,
759
+ afterVersion: snapshot.version
760
+ });
761
+ return snapshot.task;
762
+ }
575
763
  function resolvePushNotificationPair({ pushNotificationStore, pushNotificationSender }) {
576
764
  if (pushNotificationSender) return {
577
765
  pushNotificationStore: pushNotificationSender.getStore(),
@@ -600,23 +788,45 @@ function shouldSendPushNotification(previousTask, nextTask) {
600
788
  "completed",
601
789
  "failed",
602
790
  "canceled",
603
- "input-required"
791
+ "rejected",
792
+ "input-required",
793
+ "auth-required"
604
794
  ].includes(nextTask.status.state)) return false;
605
795
  return previousTask?.status.state !== nextTask.status.state;
606
796
  }
607
- async function saveTaskAndMaybeSendPushNotification({ taskStore, pushNotificationSender, previousTask, nextTask, agentId, logger }) {
608
- await taskStore.save({
797
+ function createLinkedAbortController(abortSignal) {
798
+ const controller = new AbortController();
799
+ if (!abortSignal) return {
800
+ controller,
801
+ cleanup: () => {}
802
+ };
803
+ const abortFromSignal = () => {
804
+ if (!controller.signal.aborted) controller.abort(abortSignal.reason);
805
+ };
806
+ if (abortSignal.aborted) abortFromSignal();
807
+ else abortSignal.addEventListener("abort", abortFromSignal, { once: true });
808
+ return {
809
+ controller,
810
+ cleanup: () => abortSignal.removeEventListener("abort", abortFromSignal)
811
+ };
812
+ }
813
+ async function saveTaskAndMaybeSendPushNotification({ taskStore, pushNotificationSender, previousTask, nextTask, agentId, expectedVersion, logger }) {
814
+ const storedTask = await taskStore.save({
609
815
  agentId,
610
- data: nextTask
816
+ data: nextTask,
817
+ expectedVersion,
818
+ skipIfCanceled: true
611
819
  });
612
- if (!shouldSendPushNotification(previousTask, nextTask)) return;
820
+ if (storedTask.status.state === "canceled" && nextTask.status.state !== "canceled") return storedTask;
821
+ if (!shouldSendPushNotification(previousTask, storedTask)) return storedTask;
613
822
  pushNotificationSender.sendNotifications({
614
823
  agentId,
615
- task: nextTask,
824
+ task: storedTask,
616
825
  logger
617
826
  }).catch((error) => {
618
827
  logger?.error("Failed to schedule A2A push notification", error);
619
828
  });
829
+ return storedTask;
620
830
  }
621
831
  function extractFullStreamTextDelta(value) {
622
832
  if (typeof value !== "object" || value === null || !("type" in value)) return null;
@@ -639,12 +849,10 @@ function extractFinalStructuredObject(value) {
639
849
  const objectValue = chunk.payload?.object ?? chunk.object;
640
850
  return objectValue && typeof objectValue === "object" ? objectValue : void 0;
641
851
  }
642
- function isTerminalTaskState(state) {
643
- return [
644
- "completed",
645
- "failed",
646
- "canceled"
647
- ].includes(state);
852
+ function isSuspensionChunk(value) {
853
+ if (typeof value !== "object" || value === null || !("type" in value)) return false;
854
+ const type = value.type;
855
+ return type === "tool-call-suspended" || type === "tool-call-approval";
648
856
  }
649
857
  function artifactIdentity(artifact) {
650
858
  return artifact.artifactId || artifact.name;
@@ -689,11 +897,15 @@ function getTaskArtifactUpdates({ previous, next }) {
689
897
  artifact: structuredClone(artifact)
690
898
  }));
691
899
  }
692
- async function executeMessageSend({ requestId, message, metadata, currentData, taskStore, pushNotificationSender, agent, agentId, logger, requestContext }) {
900
+ async function executeMessageSend({ requestId, message, metadata, currentData, taskStore, pushNotificationSender, agent, agentId, logger, requestContext, resume }) {
693
901
  const { contextId } = message;
694
902
  try {
695
903
  const resourceId = metadata?.resourceId ?? message.metadata?.resourceId ?? agentId;
696
- const result = await agent.generate([convertToCoreMessage(message)], {
904
+ const result = resume ? await agent.resumeGenerate(normalizeResumeData(extractResumeData(message), resume.requiresApproval), {
905
+ runId: resume.runId,
906
+ ...resume.toolCallId ? { toolCallId: resume.toolCallId } : {},
907
+ requestContext
908
+ }) : await agent.generate([convertToCoreMessage(message)], {
697
909
  runId: currentData.id,
698
910
  requestContext,
699
911
  ...contextId ? {
@@ -714,13 +926,32 @@ async function executeMessageSend({ requestId, message, metadata, currentData, t
714
926
  data: result.object
715
927
  });
716
928
  if (artifactUpdate) currentData = applyUpdateToTask(currentData, artifactUpdate);
929
+ if (result.finishReason === "suspended") {
930
+ const previousTask = currentData;
931
+ currentData = applySuspensionToTask({
932
+ task: currentData,
933
+ suspendPayload: result.suspendPayload,
934
+ resumeSchema: result.resumeSchema,
935
+ runId: result.runId ?? currentData.id,
936
+ logger
937
+ });
938
+ await saveTaskAndMaybeSendPushNotification({
939
+ taskStore,
940
+ pushNotificationSender,
941
+ previousTask,
942
+ nextTask: currentData,
943
+ agentId,
944
+ logger
945
+ });
946
+ return createSuccessResponse(requestId, currentData);
947
+ }
717
948
  const previousTask = currentData;
718
949
  currentData = applyUpdateToTask(currentData, {
719
950
  state: "completed",
720
951
  message: void 0
721
952
  });
722
953
  currentData.metadata = {
723
- ...currentData.metadata,
954
+ ...clearSuspensionMetadata(currentData.metadata),
724
955
  execution: {
725
956
  toolCalls: result.toolCalls,
726
957
  toolResults: result.toolResults,
@@ -778,11 +1009,28 @@ async function handleMessageSend({ requestId, params, taskStore, pushNotificatio
778
1009
  const { message, metadata } = params;
779
1010
  const { contextId } = message;
780
1011
  const taskId = message.taskId || crypto.randomUUID();
781
- const existingTask = await taskStore.load({
1012
+ const existingTask = taskStore.loadWithVersion({
782
1013
  agentId,
783
1014
  taskId
784
- });
1015
+ })?.task;
1016
+ if (message.taskId && !existingTask) throw MastraA2AError.taskNotFound(message.taskId);
785
1017
  if (params.configuration?.blocking === false && existingTask?.status.state === "working") return createSuccessResponse(requestId, existingTask);
1018
+ if (existingTask?.status.state === "working" && getSuspendedRunId(existingTask)) return createSuccessResponse(requestId, await waitForClaimedResume({
1019
+ taskStore,
1020
+ agentId,
1021
+ taskId
1022
+ }));
1023
+ const wasInterrupted = isInterruptedTaskState(existingTask?.status.state);
1024
+ const resume = await claimInterruptedTaskResume({
1025
+ taskStore,
1026
+ agentId,
1027
+ taskId
1028
+ });
1029
+ if (wasInterrupted && !resume) return createSuccessResponse(requestId, await waitForClaimedResume({
1030
+ taskStore,
1031
+ agentId,
1032
+ taskId
1033
+ }));
786
1034
  const { pushNotificationStore: resolvedPushNotificationStore, pushNotificationSender: resolvedPushNotificationSender } = resolvePushNotificationPair({
787
1035
  pushNotificationStore,
788
1036
  pushNotificationSender
@@ -828,7 +1076,8 @@ async function handleMessageSend({ requestId, params, taskStore, pushNotificatio
828
1076
  agent,
829
1077
  agentId,
830
1078
  logger,
831
- requestContext
1079
+ requestContext,
1080
+ resume
832
1081
  });
833
1082
  if (params.configuration?.blocking === false) {
834
1083
  execution.catch((error) => {
@@ -905,11 +1154,20 @@ async function handleDeleteTaskPushNotificationConfig({ requestId, taskStore, pu
905
1154
  })) throw MastraA2AError.invalidParams(`Push notification config not found: ${params.pushNotificationConfigId}`);
906
1155
  return createSuccessResponse(requestId, null);
907
1156
  }
908
- async function* handleMessageStream({ requestId, params, taskStore, pushNotificationStore, pushNotificationSender, agent, agentId, logger, requestContext }) {
1157
+ async function* handleMessageStream({ requestId, params, taskStore, pushNotificationStore, pushNotificationSender, agent, agentId, logger, requestContext, abortSignal }) {
909
1158
  validateMessageSendParams(params);
910
1159
  const { message, metadata } = params;
911
1160
  const { contextId } = message;
912
1161
  const taskId = message.taskId || crypto.randomUUID();
1162
+ if (message.taskId && !taskStore.loadWithVersion({
1163
+ agentId,
1164
+ taskId
1165
+ })) throw MastraA2AError.taskNotFound(message.taskId);
1166
+ const resume = await claimInterruptedTaskResume({
1167
+ taskStore,
1168
+ agentId,
1169
+ taskId
1170
+ });
913
1171
  const { pushNotificationStore: resolvedPushNotificationStore, pushNotificationSender: resolvedPushNotificationSender } = resolvePushNotificationPair({
914
1172
  pushNotificationStore,
915
1173
  pushNotificationSender
@@ -945,12 +1203,24 @@ async function* handleMessageStream({ requestId, params, taskStore, pushNotifica
945
1203
  agentId,
946
1204
  logger
947
1205
  });
948
- yield createSuccessResponse(requestId, currentData);
1206
+ const { controller: taskAbortController, cleanup: cleanupLinkedAbortController } = createLinkedAbortController(abortSignal);
1207
+ const unregisterTaskAbortController = taskStore.registerAbortController({
1208
+ agentId,
1209
+ taskId,
1210
+ controller: taskAbortController
1211
+ });
949
1212
  try {
1213
+ yield createSuccessResponse(requestId, currentData);
950
1214
  const resourceId = metadata?.resourceId ?? message.metadata?.resourceId ?? agentId;
951
- const result = await agent.stream([convertToCoreMessage(message)], {
1215
+ const result = resume ? await agent.resumeStream(normalizeResumeData(extractResumeData(message), resume.requiresApproval), {
1216
+ runId: resume.runId,
1217
+ ...resume.toolCallId ? { toolCallId: resume.toolCallId } : {},
1218
+ requestContext,
1219
+ abortSignal: taskAbortController.signal
1220
+ }) : await agent.stream([convertToCoreMessage(message)], {
952
1221
  runId: taskId,
953
1222
  requestContext,
1223
+ abortSignal: taskAbortController.signal,
954
1224
  ...contextId ? {
955
1225
  threadId: contextId,
956
1226
  resourceId
@@ -959,7 +1229,22 @@ async function* handleMessageStream({ requestId, params, taskStore, pushNotifica
959
1229
  let sawTextArtifact = false;
960
1230
  let pendingTextChunk;
961
1231
  let structuredData;
1232
+ let suspended = false;
1233
+ let streamCanceled = false;
962
1234
  for await (const chunk of result.fullStream) {
1235
+ if (taskAbortController.signal.aborted) {
1236
+ const latestTask = await taskStore.load({
1237
+ agentId,
1238
+ taskId: currentData.id
1239
+ });
1240
+ if (latestTask) currentData = latestTask;
1241
+ streamCanceled = true;
1242
+ break;
1243
+ }
1244
+ if (isSuspensionChunk(chunk)) {
1245
+ suspended = true;
1246
+ continue;
1247
+ }
963
1248
  const textDelta = extractFullStreamTextDelta(chunk);
964
1249
  if (textDelta !== null) {
965
1250
  if (!pendingTextChunk) {
@@ -974,13 +1259,17 @@ async function* handleMessageStream({ requestId, params, taskStore, pushNotifica
974
1259
  lastChunk: false
975
1260
  });
976
1261
  currentData = applyUpdateToTask(currentData, textUpdate);
977
- await saveTaskAndMaybeSendPushNotification({
1262
+ currentData = await saveTaskAndMaybeSendPushNotification({
978
1263
  taskStore,
979
1264
  pushNotificationSender: resolvedPushNotificationSender,
980
1265
  nextTask: currentData,
981
1266
  agentId,
982
1267
  logger
983
1268
  });
1269
+ if (currentData.status.state === "canceled") {
1270
+ streamCanceled = true;
1271
+ break;
1272
+ }
984
1273
  yield createSuccessResponse(requestId, textUpdate);
985
1274
  sawTextArtifact = true;
986
1275
  pendingTextChunk = textDelta;
@@ -989,98 +1278,217 @@ async function* handleMessageStream({ requestId, params, taskStore, pushNotifica
989
1278
  const finalStructuredObject = extractFinalStructuredObject(chunk);
990
1279
  if (finalStructuredObject) structuredData = finalStructuredObject;
991
1280
  }
992
- structuredData ??= await result.object;
993
- if (!pendingTextChunk && !sawTextArtifact) {
994
- const finalText = await result.text;
995
- if (finalText) pendingTextChunk = finalText;
1281
+ if (!streamCanceled && taskAbortController.signal.aborted) {
1282
+ const latestTask = await taskStore.load({
1283
+ agentId,
1284
+ taskId: currentData.id
1285
+ });
1286
+ if (latestTask) currentData = latestTask;
1287
+ streamCanceled = true;
996
1288
  }
997
- if (pendingTextChunk) {
998
- const textUpdate = createTextChunkArtifactUpdate({
999
- taskId: currentData.id,
1000
- contextId: currentData.contextId,
1001
- text: pendingTextChunk,
1002
- append: sawTextArtifact,
1003
- lastChunk: !structuredData
1289
+ if (streamCanceled && abortSignal?.aborted && currentData.status.state !== "canceled") {
1290
+ const previousTask = currentData;
1291
+ currentData = applyUpdateToTask(currentData, {
1292
+ state: "canceled",
1293
+ message: {
1294
+ messageId: crypto.randomUUID(),
1295
+ role: "agent",
1296
+ parts: [{
1297
+ kind: "text",
1298
+ text: "Task canceled because the request was aborted."
1299
+ }],
1300
+ kind: "message"
1301
+ }
1004
1302
  });
1005
- currentData = applyUpdateToTask(currentData, textUpdate);
1006
- await saveTaskAndMaybeSendPushNotification({
1303
+ currentData = await saveTaskAndMaybeSendPushNotification({
1007
1304
  taskStore,
1008
1305
  pushNotificationSender: resolvedPushNotificationSender,
1306
+ previousTask,
1009
1307
  nextTask: currentData,
1010
1308
  agentId,
1011
1309
  logger
1012
1310
  });
1013
- yield createSuccessResponse(requestId, textUpdate);
1014
- sawTextArtifact = true;
1015
- pendingTextChunk = void 0;
1016
1311
  }
1017
- if (structuredData) {
1018
- const dataUpdate = createDataArtifactUpdate({
1019
- taskId: currentData.id,
1020
- contextId: currentData.contextId,
1021
- data: structuredData,
1022
- lastChunk: true
1312
+ if (!streamCanceled && suspended) {
1313
+ if (pendingTextChunk) {
1314
+ const textUpdate = createTextChunkArtifactUpdate({
1315
+ taskId: currentData.id,
1316
+ contextId: currentData.contextId,
1317
+ text: pendingTextChunk,
1318
+ append: sawTextArtifact,
1319
+ lastChunk: true
1320
+ });
1321
+ currentData = applyUpdateToTask(currentData, textUpdate);
1322
+ currentData = await saveTaskAndMaybeSendPushNotification({
1323
+ taskStore,
1324
+ pushNotificationSender: resolvedPushNotificationSender,
1325
+ nextTask: currentData,
1326
+ agentId,
1327
+ logger
1328
+ });
1329
+ if (currentData.status.state === "canceled") streamCanceled = true;
1330
+ else yield createSuccessResponse(requestId, textUpdate);
1331
+ }
1332
+ const suspensionTask = currentData;
1333
+ currentData = applySuspensionToTask({
1334
+ task: currentData,
1335
+ suspendPayload: await result.suspendPayload,
1336
+ resumeSchema: await result.resumeSchema,
1337
+ runId: result.runId ?? currentData.id,
1338
+ logger
1023
1339
  });
1024
- currentData = applyUpdateToTask(currentData, dataUpdate);
1025
- await saveTaskAndMaybeSendPushNotification({
1340
+ currentData = await saveTaskAndMaybeSendPushNotification({
1026
1341
  taskStore,
1027
1342
  pushNotificationSender: resolvedPushNotificationSender,
1343
+ previousTask: suspensionTask,
1028
1344
  nextTask: currentData,
1029
1345
  agentId,
1030
1346
  logger
1031
1347
  });
1032
- yield createSuccessResponse(requestId, dataUpdate);
1348
+ if (currentData.status.state === "canceled") streamCanceled = true;
1033
1349
  }
1034
- const previousTask = currentData;
1035
- const completedTask = applyUpdateToTask(currentData, {
1036
- state: "completed",
1037
- message: void 0
1038
- });
1039
- completedTask.metadata = {
1040
- ...completedTask.metadata,
1041
- execution: {
1042
- toolCalls: await result.toolCalls,
1043
- toolResults: await result.toolResults,
1044
- usage: await result.usage,
1045
- finishReason: await result.finishReason
1350
+ if (!streamCanceled && !suspended) {
1351
+ structuredData ??= await result.object;
1352
+ if (!pendingTextChunk && !sawTextArtifact) {
1353
+ const finalText = await result.text;
1354
+ if (finalText) pendingTextChunk = finalText;
1046
1355
  }
1047
- };
1048
- currentData = completedTask;
1049
- await saveTaskAndMaybeSendPushNotification({
1050
- taskStore,
1051
- pushNotificationSender: resolvedPushNotificationSender,
1052
- previousTask,
1053
- nextTask: currentData,
1356
+ if (pendingTextChunk) {
1357
+ const textUpdate = createTextChunkArtifactUpdate({
1358
+ taskId: currentData.id,
1359
+ contextId: currentData.contextId,
1360
+ text: pendingTextChunk,
1361
+ append: sawTextArtifact,
1362
+ lastChunk: !structuredData
1363
+ });
1364
+ currentData = applyUpdateToTask(currentData, textUpdate);
1365
+ currentData = await saveTaskAndMaybeSendPushNotification({
1366
+ taskStore,
1367
+ pushNotificationSender: resolvedPushNotificationSender,
1368
+ nextTask: currentData,
1369
+ agentId,
1370
+ logger
1371
+ });
1372
+ if (currentData.status.state === "canceled") streamCanceled = true;
1373
+ else yield createSuccessResponse(requestId, textUpdate);
1374
+ sawTextArtifact = true;
1375
+ pendingTextChunk = void 0;
1376
+ }
1377
+ if (!streamCanceled && structuredData) {
1378
+ const dataUpdate = createDataArtifactUpdate({
1379
+ taskId: currentData.id,
1380
+ contextId: currentData.contextId,
1381
+ data: structuredData,
1382
+ lastChunk: true
1383
+ });
1384
+ currentData = applyUpdateToTask(currentData, dataUpdate);
1385
+ currentData = await saveTaskAndMaybeSendPushNotification({
1386
+ taskStore,
1387
+ pushNotificationSender: resolvedPushNotificationSender,
1388
+ nextTask: currentData,
1389
+ agentId,
1390
+ logger
1391
+ });
1392
+ if (currentData.status.state === "canceled") streamCanceled = true;
1393
+ else yield createSuccessResponse(requestId, dataUpdate);
1394
+ }
1395
+ if (!streamCanceled) {
1396
+ const previousTask = currentData;
1397
+ const completedTask = applyUpdateToTask(currentData, {
1398
+ state: "completed",
1399
+ message: void 0
1400
+ });
1401
+ completedTask.metadata = {
1402
+ ...clearSuspensionMetadata(completedTask.metadata),
1403
+ execution: {
1404
+ toolCalls: await result.toolCalls,
1405
+ toolResults: await result.toolResults,
1406
+ usage: await result.usage,
1407
+ finishReason: await result.finishReason
1408
+ }
1409
+ };
1410
+ currentData = await saveTaskAndMaybeSendPushNotification({
1411
+ taskStore,
1412
+ pushNotificationSender: resolvedPushNotificationSender,
1413
+ previousTask,
1414
+ nextTask: completedTask,
1415
+ agentId,
1416
+ logger
1417
+ });
1418
+ }
1419
+ }
1420
+ } catch (handlerError) {
1421
+ const latestTask = await taskStore.load({
1054
1422
  agentId,
1055
- logger
1423
+ taskId: currentData.id
1056
1424
  });
1057
- } catch (handlerError) {
1058
- const previousTask = currentData;
1059
- currentData = applyUpdateToTask(currentData, {
1060
- state: "failed",
1061
- message: {
1062
- messageId: crypto.randomUUID(),
1063
- role: "agent",
1064
- parts: [{
1065
- kind: "text",
1066
- text: `Handler failed: ${handlerError instanceof Error ? handlerError.message : String(handlerError)}`
1067
- }],
1068
- kind: "message"
1425
+ if (latestTask?.status.state === "canceled") currentData = latestTask;
1426
+ else if (taskAbortController.signal.aborted) {
1427
+ currentData = latestTask ?? currentData;
1428
+ if (abortSignal?.aborted) {
1429
+ const previousTask = currentData;
1430
+ currentData = applyUpdateToTask(currentData, {
1431
+ state: "canceled",
1432
+ message: {
1433
+ messageId: crypto.randomUUID(),
1434
+ role: "agent",
1435
+ parts: [{
1436
+ kind: "text",
1437
+ text: "Task canceled because the request was aborted."
1438
+ }],
1439
+ kind: "message"
1440
+ }
1441
+ });
1442
+ try {
1443
+ currentData = await saveTaskAndMaybeSendPushNotification({
1444
+ taskStore,
1445
+ pushNotificationSender: resolvedPushNotificationSender,
1446
+ previousTask,
1447
+ nextTask: currentData,
1448
+ agentId,
1449
+ logger
1450
+ });
1451
+ } catch (saveError) {
1452
+ logger?.error(`Failed to save task ${currentData.id} after request abort:`, saveError?.message);
1453
+ }
1069
1454
  }
1070
- });
1071
- try {
1072
- await saveTaskAndMaybeSendPushNotification({
1073
- taskStore,
1074
- pushNotificationSender: resolvedPushNotificationSender,
1075
- previousTask,
1076
- nextTask: currentData,
1077
- agentId,
1078
- logger
1455
+ } else {
1456
+ currentData = latestTask ?? currentData;
1457
+ const previousTask = currentData;
1458
+ currentData = applyUpdateToTask(currentData, {
1459
+ state: "failed",
1460
+ message: {
1461
+ messageId: crypto.randomUUID(),
1462
+ role: "agent",
1463
+ parts: [{
1464
+ kind: "text",
1465
+ text: `Handler failed: ${handlerError instanceof Error ? handlerError.message : String(handlerError)}`
1466
+ }],
1467
+ kind: "message"
1468
+ }
1079
1469
  });
1080
- } catch (saveError) {
1081
- logger?.error(`Failed to save task ${currentData.id} after handler error:`, saveError?.message);
1470
+ try {
1471
+ currentData = await saveTaskAndMaybeSendPushNotification({
1472
+ taskStore,
1473
+ pushNotificationSender: resolvedPushNotificationSender,
1474
+ previousTask,
1475
+ nextTask: currentData,
1476
+ agentId,
1477
+ logger
1478
+ });
1479
+ } catch (saveError) {
1480
+ logger?.error(`Failed to save task ${currentData.id} after handler error:`, saveError?.message);
1481
+ }
1082
1482
  }
1483
+ } finally {
1484
+ unregisterTaskAbortController();
1485
+ cleanupLinkedAbortController();
1083
1486
  }
1487
+ const latestTask = await taskStore.load({
1488
+ agentId,
1489
+ taskId: currentData.id
1490
+ });
1491
+ if (latestTask?.status.state === "canceled") currentData = latestTask;
1084
1492
  yield createSuccessResponse(requestId, {
1085
1493
  kind: "status-update",
1086
1494
  taskId: currentData.id,
@@ -1096,7 +1504,7 @@ async function* handleTaskResubscribe({ requestId, taskStore, agentId, taskId, a
1096
1504
  });
1097
1505
  if (!snapshot) throw MastraA2AError.taskNotFound(taskId);
1098
1506
  yield createSuccessResponse(requestId, snapshot.task);
1099
- if (isTerminalTaskState(snapshot.task.status.state)) return;
1507
+ if (isTerminalTaskState(snapshot.task.status.state) || isInterruptedTaskState(snapshot.task.status.state)) return;
1100
1508
  while (true) {
1101
1509
  const { task, version } = snapshot;
1102
1510
  const nextUpdate = await taskStore.waitForNextUpdate({
@@ -1109,14 +1517,16 @@ async function* handleTaskResubscribe({ requestId, taskStore, agentId, taskId, a
1109
1517
  previous: task,
1110
1518
  next: nextUpdate.task
1111
1519
  })) yield createSuccessResponse(requestId, artifactUpdate);
1520
+ const nextState = nextUpdate.task.status.state;
1521
+ const streamEnded = isTerminalTaskState(nextState) || isInterruptedTaskState(nextState);
1112
1522
  if (didTaskStatusChange(task, nextUpdate.task)) yield createSuccessResponse(requestId, {
1113
1523
  kind: "status-update",
1114
1524
  taskId: nextUpdate.task.id,
1115
1525
  contextId: nextUpdate.task.contextId,
1116
1526
  status: nextUpdate.task.status,
1117
- final: isTerminalTaskState(nextUpdate.task.status.state)
1527
+ final: streamEnded
1118
1528
  });
1119
- if (isTerminalTaskState(nextUpdate.task.status.state)) return;
1529
+ if (streamEnded) return;
1120
1530
  snapshot = nextUpdate;
1121
1531
  }
1122
1532
  }
@@ -1154,44 +1564,53 @@ function createA2ASSEResponse(payload) {
1154
1564
  } });
1155
1565
  }
1156
1566
  async function handleTaskCancel({ requestId, taskStore, pushNotificationSender, agentId, taskId, logger }) {
1157
- let data = await taskStore.load({
1158
- agentId,
1159
- taskId
1160
- });
1161
- if (!data) throw MastraA2AError.taskNotFound(taskId);
1162
- if ([
1163
- "completed",
1164
- "failed",
1165
- "canceled"
1166
- ].includes(data.status.state)) {
1167
- logger?.info(`Task ${taskId} already in final state ${data.status.state}, cannot cancel.`);
1168
- return createSuccessResponse(requestId, data);
1169
- }
1170
- taskStore.activeCancellations.add(taskId);
1171
- const cancelUpdate = {
1172
- state: "canceled",
1173
- message: {
1174
- role: "agent",
1175
- parts: [{
1176
- kind: "text",
1177
- text: "Task cancelled by request."
1178
- }],
1179
- kind: "message",
1180
- messageId: crypto.randomUUID()
1567
+ for (let attempt = 0; attempt < 2; attempt++) {
1568
+ const snapshot = taskStore.loadWithVersion({
1569
+ agentId,
1570
+ taskId
1571
+ });
1572
+ const data = snapshot?.task;
1573
+ if (!data) throw MastraA2AError.taskNotFound(taskId);
1574
+ if (isTerminalTaskState(data.status.state)) {
1575
+ logger?.info(`Task ${taskId} already in final state ${data.status.state}, cannot cancel.`);
1576
+ throw MastraA2AError.taskNotCancelable(taskId);
1181
1577
  }
1182
- };
1183
- const previousTask = data;
1184
- data = applyUpdateToTask(data, cancelUpdate);
1185
- await saveTaskAndMaybeSendPushNotification({
1186
- taskStore,
1187
- pushNotificationSender: resolvePushNotificationPair({ pushNotificationSender }).pushNotificationSender,
1188
- previousTask,
1189
- nextTask: data,
1190
- agentId,
1191
- logger
1192
- });
1193
- taskStore.activeCancellations.delete(taskId);
1194
- return createSuccessResponse(requestId, data);
1578
+ taskStore.activeCancellations.add(taskId);
1579
+ taskStore.abortTask({
1580
+ agentId,
1581
+ taskId,
1582
+ reason: new DOMException("Task cancelled by request.", "AbortError")
1583
+ });
1584
+ const canceledTask = applyUpdateToTask(data, {
1585
+ state: "canceled",
1586
+ message: {
1587
+ role: "agent",
1588
+ parts: [{
1589
+ kind: "text",
1590
+ text: "Task cancelled by request."
1591
+ }],
1592
+ kind: "message",
1593
+ messageId: crypto.randomUUID()
1594
+ }
1595
+ });
1596
+ try {
1597
+ await saveTaskAndMaybeSendPushNotification({
1598
+ taskStore,
1599
+ pushNotificationSender: resolvePushNotificationPair({ pushNotificationSender }).pushNotificationSender,
1600
+ previousTask: data,
1601
+ nextTask: canceledTask,
1602
+ agentId,
1603
+ expectedVersion: snapshot.version,
1604
+ logger
1605
+ });
1606
+ return createSuccessResponse(requestId, canceledTask);
1607
+ } catch (error) {
1608
+ if (!(error instanceof TaskStoreVersionConflictError)) throw error;
1609
+ } finally {
1610
+ taskStore.activeCancellations.delete(taskId);
1611
+ }
1612
+ }
1613
+ throw MastraA2AError.invalidRequest(`Task ${taskId} was updated concurrently. Retry the request.`);
1195
1614
  }
1196
1615
  async function getAgentExecutionHandler({ requestId, mastra, agentId, requestContext, method, params, taskStore, pushNotificationStore, pushNotificationSender, logger, abortSignal }) {
1197
1616
  const agent = await getAgentFromSystem({
@@ -1226,7 +1645,8 @@ async function getAgentExecutionHandler({ requestId, mastra, agentId, requestCon
1226
1645
  agent,
1227
1646
  agentId,
1228
1647
  logger,
1229
- requestContext
1648
+ requestContext,
1649
+ abortSignal
1230
1650
  });
1231
1651
  case "tasks/get": return await handleTaskGet({
1232
1652
  requestId,
@@ -1340,4 +1760,4 @@ const AGENT_EXECUTION_ROUTE = createRoute({
1340
1760
  //#endregion
1341
1761
  export { getAgentExecutionHandler as a, handleListTaskPushNotificationConfig as c, handleSetTaskPushNotificationConfig as d, handleTaskCancel as f, getAgentCardByIdHandler as i, handleMessageSend as l, handleTaskResubscribe as m, GET_AGENT_CARD_ROUTE as n, handleDeleteTaskPushNotificationConfig as o, handleTaskGet as p, a2a_exports as r, handleGetTaskPushNotificationConfig as s, AGENT_EXECUTION_ROUTE as t, handleMessageStream as u };
1342
1762
 
1343
- //# sourceMappingURL=a2a-uPi5sPwb.js.map
1763
+ //# sourceMappingURL=a2a-CHi9AJ0n.js.map