@mastra/server 1.58.0-alpha.0 → 1.58.0-alpha.3

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 (127) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/dist/_types/@mastra_schema-compat/dist/provider-compats/anthropic.d.ts +1 -1
  3. package/dist/{a2a-CnU9zaGR.js → a2a-8C4O2dVJ.js} +41 -5
  4. package/dist/a2a-8C4O2dVJ.js.map +1 -0
  5. package/dist/{a2a-DKnwIgiw.cjs → a2a-B6YkZ2i9.cjs} +872 -227
  6. package/dist/a2a-B6YkZ2i9.cjs.map +1 -0
  7. package/dist/{a2a-oM7aqLz4.cjs → a2a-MPoM5lzS.cjs} +41 -5
  8. package/dist/a2a-MPoM5lzS.cjs.map +1 -0
  9. package/dist/{a2a-uPi5sPwb.js → a2a-_D2EA4oa.js} +861 -228
  10. package/dist/a2a-_D2EA4oa.js.map +1 -0
  11. package/dist/{agent-builder-BDYN3r89.cjs → agent-builder-DAiJH1u2.cjs} +2 -2
  12. package/dist/{agent-builder-BDYN3r89.cjs.map → agent-builder-DAiJH1u2.cjs.map} +1 -1
  13. package/dist/{agent-builder-Dqy_qumV.js → agent-builder-gGFywDVq.js} +2 -2
  14. package/dist/{agent-builder-Dqy_qumV.js.map → agent-builder-gGFywDVq.js.map} +1 -1
  15. package/dist/{api-schema-manifest-7-NC9Dyc.cjs → api-schema-manifest-B3bC-D98.cjs} +2 -2
  16. package/dist/{api-schema-manifest-7-NC9Dyc.cjs.map → api-schema-manifest-B3bC-D98.cjs.map} +1 -1
  17. package/dist/{api-schema-manifest-C5hpdIE_.js → api-schema-manifest-B4J_QYOr.js} +2 -2
  18. package/dist/{api-schema-manifest-C5hpdIE_.js.map → api-schema-manifest-B4J_QYOr.js.map} +1 -1
  19. package/dist/{datasets-Cvix7Rms.cjs → datasets-BMeFvFwT.cjs} +43 -1
  20. package/dist/datasets-BMeFvFwT.cjs.map +1 -0
  21. package/dist/{datasets-dhXnHLAL.js → datasets-BeBH9Seg.js} +38 -2
  22. package/dist/datasets-BeBH9Seg.js.map +1 -0
  23. package/dist/{dist-DGqlUp0b.js → dist-BKRMiJcn.js} +215 -74
  24. package/dist/dist-BKRMiJcn.js.map +1 -0
  25. package/dist/{dist-COHIL6fR.cjs → dist-C2-SedDQ.cjs} +215 -74
  26. package/dist/dist-C2-SedDQ.cjs.map +1 -0
  27. package/dist/docs/SKILL.md +1 -1
  28. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  29. package/dist/docs/references/docs-agents-a2a.md +75 -2
  30. package/dist/docs/references/reference-server-routes.md +25 -11
  31. package/dist/route-builder-CIk5k35M.js.map +1 -1
  32. package/dist/route-builder-CgLVYL1c.cjs.map +1 -1
  33. package/dist/{routes-hSY4wTeS.cjs → routes-CZwPlO3-.cjs} +3 -2
  34. package/dist/routes-CZwPlO3-.cjs.map +1 -0
  35. package/dist/{routes-CRoTKPvQ.js → routes-HUaCyUb7.js} +4 -3
  36. package/dist/{routes-CRoTKPvQ.js.map → routes-HUaCyUb7.js.map} +1 -1
  37. package/dist/server/a2a/store.cjs +30 -2
  38. package/dist/server/a2a/store.cjs.map +1 -1
  39. package/dist/server/a2a/store.d.ts +21 -2
  40. package/dist/server/a2a/store.d.ts.map +1 -1
  41. package/dist/server/a2a/store.js +30 -3
  42. package/dist/server/a2a/store.js.map +1 -1
  43. package/dist/server/a2a/task-state.d.ts +4 -0
  44. package/dist/server/a2a/task-state.d.ts.map +1 -0
  45. package/dist/server/a2a/tasks.d.ts +1 -1
  46. package/dist/server/a2a/tasks.d.ts.map +1 -1
  47. package/dist/server/handlers/a2a.cjs +3 -1
  48. package/dist/server/handlers/a2a.d.ts +155 -12
  49. package/dist/server/handlers/a2a.d.ts.map +1 -1
  50. package/dist/server/handlers/a2a.js +2 -2
  51. package/dist/server/handlers/agent-builder.cjs +3 -3
  52. package/dist/server/handlers/agent-builder.js +3 -3
  53. package/dist/server/handlers/agent-controller.cjs +25 -6
  54. package/dist/server/handlers/agent-controller.cjs.map +1 -1
  55. package/dist/server/handlers/agent-controller.d.ts +4 -3
  56. package/dist/server/handlers/agent-controller.d.ts.map +1 -1
  57. package/dist/server/handlers/agent-controller.js +25 -6
  58. package/dist/server/handlers/agent-controller.js.map +1 -1
  59. package/dist/server/handlers/agent-versions.cjs +6 -0
  60. package/dist/server/handlers/agent-versions.cjs.map +1 -1
  61. package/dist/server/handlers/agent-versions.d.ts.map +1 -1
  62. package/dist/server/handlers/agent-versions.js +6 -0
  63. package/dist/server/handlers/agent-versions.js.map +1 -1
  64. package/dist/server/handlers/datasets.cjs +26 -11
  65. package/dist/server/handlers/datasets.cjs.map +1 -1
  66. package/dist/server/handlers/datasets.d.ts +136 -0
  67. package/dist/server/handlers/datasets.d.ts.map +1 -1
  68. package/dist/server/handlers/datasets.js +26 -11
  69. package/dist/server/handlers/datasets.js.map +1 -1
  70. package/dist/server/handlers/observability.cjs +20 -9
  71. package/dist/server/handlers/observability.cjs.map +1 -1
  72. package/dist/server/handlers/observability.d.ts.map +1 -1
  73. package/dist/server/handlers/observability.js +20 -9
  74. package/dist/server/handlers/observability.js.map +1 -1
  75. package/dist/server/handlers/stored-agents.cjs +16 -1
  76. package/dist/server/handlers/stored-agents.cjs.map +1 -1
  77. package/dist/server/handlers/stored-agents.d.ts.map +1 -1
  78. package/dist/server/handlers/stored-agents.js +16 -1
  79. package/dist/server/handlers/stored-agents.js.map +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 +105 -2
  89. package/dist/server/handlers/workflows.cjs.map +1 -1
  90. package/dist/server/handlers/workflows.d.ts +9 -0
  91. package/dist/server/handlers/workflows.d.ts.map +1 -1
  92. package/dist/server/handlers/workflows.js +104 -3
  93. package/dist/server/handlers/workflows.js.map +1 -1
  94. package/dist/server/handlers.cjs +1 -1
  95. package/dist/server/handlers.js +1 -1
  96. package/dist/server/schemas/a2a.d.ts +70 -6
  97. package/dist/server/schemas/a2a.d.ts.map +1 -1
  98. package/dist/server/schemas/datasets.d.ts +53 -0
  99. package/dist/server/schemas/datasets.d.ts.map +1 -1
  100. package/dist/server/schemas/index.cjs +5 -4
  101. package/dist/server/schemas/index.js +5 -5
  102. package/dist/server/schemas/workflows.d.ts +8 -0
  103. package/dist/server/schemas/workflows.d.ts.map +1 -1
  104. package/dist/server/server-adapter/index.cjs +1 -1
  105. package/dist/server/server-adapter/index.js +1 -1
  106. package/dist/server/server-adapter/routes/a2a.d.ts +116 -8
  107. package/dist/server/server-adapter/routes/a2a.d.ts.map +1 -1
  108. package/dist/server/server-adapter/routes/datasets.d.ts +136 -0
  109. package/dist/server/server-adapter/routes/datasets.d.ts.map +1 -1
  110. package/dist/server/server-adapter/routes/workflows.d.ts +7 -1
  111. package/dist/server/server-adapter/routes/workflows.d.ts.map +1 -1
  112. package/dist/{workflows-DPW6FrY0.js → workflows-CMU5dz_4.js} +7 -2
  113. package/dist/workflows-CMU5dz_4.js.map +1 -0
  114. package/dist/{workflows-XfiENvkF.cjs → workflows-Dzxr6cmf.cjs} +12 -1
  115. package/dist/workflows-Dzxr6cmf.cjs.map +1 -0
  116. package/package.json +8 -8
  117. package/dist/a2a-CnU9zaGR.js.map +0 -1
  118. package/dist/a2a-DKnwIgiw.cjs.map +0 -1
  119. package/dist/a2a-oM7aqLz4.cjs.map +0 -1
  120. package/dist/a2a-uPi5sPwb.js.map +0 -1
  121. package/dist/datasets-Cvix7Rms.cjs.map +0 -1
  122. package/dist/datasets-dhXnHLAL.js.map +0 -1
  123. package/dist/dist-COHIL6fR.cjs.map +0 -1
  124. package/dist/dist-DGqlUp0b.js.map +0 -1
  125. package/dist/routes-hSY4wTeS.cjs.map +0 -1
  126. package/dist/workflows-DPW6FrY0.js.map +0 -1
  127. package/dist/workflows-XfiENvkF.cjs.map +0 -1
@@ -2,7 +2,8 @@ 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 { a as agentExecutionResponseSchema, i as agentExecutionBodySchema, r as agentCardResponseSchema, t as a2aAgentIdPathParams } from "./a2a-CnU9zaGR.js";
5
+ import { TaskStoreVersionConflictError } from "./server/a2a/store.js";
6
+ import { a as agentExecutionResponseSchema, i as agentExecutionBodySchema, r as agentCardResponseSchema, t as a2aAgentIdPathParams } from "./a2a-8C4O2dVJ.js";
6
7
  import { getPublicOrigin } from "./server/handlers/auth.js";
7
8
  import { z } from "zod/v4";
8
9
  import { isDeepStrictEqual } from "util";
@@ -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
@@ -402,7 +420,9 @@ var a2a_exports = /* @__PURE__ */ __exportAll({
402
420
  handleSetTaskPushNotificationConfig: () => handleSetTaskPushNotificationConfig,
403
421
  handleTaskCancel: () => handleTaskCancel,
404
422
  handleTaskGet: () => handleTaskGet,
405
- handleTaskResubscribe: () => handleTaskResubscribe
423
+ handleTaskList: () => handleTaskList,
424
+ handleTaskResubscribe: () => handleTaskResubscribe,
425
+ resolveA2AProtocolVersion: () => resolveA2AProtocolVersion
406
426
  });
407
427
  const messagePartSchema = z.discriminatedUnion("kind", [
408
428
  z.object({
@@ -444,6 +464,7 @@ const messageSendParamsSchema = z.object({
444
464
  configuration: z.object({
445
465
  acceptedOutputModes: z.array(z.string()).optional(),
446
466
  blocking: z.boolean().optional(),
467
+ returnImmediately: z.boolean().optional(),
447
468
  historyLength: z.number().optional(),
448
469
  pushNotificationConfig: z.object({
449
470
  url: z.string(),
@@ -458,6 +479,159 @@ const messageSendParamsSchema = z.object({
458
479
  });
459
480
  const defaultPushNotificationStore = new InMemoryPushNotificationStore();
460
481
  const defaultPushNotificationSender = new DefaultPushNotificationSender(defaultPushNotificationStore);
482
+ const V1_TASK_STATES = {
483
+ submitted: "TASK_STATE_SUBMITTED",
484
+ working: "TASK_STATE_WORKING",
485
+ completed: "TASK_STATE_COMPLETED",
486
+ failed: "TASK_STATE_FAILED",
487
+ canceled: "TASK_STATE_CANCELED",
488
+ "input-required": "TASK_STATE_INPUT_REQUIRED",
489
+ rejected: "TASK_STATE_REJECTED",
490
+ "auth-required": "TASK_STATE_AUTH_REQUIRED"
491
+ };
492
+ function normalizeV1Part(part) {
493
+ if ("text" in part) return {
494
+ kind: "text",
495
+ text: part.text,
496
+ metadata: part.metadata
497
+ };
498
+ if ("raw" in part) return {
499
+ kind: "file",
500
+ file: {
501
+ bytes: part.raw,
502
+ mimeType: part.mediaType,
503
+ name: part.filename
504
+ },
505
+ metadata: part.metadata
506
+ };
507
+ if ("url" in part) return {
508
+ kind: "file",
509
+ file: {
510
+ uri: part.url,
511
+ mimeType: part.mediaType,
512
+ name: part.filename
513
+ },
514
+ metadata: part.metadata
515
+ };
516
+ return {
517
+ kind: "data",
518
+ data: part.data,
519
+ metadata: part.metadata
520
+ };
521
+ }
522
+ function normalizeV1Params(params) {
523
+ if (!params?.message) return params;
524
+ const configuration = params.configuration;
525
+ return {
526
+ ...params,
527
+ message: {
528
+ ...params.message,
529
+ kind: "message",
530
+ role: params.message.role === "ROLE_AGENT" ? "agent" : "user",
531
+ parts: params.message.parts.map((part) => normalizeV1Part(part))
532
+ },
533
+ configuration: configuration ? {
534
+ ...configuration,
535
+ blocking: configuration.returnImmediately === void 0 ? void 0 : !configuration.returnImmediately,
536
+ pushNotificationConfig: configuration.taskPushNotificationConfig
537
+ } : void 0
538
+ };
539
+ }
540
+ function toV1Part(part) {
541
+ if (part.kind === "text") return {
542
+ text: part.text,
543
+ metadata: part.metadata
544
+ };
545
+ if (part.kind === "file") return "uri" in part.file ? {
546
+ url: part.file.uri,
547
+ filename: part.file.name,
548
+ mediaType: part.file.mimeType,
549
+ metadata: part.metadata
550
+ } : {
551
+ raw: part.file.bytes,
552
+ filename: part.file.name,
553
+ mediaType: part.file.mimeType,
554
+ metadata: part.metadata
555
+ };
556
+ return {
557
+ data: part.data,
558
+ metadata: part.metadata
559
+ };
560
+ }
561
+ function toV1Message(message) {
562
+ if (!message) return void 0;
563
+ return {
564
+ messageId: message.messageId,
565
+ contextId: message.contextId,
566
+ taskId: message.taskId,
567
+ role: message.role === "agent" ? "ROLE_AGENT" : "ROLE_USER",
568
+ parts: message.parts?.map(toV1Part),
569
+ metadata: message.metadata,
570
+ extensions: message.extensions,
571
+ referenceTaskIds: message.referenceTaskIds
572
+ };
573
+ }
574
+ function toV1Task(task, { includeArtifacts = true, historyLength } = {}) {
575
+ return {
576
+ id: task.id,
577
+ contextId: task.contextId,
578
+ status: {
579
+ state: V1_TASK_STATES[task.status.state] ?? "TASK_STATE_UNSPECIFIED",
580
+ message: toV1Message(task.status.message),
581
+ timestamp: task.status.timestamp
582
+ },
583
+ artifacts: includeArtifacts ? task.artifacts?.map((artifact) => ({
584
+ artifactId: artifact.artifactId,
585
+ name: artifact.name,
586
+ description: artifact.description,
587
+ parts: artifact.parts?.map(toV1Part),
588
+ metadata: artifact.metadata,
589
+ extensions: artifact.extensions
590
+ })) : void 0,
591
+ history: task.history?.slice(historyLength === void 0 ? 0 : -historyLength).map(toV1Message),
592
+ metadata: task.metadata
593
+ };
594
+ }
595
+ function toV1Result(result, method) {
596
+ if (method === "message/send") return result?.kind === "message" ? { message: toV1Message(result) } : { task: toV1Task(result) };
597
+ if (method === "message/stream" || method === "tasks/resubscribe") {
598
+ if (result?.kind === "status-update") return { statusUpdate: {
599
+ taskId: result.taskId,
600
+ contextId: result.contextId,
601
+ status: {
602
+ state: V1_TASK_STATES[result.status.state] ?? "TASK_STATE_UNSPECIFIED",
603
+ message: toV1Message(result.status.message),
604
+ timestamp: result.status.timestamp
605
+ },
606
+ final: result.final,
607
+ metadata: result.metadata
608
+ } };
609
+ if (result?.kind === "artifact-update") return { artifactUpdate: {
610
+ taskId: result.taskId,
611
+ contextId: result.contextId,
612
+ artifact: {
613
+ ...result.artifact,
614
+ parts: result.artifact.parts?.map(toV1Part)
615
+ },
616
+ append: result.append,
617
+ lastChunk: result.lastChunk,
618
+ metadata: result.metadata
619
+ } };
620
+ return result?.kind === "message" ? { message: toV1Message(result) } : { task: toV1Task(result) };
621
+ }
622
+ if (method === "tasks/get" || method === "tasks/cancel") return toV1Task(result);
623
+ return result;
624
+ }
625
+ function convertV1Response(response, method) {
626
+ if (!response || !("result" in response)) return response;
627
+ return {
628
+ ...response,
629
+ result: toV1Result(response.result, method)
630
+ };
631
+ }
632
+ async function* convertV1Stream(stream, method) {
633
+ for await (const response of stream) yield convertV1Response(response, method);
634
+ }
461
635
  function createAgentCardDefaults({ pushNotifications = false } = {}) {
462
636
  return {
463
637
  protocolVersion: "0.3.0",
@@ -572,6 +746,176 @@ function createDataArtifactUpdate({ taskId, contextId, data, lastChunk }) {
572
746
  }
573
747
  };
574
748
  }
749
+ /**
750
+ * Task metadata keys that store the resume bookkeeping for a suspended agent
751
+ * run so a follow-up message for an `input-required` task can resume it.
752
+ */
753
+ const SUSPENDED_RUN_ID_METADATA_KEY = "suspendedRunId";
754
+ const SUSPENDED_TOOL_CALL_ID_METADATA_KEY = "suspendedToolCallId";
755
+ const SUSPENDED_REQUIRES_APPROVAL_METADATA_KEY = "suspendedRequiresApproval";
756
+ function toJsonSafe(value) {
757
+ if (value === void 0) return;
758
+ try {
759
+ return JSON.parse(JSON.stringify(value));
760
+ } catch {
761
+ return;
762
+ }
763
+ }
764
+ /**
765
+ * Builds the `input-required` status update for a suspended agent run.
766
+ * The status message carries a human-readable prompt plus a data part with
767
+ * the structured suspend payload and resume schema so A2A clients can
768
+ * render/collect the required input (HITL per the A2A spec).
769
+ */
770
+ function createInputRequiredStatusUpdate({ taskId, contextId, suspendPayload, resumeSchema, logger }) {
771
+ const extractMessage = (value) => {
772
+ if (!value || typeof value !== "object") return;
773
+ const messageValue = value.message;
774
+ return typeof messageValue === "string" && messageValue.length > 0 ? messageValue : void 0;
775
+ };
776
+ const promptText = extractMessage(suspendPayload) ?? extractMessage(suspendPayload?.suspendPayload) ?? "Additional input is required to continue this task.";
777
+ const safeSuspendPayload = toJsonSafe(suspendPayload);
778
+ const safeResumeSchema = toJsonSafe(resumeSchema);
779
+ if (suspendPayload !== void 0 && safeSuspendPayload === void 0) logger?.warn(`[Task ${taskId}] Suspend payload is not JSON-serializable and was omitted from the status message.`);
780
+ if (resumeSchema !== void 0 && safeResumeSchema === void 0) logger?.warn(`[Task ${taskId}] Resume schema is not JSON-serializable and was omitted from the status message.`);
781
+ const data = {
782
+ ...safeSuspendPayload !== void 0 ? { suspendPayload: safeSuspendPayload } : {},
783
+ ...safeResumeSchema !== void 0 ? { resumeSchema: safeResumeSchema } : {}
784
+ };
785
+ return {
786
+ state: "input-required",
787
+ message: {
788
+ messageId: crypto.randomUUID(),
789
+ kind: "message",
790
+ role: "agent",
791
+ taskId,
792
+ contextId,
793
+ parts: [{
794
+ kind: "text",
795
+ text: promptText
796
+ }, ...Object.keys(data).length > 0 ? [{
797
+ kind: "data",
798
+ data
799
+ }] : []]
800
+ }
801
+ };
802
+ }
803
+ /**
804
+ * Extracts resume data from a follow-up message for an `input-required` task.
805
+ * Prefers a structured data part; falls back to parsing the text as JSON
806
+ * (the Mastra A2A client serializes structured resume data as JSON text),
807
+ * and finally to the raw text.
808
+ */
809
+ function extractResumeData(message) {
810
+ const dataPart = message.parts.find((part) => part.kind === "data");
811
+ if (dataPart && "data" in dataPart) return dataPart.data;
812
+ const text = message.parts.filter((part) => part.kind === "text").map((part) => part.text).join("\n").trim();
813
+ if (!text) return;
814
+ try {
815
+ return JSON.parse(text);
816
+ } catch {
817
+ return text;
818
+ }
819
+ }
820
+ function getSuspendedRunId(task) {
821
+ const value = task?.metadata?.[SUSPENDED_RUN_ID_METADATA_KEY];
822
+ return typeof value === "string" ? value : void 0;
823
+ }
824
+ /**
825
+ * Approval suspensions (`requireApproval`) carry `{ toolCallId, toolName, args, resumeSchema }`
826
+ * without a nested `suspendPayload`, while `suspend()` suspensions include one
827
+ * (see `ToolCallApprovalPayload` / `ToolCallSuspendedPayload` in @mastra/core).
828
+ */
829
+ function isApprovalSuspension(suspendPayload) {
830
+ if (!suspendPayload || typeof suspendPayload !== "object") return false;
831
+ const payload = suspendPayload;
832
+ return typeof payload.toolCallId === "string" && payload.suspendPayload === void 0;
833
+ }
834
+ const APPROVAL_AFFIRMATIVE_PATTERN = /^(y|yes|approve|approved|ok|okay|confirm|confirmed|true)[.!]?$/i;
835
+ const APPROVAL_NEGATIVE_PATTERN = /^(n|no|decline|declined|deny|denied|reject|rejected|false)[.!]?$/i;
836
+ /**
837
+ * Approval resumes are driven by `resumeData.approved` in the agentic loop, so
838
+ * plain-text replies from A2A clients ("yes", "no") are coerced to the
839
+ * `{ approved }` shape. Unrecognized values pass through unchanged.
840
+ */
841
+ function normalizeResumeData(resumeData, requiresApproval) {
842
+ if (!requiresApproval || typeof resumeData !== "string") return resumeData;
843
+ const text = resumeData.trim();
844
+ if (APPROVAL_AFFIRMATIVE_PATTERN.test(text)) return { approved: true };
845
+ if (APPROVAL_NEGATIVE_PATTERN.test(text)) return { approved: false };
846
+ return resumeData;
847
+ }
848
+ /**
849
+ * Marks a task `input-required` for a suspended agent run and records the
850
+ * resume bookkeeping (runId, toolCallId, approval flag) in task metadata.
851
+ * Shared by the send and stream paths so both report identical suspensions.
852
+ */
853
+ function applySuspensionToTask({ task, suspendPayload, resumeSchema, runId, logger }) {
854
+ const nextTask = applyUpdateToTask(task, createInputRequiredStatusUpdate({
855
+ taskId: task.id,
856
+ contextId: task.contextId,
857
+ suspendPayload,
858
+ resumeSchema,
859
+ logger
860
+ }));
861
+ const payload = suspendPayload;
862
+ nextTask.metadata = {
863
+ ...clearSuspensionMetadata(nextTask.metadata),
864
+ [SUSPENDED_RUN_ID_METADATA_KEY]: runId,
865
+ ...typeof payload?.toolCallId === "string" ? { [SUSPENDED_TOOL_CALL_ID_METADATA_KEY]: payload.toolCallId } : {},
866
+ ...isApprovalSuspension(suspendPayload) ? { [SUSPENDED_REQUIRES_APPROVAL_METADATA_KEY]: true } : {}
867
+ };
868
+ return nextTask;
869
+ }
870
+ /** Removes the suspension bookkeeping from task metadata once the run completes. */
871
+ function clearSuspensionMetadata(metadata) {
872
+ const { [SUSPENDED_RUN_ID_METADATA_KEY]: _runId, [SUSPENDED_TOOL_CALL_ID_METADATA_KEY]: _toolCallId, [SUSPENDED_REQUIRES_APPROVAL_METADATA_KEY]: _requiresApproval, ...rest } = metadata ?? {};
873
+ return rest;
874
+ }
875
+ /**
876
+ * Claims an interrupted task for resume by transitioning it to `working`.
877
+ * `loadWithVersion` and the body of `InMemoryTaskStore.save` both execute
878
+ * synchronously, so two concurrent follow-up messages cannot both claim (and
879
+ * double-resume) the same suspended run.
880
+ */
881
+ async function claimInterruptedTaskResume({ taskStore, agentId, taskId }) {
882
+ const snapshot = taskStore.loadWithVersion({
883
+ agentId,
884
+ taskId
885
+ });
886
+ if (snapshot?.task.status.state !== "input-required" && snapshot?.task.status.state !== "auth-required") return;
887
+ const task = snapshot.task;
888
+ const toolCallId = task.metadata?.[SUSPENDED_TOOL_CALL_ID_METADATA_KEY];
889
+ const claim = {
890
+ runId: getSuspendedRunId(task) ?? taskId,
891
+ ...typeof toolCallId === "string" ? { toolCallId } : {},
892
+ requiresApproval: task.metadata?.[SUSPENDED_REQUIRES_APPROVAL_METADATA_KEY] === true
893
+ };
894
+ try {
895
+ await taskStore.save({
896
+ agentId,
897
+ data: applyUpdateToTask(task, { state: "working" }),
898
+ expectedVersion: snapshot.version
899
+ });
900
+ } catch (error) {
901
+ if (error instanceof TaskStoreVersionConflictError) return;
902
+ throw error;
903
+ }
904
+ return claim;
905
+ }
906
+ async function waitForClaimedResume({ taskStore, agentId, taskId }) {
907
+ let snapshot = taskStore.loadWithVersion({
908
+ agentId,
909
+ taskId
910
+ });
911
+ if (!snapshot) throw MastraA2AError.taskNotFound(taskId);
912
+ while (snapshot.task.status.state === "working") snapshot = await taskStore.waitForNextUpdate({
913
+ agentId,
914
+ taskId,
915
+ afterVersion: snapshot.version
916
+ });
917
+ return snapshot.task;
918
+ }
575
919
  function resolvePushNotificationPair({ pushNotificationStore, pushNotificationSender }) {
576
920
  if (pushNotificationSender) return {
577
921
  pushNotificationStore: pushNotificationSender.getStore(),
@@ -600,23 +944,45 @@ function shouldSendPushNotification(previousTask, nextTask) {
600
944
  "completed",
601
945
  "failed",
602
946
  "canceled",
603
- "input-required"
947
+ "rejected",
948
+ "input-required",
949
+ "auth-required"
604
950
  ].includes(nextTask.status.state)) return false;
605
951
  return previousTask?.status.state !== nextTask.status.state;
606
952
  }
607
- async function saveTaskAndMaybeSendPushNotification({ taskStore, pushNotificationSender, previousTask, nextTask, agentId, logger }) {
608
- await taskStore.save({
953
+ function createLinkedAbortController(abortSignal) {
954
+ const controller = new AbortController();
955
+ if (!abortSignal) return {
956
+ controller,
957
+ cleanup: () => {}
958
+ };
959
+ const abortFromSignal = () => {
960
+ if (!controller.signal.aborted) controller.abort(abortSignal.reason);
961
+ };
962
+ if (abortSignal.aborted) abortFromSignal();
963
+ else abortSignal.addEventListener("abort", abortFromSignal, { once: true });
964
+ return {
965
+ controller,
966
+ cleanup: () => abortSignal.removeEventListener("abort", abortFromSignal)
967
+ };
968
+ }
969
+ async function saveTaskAndMaybeSendPushNotification({ taskStore, pushNotificationSender, previousTask, nextTask, agentId, expectedVersion, logger }) {
970
+ const storedTask = await taskStore.save({
609
971
  agentId,
610
- data: nextTask
972
+ data: nextTask,
973
+ expectedVersion,
974
+ skipIfCanceled: true
611
975
  });
612
- if (!shouldSendPushNotification(previousTask, nextTask)) return;
976
+ if (storedTask.status.state === "canceled" && nextTask.status.state !== "canceled") return storedTask;
977
+ if (!shouldSendPushNotification(previousTask, storedTask)) return storedTask;
613
978
  pushNotificationSender.sendNotifications({
614
979
  agentId,
615
- task: nextTask,
980
+ task: storedTask,
616
981
  logger
617
982
  }).catch((error) => {
618
983
  logger?.error("Failed to schedule A2A push notification", error);
619
984
  });
985
+ return storedTask;
620
986
  }
621
987
  function extractFullStreamTextDelta(value) {
622
988
  if (typeof value !== "object" || value === null || !("type" in value)) return null;
@@ -639,12 +1005,10 @@ function extractFinalStructuredObject(value) {
639
1005
  const objectValue = chunk.payload?.object ?? chunk.object;
640
1006
  return objectValue && typeof objectValue === "object" ? objectValue : void 0;
641
1007
  }
642
- function isTerminalTaskState(state) {
643
- return [
644
- "completed",
645
- "failed",
646
- "canceled"
647
- ].includes(state);
1008
+ function isSuspensionChunk(value) {
1009
+ if (typeof value !== "object" || value === null || !("type" in value)) return false;
1010
+ const type = value.type;
1011
+ return type === "tool-call-suspended" || type === "tool-call-approval";
648
1012
  }
649
1013
  function artifactIdentity(artifact) {
650
1014
  return artifact.artifactId || artifact.name;
@@ -689,11 +1053,15 @@ function getTaskArtifactUpdates({ previous, next }) {
689
1053
  artifact: structuredClone(artifact)
690
1054
  }));
691
1055
  }
692
- async function executeMessageSend({ requestId, message, metadata, currentData, taskStore, pushNotificationSender, agent, agentId, logger, requestContext }) {
1056
+ async function executeMessageSend({ requestId, message, metadata, currentData, taskStore, pushNotificationSender, agent, agentId, logger, requestContext, resume }) {
693
1057
  const { contextId } = message;
694
1058
  try {
695
1059
  const resourceId = metadata?.resourceId ?? message.metadata?.resourceId ?? agentId;
696
- const result = await agent.generate([convertToCoreMessage(message)], {
1060
+ const result = resume ? await agent.resumeGenerate(normalizeResumeData(extractResumeData(message), resume.requiresApproval), {
1061
+ runId: resume.runId,
1062
+ ...resume.toolCallId ? { toolCallId: resume.toolCallId } : {},
1063
+ requestContext
1064
+ }) : await agent.generate([convertToCoreMessage(message)], {
697
1065
  runId: currentData.id,
698
1066
  requestContext,
699
1067
  ...contextId ? {
@@ -714,13 +1082,32 @@ async function executeMessageSend({ requestId, message, metadata, currentData, t
714
1082
  data: result.object
715
1083
  });
716
1084
  if (artifactUpdate) currentData = applyUpdateToTask(currentData, artifactUpdate);
1085
+ if (result.finishReason === "suspended") {
1086
+ const previousTask = currentData;
1087
+ currentData = applySuspensionToTask({
1088
+ task: currentData,
1089
+ suspendPayload: result.suspendPayload,
1090
+ resumeSchema: result.resumeSchema,
1091
+ runId: result.runId ?? currentData.id,
1092
+ logger
1093
+ });
1094
+ await saveTaskAndMaybeSendPushNotification({
1095
+ taskStore,
1096
+ pushNotificationSender,
1097
+ previousTask,
1098
+ nextTask: currentData,
1099
+ agentId,
1100
+ logger
1101
+ });
1102
+ return createSuccessResponse(requestId, currentData);
1103
+ }
717
1104
  const previousTask = currentData;
718
1105
  currentData = applyUpdateToTask(currentData, {
719
1106
  state: "completed",
720
1107
  message: void 0
721
1108
  });
722
1109
  currentData.metadata = {
723
- ...currentData.metadata,
1110
+ ...clearSuspensionMetadata(currentData.metadata),
724
1111
  execution: {
725
1112
  toolCalls: result.toolCalls,
726
1113
  toolResults: result.toolResults,
@@ -778,11 +1165,28 @@ async function handleMessageSend({ requestId, params, taskStore, pushNotificatio
778
1165
  const { message, metadata } = params;
779
1166
  const { contextId } = message;
780
1167
  const taskId = message.taskId || crypto.randomUUID();
781
- const existingTask = await taskStore.load({
1168
+ const existingTask = taskStore.loadWithVersion({
782
1169
  agentId,
783
1170
  taskId
784
- });
1171
+ })?.task;
1172
+ if (message.taskId && !existingTask) throw MastraA2AError.taskNotFound(message.taskId);
785
1173
  if (params.configuration?.blocking === false && existingTask?.status.state === "working") return createSuccessResponse(requestId, existingTask);
1174
+ if (existingTask?.status.state === "working" && getSuspendedRunId(existingTask)) return createSuccessResponse(requestId, await waitForClaimedResume({
1175
+ taskStore,
1176
+ agentId,
1177
+ taskId
1178
+ }));
1179
+ const wasInterrupted = isInterruptedTaskState(existingTask?.status.state);
1180
+ const resume = await claimInterruptedTaskResume({
1181
+ taskStore,
1182
+ agentId,
1183
+ taskId
1184
+ });
1185
+ if (wasInterrupted && !resume) return createSuccessResponse(requestId, await waitForClaimedResume({
1186
+ taskStore,
1187
+ agentId,
1188
+ taskId
1189
+ }));
786
1190
  const { pushNotificationStore: resolvedPushNotificationStore, pushNotificationSender: resolvedPushNotificationSender } = resolvePushNotificationPair({
787
1191
  pushNotificationStore,
788
1192
  pushNotificationSender
@@ -828,7 +1232,8 @@ async function handleMessageSend({ requestId, params, taskStore, pushNotificatio
828
1232
  agent,
829
1233
  agentId,
830
1234
  logger,
831
- requestContext
1235
+ requestContext,
1236
+ resume
832
1237
  });
833
1238
  if (params.configuration?.blocking === false) {
834
1239
  execution.catch((error) => {
@@ -846,6 +1251,25 @@ async function handleTaskGet({ requestId, taskStore, agentId, taskId }) {
846
1251
  if (!task) throw MastraA2AError.taskNotFound(taskId);
847
1252
  return createSuccessResponse(requestId, task);
848
1253
  }
1254
+ function handleTaskList({ requestId, taskStore, agentId, params }) {
1255
+ const requestedPageSize = params.pageSize ?? 50;
1256
+ const offset = params.pageToken ? Number.parseInt(params.pageToken, 10) : 0;
1257
+ const start = Number.isFinite(offset) && offset >= 0 ? offset : 0;
1258
+ const status = typeof params.status === "string" ? params.status.replace(/^TASK_STATE_/, "").toLowerCase().replaceAll("_", "-") : void 0;
1259
+ const timestampAfter = params.statusTimestampAfter ? Date.parse(params.statusTimestampAfter) : void 0;
1260
+ const matchingTasks = taskStore.list({ agentId }).filter((task) => !params.contextId || task.contextId === params.contextId).filter((task) => !status || task.status.state === status).filter((task) => !timestampAfter || task.status.timestamp !== void 0 && Date.parse(task.status.timestamp) >= timestampAfter);
1261
+ const tasks = matchingTasks.slice(start, start + requestedPageSize).map((task) => toV1Task(task, {
1262
+ includeArtifacts: params.includeArtifacts ?? false,
1263
+ historyLength: params.historyLength
1264
+ }));
1265
+ const nextOffset = start + tasks.length;
1266
+ return createSuccessResponse(requestId, {
1267
+ tasks,
1268
+ nextPageToken: nextOffset < matchingTasks.length ? String(nextOffset) : "",
1269
+ pageSize: requestedPageSize,
1270
+ totalSize: matchingTasks.length
1271
+ });
1272
+ }
849
1273
  async function loadTaskOrThrow({ taskStore, agentId, taskId }) {
850
1274
  const task = await taskStore.load({
851
1275
  agentId,
@@ -905,11 +1329,20 @@ async function handleDeleteTaskPushNotificationConfig({ requestId, taskStore, pu
905
1329
  })) throw MastraA2AError.invalidParams(`Push notification config not found: ${params.pushNotificationConfigId}`);
906
1330
  return createSuccessResponse(requestId, null);
907
1331
  }
908
- async function* handleMessageStream({ requestId, params, taskStore, pushNotificationStore, pushNotificationSender, agent, agentId, logger, requestContext }) {
1332
+ async function* handleMessageStream({ requestId, params, taskStore, pushNotificationStore, pushNotificationSender, agent, agentId, logger, requestContext, abortSignal }) {
909
1333
  validateMessageSendParams(params);
910
1334
  const { message, metadata } = params;
911
1335
  const { contextId } = message;
912
1336
  const taskId = message.taskId || crypto.randomUUID();
1337
+ if (message.taskId && !taskStore.loadWithVersion({
1338
+ agentId,
1339
+ taskId
1340
+ })) throw MastraA2AError.taskNotFound(message.taskId);
1341
+ const resume = await claimInterruptedTaskResume({
1342
+ taskStore,
1343
+ agentId,
1344
+ taskId
1345
+ });
913
1346
  const { pushNotificationStore: resolvedPushNotificationStore, pushNotificationSender: resolvedPushNotificationSender } = resolvePushNotificationPair({
914
1347
  pushNotificationStore,
915
1348
  pushNotificationSender
@@ -945,12 +1378,24 @@ async function* handleMessageStream({ requestId, params, taskStore, pushNotifica
945
1378
  agentId,
946
1379
  logger
947
1380
  });
948
- yield createSuccessResponse(requestId, currentData);
1381
+ const { controller: taskAbortController, cleanup: cleanupLinkedAbortController } = createLinkedAbortController(abortSignal);
1382
+ const unregisterTaskAbortController = taskStore.registerAbortController({
1383
+ agentId,
1384
+ taskId,
1385
+ controller: taskAbortController
1386
+ });
949
1387
  try {
1388
+ yield createSuccessResponse(requestId, currentData);
950
1389
  const resourceId = metadata?.resourceId ?? message.metadata?.resourceId ?? agentId;
951
- const result = await agent.stream([convertToCoreMessage(message)], {
1390
+ const result = resume ? await agent.resumeStream(normalizeResumeData(extractResumeData(message), resume.requiresApproval), {
1391
+ runId: resume.runId,
1392
+ ...resume.toolCallId ? { toolCallId: resume.toolCallId } : {},
1393
+ requestContext,
1394
+ abortSignal: taskAbortController.signal
1395
+ }) : await agent.stream([convertToCoreMessage(message)], {
952
1396
  runId: taskId,
953
1397
  requestContext,
1398
+ abortSignal: taskAbortController.signal,
954
1399
  ...contextId ? {
955
1400
  threadId: contextId,
956
1401
  resourceId
@@ -959,7 +1404,22 @@ async function* handleMessageStream({ requestId, params, taskStore, pushNotifica
959
1404
  let sawTextArtifact = false;
960
1405
  let pendingTextChunk;
961
1406
  let structuredData;
1407
+ let suspended = false;
1408
+ let streamCanceled = false;
962
1409
  for await (const chunk of result.fullStream) {
1410
+ if (taskAbortController.signal.aborted) {
1411
+ const latestTask = await taskStore.load({
1412
+ agentId,
1413
+ taskId: currentData.id
1414
+ });
1415
+ if (latestTask) currentData = latestTask;
1416
+ streamCanceled = true;
1417
+ break;
1418
+ }
1419
+ if (isSuspensionChunk(chunk)) {
1420
+ suspended = true;
1421
+ continue;
1422
+ }
963
1423
  const textDelta = extractFullStreamTextDelta(chunk);
964
1424
  if (textDelta !== null) {
965
1425
  if (!pendingTextChunk) {
@@ -974,13 +1434,17 @@ async function* handleMessageStream({ requestId, params, taskStore, pushNotifica
974
1434
  lastChunk: false
975
1435
  });
976
1436
  currentData = applyUpdateToTask(currentData, textUpdate);
977
- await saveTaskAndMaybeSendPushNotification({
1437
+ currentData = await saveTaskAndMaybeSendPushNotification({
978
1438
  taskStore,
979
1439
  pushNotificationSender: resolvedPushNotificationSender,
980
1440
  nextTask: currentData,
981
1441
  agentId,
982
1442
  logger
983
1443
  });
1444
+ if (currentData.status.state === "canceled") {
1445
+ streamCanceled = true;
1446
+ break;
1447
+ }
984
1448
  yield createSuccessResponse(requestId, textUpdate);
985
1449
  sawTextArtifact = true;
986
1450
  pendingTextChunk = textDelta;
@@ -989,98 +1453,217 @@ async function* handleMessageStream({ requestId, params, taskStore, pushNotifica
989
1453
  const finalStructuredObject = extractFinalStructuredObject(chunk);
990
1454
  if (finalStructuredObject) structuredData = finalStructuredObject;
991
1455
  }
992
- structuredData ??= await result.object;
993
- if (!pendingTextChunk && !sawTextArtifact) {
994
- const finalText = await result.text;
995
- if (finalText) pendingTextChunk = finalText;
1456
+ if (!streamCanceled && taskAbortController.signal.aborted) {
1457
+ const latestTask = await taskStore.load({
1458
+ agentId,
1459
+ taskId: currentData.id
1460
+ });
1461
+ if (latestTask) currentData = latestTask;
1462
+ streamCanceled = true;
996
1463
  }
997
- if (pendingTextChunk) {
998
- const textUpdate = createTextChunkArtifactUpdate({
999
- taskId: currentData.id,
1000
- contextId: currentData.contextId,
1001
- text: pendingTextChunk,
1002
- append: sawTextArtifact,
1003
- lastChunk: !structuredData
1464
+ if (streamCanceled && abortSignal?.aborted && currentData.status.state !== "canceled") {
1465
+ const previousTask = currentData;
1466
+ currentData = applyUpdateToTask(currentData, {
1467
+ state: "canceled",
1468
+ message: {
1469
+ messageId: crypto.randomUUID(),
1470
+ role: "agent",
1471
+ parts: [{
1472
+ kind: "text",
1473
+ text: "Task canceled because the request was aborted."
1474
+ }],
1475
+ kind: "message"
1476
+ }
1004
1477
  });
1005
- currentData = applyUpdateToTask(currentData, textUpdate);
1006
- await saveTaskAndMaybeSendPushNotification({
1478
+ currentData = await saveTaskAndMaybeSendPushNotification({
1007
1479
  taskStore,
1008
1480
  pushNotificationSender: resolvedPushNotificationSender,
1481
+ previousTask,
1009
1482
  nextTask: currentData,
1010
1483
  agentId,
1011
1484
  logger
1012
1485
  });
1013
- yield createSuccessResponse(requestId, textUpdate);
1014
- sawTextArtifact = true;
1015
- pendingTextChunk = void 0;
1016
1486
  }
1017
- if (structuredData) {
1018
- const dataUpdate = createDataArtifactUpdate({
1019
- taskId: currentData.id,
1020
- contextId: currentData.contextId,
1021
- data: structuredData,
1022
- lastChunk: true
1487
+ if (!streamCanceled && suspended) {
1488
+ if (pendingTextChunk) {
1489
+ const textUpdate = createTextChunkArtifactUpdate({
1490
+ taskId: currentData.id,
1491
+ contextId: currentData.contextId,
1492
+ text: pendingTextChunk,
1493
+ append: sawTextArtifact,
1494
+ lastChunk: true
1495
+ });
1496
+ currentData = applyUpdateToTask(currentData, textUpdate);
1497
+ currentData = await saveTaskAndMaybeSendPushNotification({
1498
+ taskStore,
1499
+ pushNotificationSender: resolvedPushNotificationSender,
1500
+ nextTask: currentData,
1501
+ agentId,
1502
+ logger
1503
+ });
1504
+ if (currentData.status.state === "canceled") streamCanceled = true;
1505
+ else yield createSuccessResponse(requestId, textUpdate);
1506
+ }
1507
+ const suspensionTask = currentData;
1508
+ currentData = applySuspensionToTask({
1509
+ task: currentData,
1510
+ suspendPayload: await result.suspendPayload,
1511
+ resumeSchema: await result.resumeSchema,
1512
+ runId: result.runId ?? currentData.id,
1513
+ logger
1023
1514
  });
1024
- currentData = applyUpdateToTask(currentData, dataUpdate);
1025
- await saveTaskAndMaybeSendPushNotification({
1515
+ currentData = await saveTaskAndMaybeSendPushNotification({
1026
1516
  taskStore,
1027
1517
  pushNotificationSender: resolvedPushNotificationSender,
1518
+ previousTask: suspensionTask,
1028
1519
  nextTask: currentData,
1029
1520
  agentId,
1030
1521
  logger
1031
1522
  });
1032
- yield createSuccessResponse(requestId, dataUpdate);
1523
+ if (currentData.status.state === "canceled") streamCanceled = true;
1033
1524
  }
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
1525
+ if (!streamCanceled && !suspended) {
1526
+ structuredData ??= await result.object;
1527
+ if (!pendingTextChunk && !sawTextArtifact) {
1528
+ const finalText = await result.text;
1529
+ if (finalText) pendingTextChunk = finalText;
1046
1530
  }
1047
- };
1048
- currentData = completedTask;
1049
- await saveTaskAndMaybeSendPushNotification({
1050
- taskStore,
1051
- pushNotificationSender: resolvedPushNotificationSender,
1052
- previousTask,
1053
- nextTask: currentData,
1531
+ if (pendingTextChunk) {
1532
+ const textUpdate = createTextChunkArtifactUpdate({
1533
+ taskId: currentData.id,
1534
+ contextId: currentData.contextId,
1535
+ text: pendingTextChunk,
1536
+ append: sawTextArtifact,
1537
+ lastChunk: !structuredData
1538
+ });
1539
+ currentData = applyUpdateToTask(currentData, textUpdate);
1540
+ currentData = await saveTaskAndMaybeSendPushNotification({
1541
+ taskStore,
1542
+ pushNotificationSender: resolvedPushNotificationSender,
1543
+ nextTask: currentData,
1544
+ agentId,
1545
+ logger
1546
+ });
1547
+ if (currentData.status.state === "canceled") streamCanceled = true;
1548
+ else yield createSuccessResponse(requestId, textUpdate);
1549
+ sawTextArtifact = true;
1550
+ pendingTextChunk = void 0;
1551
+ }
1552
+ if (!streamCanceled && structuredData) {
1553
+ const dataUpdate = createDataArtifactUpdate({
1554
+ taskId: currentData.id,
1555
+ contextId: currentData.contextId,
1556
+ data: structuredData,
1557
+ lastChunk: true
1558
+ });
1559
+ currentData = applyUpdateToTask(currentData, dataUpdate);
1560
+ currentData = await saveTaskAndMaybeSendPushNotification({
1561
+ taskStore,
1562
+ pushNotificationSender: resolvedPushNotificationSender,
1563
+ nextTask: currentData,
1564
+ agentId,
1565
+ logger
1566
+ });
1567
+ if (currentData.status.state === "canceled") streamCanceled = true;
1568
+ else yield createSuccessResponse(requestId, dataUpdate);
1569
+ }
1570
+ if (!streamCanceled) {
1571
+ const previousTask = currentData;
1572
+ const completedTask = applyUpdateToTask(currentData, {
1573
+ state: "completed",
1574
+ message: void 0
1575
+ });
1576
+ completedTask.metadata = {
1577
+ ...clearSuspensionMetadata(completedTask.metadata),
1578
+ execution: {
1579
+ toolCalls: await result.toolCalls,
1580
+ toolResults: await result.toolResults,
1581
+ usage: await result.usage,
1582
+ finishReason: await result.finishReason
1583
+ }
1584
+ };
1585
+ currentData = await saveTaskAndMaybeSendPushNotification({
1586
+ taskStore,
1587
+ pushNotificationSender: resolvedPushNotificationSender,
1588
+ previousTask,
1589
+ nextTask: completedTask,
1590
+ agentId,
1591
+ logger
1592
+ });
1593
+ }
1594
+ }
1595
+ } catch (handlerError) {
1596
+ const latestTask = await taskStore.load({
1054
1597
  agentId,
1055
- logger
1598
+ taskId: currentData.id
1056
1599
  });
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"
1600
+ if (latestTask?.status.state === "canceled") currentData = latestTask;
1601
+ else if (taskAbortController.signal.aborted) {
1602
+ currentData = latestTask ?? currentData;
1603
+ if (abortSignal?.aborted) {
1604
+ const previousTask = currentData;
1605
+ currentData = applyUpdateToTask(currentData, {
1606
+ state: "canceled",
1607
+ message: {
1608
+ messageId: crypto.randomUUID(),
1609
+ role: "agent",
1610
+ parts: [{
1611
+ kind: "text",
1612
+ text: "Task canceled because the request was aborted."
1613
+ }],
1614
+ kind: "message"
1615
+ }
1616
+ });
1617
+ try {
1618
+ currentData = await saveTaskAndMaybeSendPushNotification({
1619
+ taskStore,
1620
+ pushNotificationSender: resolvedPushNotificationSender,
1621
+ previousTask,
1622
+ nextTask: currentData,
1623
+ agentId,
1624
+ logger
1625
+ });
1626
+ } catch (saveError) {
1627
+ logger?.error(`Failed to save task ${currentData.id} after request abort:`, saveError?.message);
1628
+ }
1069
1629
  }
1070
- });
1071
- try {
1072
- await saveTaskAndMaybeSendPushNotification({
1073
- taskStore,
1074
- pushNotificationSender: resolvedPushNotificationSender,
1075
- previousTask,
1076
- nextTask: currentData,
1077
- agentId,
1078
- logger
1630
+ } else {
1631
+ currentData = latestTask ?? currentData;
1632
+ const previousTask = currentData;
1633
+ currentData = applyUpdateToTask(currentData, {
1634
+ state: "failed",
1635
+ message: {
1636
+ messageId: crypto.randomUUID(),
1637
+ role: "agent",
1638
+ parts: [{
1639
+ kind: "text",
1640
+ text: `Handler failed: ${handlerError instanceof Error ? handlerError.message : String(handlerError)}`
1641
+ }],
1642
+ kind: "message"
1643
+ }
1079
1644
  });
1080
- } catch (saveError) {
1081
- logger?.error(`Failed to save task ${currentData.id} after handler error:`, saveError?.message);
1645
+ try {
1646
+ currentData = await saveTaskAndMaybeSendPushNotification({
1647
+ taskStore,
1648
+ pushNotificationSender: resolvedPushNotificationSender,
1649
+ previousTask,
1650
+ nextTask: currentData,
1651
+ agentId,
1652
+ logger
1653
+ });
1654
+ } catch (saveError) {
1655
+ logger?.error(`Failed to save task ${currentData.id} after handler error:`, saveError?.message);
1656
+ }
1082
1657
  }
1658
+ } finally {
1659
+ unregisterTaskAbortController();
1660
+ cleanupLinkedAbortController();
1083
1661
  }
1662
+ const latestTask = await taskStore.load({
1663
+ agentId,
1664
+ taskId: currentData.id
1665
+ });
1666
+ if (latestTask?.status.state === "canceled") currentData = latestTask;
1084
1667
  yield createSuccessResponse(requestId, {
1085
1668
  kind: "status-update",
1086
1669
  taskId: currentData.id,
@@ -1096,7 +1679,7 @@ async function* handleTaskResubscribe({ requestId, taskStore, agentId, taskId, a
1096
1679
  });
1097
1680
  if (!snapshot) throw MastraA2AError.taskNotFound(taskId);
1098
1681
  yield createSuccessResponse(requestId, snapshot.task);
1099
- if (isTerminalTaskState(snapshot.task.status.state)) return;
1682
+ if (isTerminalTaskState(snapshot.task.status.state) || isInterruptedTaskState(snapshot.task.status.state)) return;
1100
1683
  while (true) {
1101
1684
  const { task, version } = snapshot;
1102
1685
  const nextUpdate = await taskStore.waitForNextUpdate({
@@ -1109,14 +1692,16 @@ async function* handleTaskResubscribe({ requestId, taskStore, agentId, taskId, a
1109
1692
  previous: task,
1110
1693
  next: nextUpdate.task
1111
1694
  })) yield createSuccessResponse(requestId, artifactUpdate);
1695
+ const nextState = nextUpdate.task.status.state;
1696
+ const streamEnded = isTerminalTaskState(nextState) || isInterruptedTaskState(nextState);
1112
1697
  if (didTaskStatusChange(task, nextUpdate.task)) yield createSuccessResponse(requestId, {
1113
1698
  kind: "status-update",
1114
1699
  taskId: nextUpdate.task.id,
1115
1700
  contextId: nextUpdate.task.contextId,
1116
1701
  status: nextUpdate.task.status,
1117
- final: isTerminalTaskState(nextUpdate.task.status.state)
1702
+ final: streamEnded
1118
1703
  });
1119
- if (isTerminalTaskState(nextUpdate.task.status.state)) return;
1704
+ if (streamEnded) return;
1120
1705
  snapshot = nextUpdate;
1121
1706
  }
1122
1707
  }
@@ -1154,101 +1739,135 @@ function createA2ASSEResponse(payload) {
1154
1739
  } });
1155
1740
  }
1156
1741
  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()
1742
+ for (let attempt = 0; attempt < 2; attempt++) {
1743
+ const snapshot = taskStore.loadWithVersion({
1744
+ agentId,
1745
+ taskId
1746
+ });
1747
+ const data = snapshot?.task;
1748
+ if (!data) throw MastraA2AError.taskNotFound(taskId);
1749
+ if (isTerminalTaskState(data.status.state)) {
1750
+ logger?.info(`Task ${taskId} already in final state ${data.status.state}, cannot cancel.`);
1751
+ throw MastraA2AError.taskNotCancelable(taskId);
1181
1752
  }
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);
1753
+ taskStore.activeCancellations.add(taskId);
1754
+ taskStore.abortTask({
1755
+ agentId,
1756
+ taskId,
1757
+ reason: new DOMException("Task cancelled by request.", "AbortError")
1758
+ });
1759
+ const canceledTask = applyUpdateToTask(data, {
1760
+ state: "canceled",
1761
+ message: {
1762
+ role: "agent",
1763
+ parts: [{
1764
+ kind: "text",
1765
+ text: "Task cancelled by request."
1766
+ }],
1767
+ kind: "message",
1768
+ messageId: crypto.randomUUID()
1769
+ }
1770
+ });
1771
+ try {
1772
+ await saveTaskAndMaybeSendPushNotification({
1773
+ taskStore,
1774
+ pushNotificationSender: resolvePushNotificationPair({ pushNotificationSender }).pushNotificationSender,
1775
+ previousTask: data,
1776
+ nextTask: canceledTask,
1777
+ agentId,
1778
+ expectedVersion: snapshot.version,
1779
+ logger
1780
+ });
1781
+ return createSuccessResponse(requestId, canceledTask);
1782
+ } catch (error) {
1783
+ if (!(error instanceof TaskStoreVersionConflictError)) throw error;
1784
+ } finally {
1785
+ taskStore.activeCancellations.delete(taskId);
1786
+ }
1787
+ }
1788
+ throw MastraA2AError.invalidRequest(`Task ${taskId} was updated concurrently. Retry the request.`);
1195
1789
  }
1196
- async function getAgentExecutionHandler({ requestId, mastra, agentId, requestContext, method, params, taskStore, pushNotificationStore, pushNotificationSender, logger, abortSignal }) {
1790
+ async function getAgentExecutionHandler({ requestId, mastra, agentId, requestContext, method, params, taskStore, pushNotificationStore, pushNotificationSender, logger, abortSignal, protocolVersion = "0.3" }) {
1197
1791
  const agent = await getAgentFromSystem({
1198
1792
  mastra,
1199
1793
  agentId
1200
1794
  });
1795
+ const protocolParams = protocolVersion === "1.0" ? normalizeV1Params(params) : params;
1201
1796
  const { pushNotificationStore: resolvedPushNotificationStore, pushNotificationSender: resolvedPushNotificationSender } = resolvePushNotificationPair({
1202
1797
  pushNotificationStore,
1203
1798
  pushNotificationSender
1204
1799
  });
1205
1800
  let taskId;
1206
1801
  try {
1207
- taskId = getTaskIdFromParams(params);
1802
+ taskId = getTaskIdFromParams(protocolParams);
1208
1803
  switch (method) {
1209
- case "message/send": return await handleMessageSend({
1210
- requestId,
1211
- params,
1212
- taskStore,
1213
- pushNotificationStore: resolvedPushNotificationStore,
1214
- pushNotificationSender: resolvedPushNotificationSender,
1215
- agent,
1216
- agentId,
1217
- logger,
1218
- requestContext
1219
- });
1220
- case "message/stream": return await handleMessageStream({
1221
- requestId,
1222
- taskStore,
1223
- params,
1224
- pushNotificationStore: resolvedPushNotificationStore,
1225
- pushNotificationSender: resolvedPushNotificationSender,
1226
- agent,
1227
- agentId,
1228
- logger,
1229
- requestContext
1230
- });
1231
- case "tasks/get": return await handleTaskGet({
1232
- requestId,
1233
- taskStore,
1234
- agentId,
1235
- taskId: taskId || "No task ID provided"
1236
- });
1237
- case "tasks/cancel": return await handleTaskCancel({
1238
- requestId,
1239
- taskStore,
1240
- pushNotificationSender: resolvedPushNotificationSender,
1241
- agentId,
1242
- taskId: taskId || "No task ID provided",
1243
- logger
1244
- });
1245
- case "tasks/resubscribe": return await handleTaskResubscribe({
1246
- requestId,
1247
- taskStore,
1248
- agentId,
1249
- taskId: taskId || "No task ID provided",
1250
- abortSignal
1251
- });
1804
+ case "message/send": {
1805
+ const result = await handleMessageSend({
1806
+ requestId,
1807
+ params: protocolParams,
1808
+ taskStore,
1809
+ pushNotificationStore: resolvedPushNotificationStore,
1810
+ pushNotificationSender: resolvedPushNotificationSender,
1811
+ agent,
1812
+ agentId,
1813
+ logger,
1814
+ requestContext
1815
+ });
1816
+ return protocolVersion === "1.0" ? convertV1Response(result, method) : result;
1817
+ }
1818
+ case "message/stream": {
1819
+ const result = await handleMessageStream({
1820
+ requestId,
1821
+ taskStore,
1822
+ params: protocolParams,
1823
+ pushNotificationStore: resolvedPushNotificationStore,
1824
+ pushNotificationSender: resolvedPushNotificationSender,
1825
+ agent,
1826
+ agentId,
1827
+ logger,
1828
+ requestContext,
1829
+ abortSignal
1830
+ });
1831
+ return protocolVersion === "1.0" ? convertV1Stream(result, method) : result;
1832
+ }
1833
+ case "tasks/get": {
1834
+ const result = await handleTaskGet({
1835
+ requestId,
1836
+ taskStore,
1837
+ agentId,
1838
+ taskId: taskId || "No task ID provided"
1839
+ });
1840
+ return protocolVersion === "1.0" ? convertV1Response(result, method) : result;
1841
+ }
1842
+ case "tasks/list":
1843
+ if (protocolVersion !== "1.0") throw MastraA2AError.methodNotFound(method);
1844
+ return handleTaskList({
1845
+ requestId,
1846
+ taskStore,
1847
+ agentId,
1848
+ params: protocolParams ?? {}
1849
+ });
1850
+ case "tasks/cancel": {
1851
+ const result = await handleTaskCancel({
1852
+ requestId,
1853
+ taskStore,
1854
+ pushNotificationSender: resolvedPushNotificationSender,
1855
+ agentId,
1856
+ taskId: taskId || "No task ID provided",
1857
+ logger
1858
+ });
1859
+ return protocolVersion === "1.0" ? convertV1Response(result, method) : result;
1860
+ }
1861
+ case "tasks/resubscribe": {
1862
+ const result = handleTaskResubscribe({
1863
+ requestId,
1864
+ taskStore,
1865
+ agentId,
1866
+ taskId: taskId || "No task ID provided",
1867
+ abortSignal
1868
+ });
1869
+ return protocolVersion === "1.0" ? convertV1Stream(result, method) : result;
1870
+ }
1252
1871
  case "tasks/pushNotificationConfig/set": return await handleSetTaskPushNotificationConfig({
1253
1872
  requestId,
1254
1873
  taskStore,
@@ -1285,6 +1904,12 @@ async function getAgentExecutionHandler({ requestId, mastra, agentId, requestCon
1285
1904
  return normalizeError(error, requestId, taskId, logger);
1286
1905
  }
1287
1906
  }
1907
+ function resolveA2AProtocolVersion(request) {
1908
+ const version = request?.headers.get("A2A-Version")?.trim();
1909
+ if (!version || version === "0.3") return "0.3";
1910
+ if (version === "1.0") return "1.0";
1911
+ throw MastraA2AError.versionNotSupported(version);
1912
+ }
1288
1913
  const GET_AGENT_CARD_ROUTE = createRoute({
1289
1914
  method: "GET",
1290
1915
  path: "/.well-known/:agentId/agent-card.json",
@@ -1321,23 +1946,31 @@ const AGENT_EXECUTION_ROUTE = createRoute({
1321
1946
  description: "Executes an agent action via JSON-RPC 2.0 over A2A protocol",
1322
1947
  tags: ["Agent-to-Agent"],
1323
1948
  requiresAuth: true,
1324
- handler: async ({ mastra, agentId, requestContext, taskStore, abortSignal, ...bodyParams }) => {
1949
+ handler: async ({ mastra, agentId, requestContext, taskStore, abortSignal, request, ...bodyParams }) => {
1325
1950
  const { id: requestId, method } = bodyParams;
1951
+ const params = "params" in bodyParams ? bodyParams.params : void 0;
1952
+ let protocolVersion;
1953
+ try {
1954
+ protocolVersion = resolveA2AProtocolVersion(request);
1955
+ } catch (error) {
1956
+ return createA2AJsonResponse(normalizeError(error, requestId));
1957
+ }
1326
1958
  const result = await getAgentExecutionHandler({
1327
1959
  requestId,
1328
1960
  mastra,
1329
1961
  agentId,
1330
1962
  requestContext,
1331
1963
  method,
1332
- params: "params" in bodyParams ? bodyParams.params : void 0,
1964
+ params,
1333
1965
  taskStore,
1334
- abortSignal
1966
+ abortSignal,
1967
+ protocolVersion
1335
1968
  });
1336
1969
  if (method === "message/stream" || method === "tasks/resubscribe") return createA2ASSEResponse(result);
1337
1970
  return createA2AJsonResponse(result);
1338
1971
  }
1339
1972
  });
1340
1973
  //#endregion
1341
- 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 };
1974
+ export { getAgentExecutionHandler as a, handleListTaskPushNotificationConfig as c, handleSetTaskPushNotificationConfig as d, handleTaskCancel as f, resolveA2AProtocolVersion as g, handleTaskResubscribe as h, getAgentCardByIdHandler as i, handleMessageSend as l, handleTaskList 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
1975
 
1343
- //# sourceMappingURL=a2a-uPi5sPwb.js.map
1976
+ //# sourceMappingURL=a2a-_D2EA4oa.js.map