@artinet/sdk 0.6.0-preview.1 → 0.6.0-preview.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 (123) hide show
  1. package/README.md +8 -17
  2. package/dist/browser/client/a2a-client.js +4 -4
  3. package/dist/browser/config/index.d.ts +1 -1
  4. package/dist/browser/config/index.js +1 -1
  5. package/dist/browser/create/agentcard-builder.d.ts +47 -0
  6. package/dist/browser/create/agentcard-builder.js +65 -0
  7. package/dist/browser/create/base.d.ts +4 -0
  8. package/dist/browser/create/base.js +1 -0
  9. package/dist/browser/create/describe.d.ts +8 -0
  10. package/dist/browser/create/describe.js +8 -0
  11. package/dist/browser/create/message-builder.d.ts +78 -0
  12. package/dist/browser/create/message-builder.js +108 -0
  13. package/dist/browser/create/part-builder.d.ts +60 -0
  14. package/dist/browser/create/part-builder.js +81 -0
  15. package/dist/browser/create/task-builder.d.ts +251 -0
  16. package/dist/browser/create/task-builder.js +379 -0
  17. package/dist/browser/transport/rpc/parser.d.ts +1 -1
  18. package/dist/browser/transport/rpc/parser.js +2 -1
  19. package/dist/browser/types/a2a/a2a.d.ts +7 -3
  20. package/dist/browser/types/a2a/index.d.ts +3 -1
  21. package/dist/browser/types/a2a/index.js +0 -1
  22. package/dist/browser/utils/common/constants.d.ts +0 -5
  23. package/dist/browser/utils/common/constants.js +0 -27
  24. package/dist/browser/utils/common/errors.d.ts +57 -1
  25. package/dist/browser/utils/common/errors.js +68 -15
  26. package/dist/client/a2a-client.js +4 -4
  27. package/dist/config/default.d.ts +1 -1
  28. package/dist/config/default.js +1 -1
  29. package/dist/config/index.d.ts +1 -1
  30. package/dist/config/index.js +1 -1
  31. package/dist/create/agent-builder.d.ts +77 -0
  32. package/dist/create/agent-builder.js +20 -0
  33. package/dist/create/agentcard-builder.d.ts +47 -0
  34. package/dist/create/agentcard-builder.js +66 -0
  35. package/dist/create/base.d.ts +4 -0
  36. package/dist/create/base.js +1 -0
  37. package/dist/create/create.d.ts +762 -0
  38. package/dist/create/create.js +556 -0
  39. package/dist/create/describe.d.ts +8 -0
  40. package/dist/create/describe.js +8 -0
  41. package/dist/create/index.d.ts +4 -0
  42. package/dist/create/index.js +4 -0
  43. package/dist/create/message-builder.d.ts +78 -0
  44. package/dist/create/message-builder.js +110 -0
  45. package/dist/create/part-builder.d.ts +60 -0
  46. package/dist/create/part-builder.js +84 -0
  47. package/dist/create/status-builder.d.ts +30 -0
  48. package/dist/create/status-builder.js +46 -0
  49. package/dist/create/task-builder.d.ts +251 -0
  50. package/dist/create/task-builder.js +384 -0
  51. package/dist/create/transform.d.ts +16 -0
  52. package/dist/create/transform.js +106 -0
  53. package/dist/extensions/otel.d.ts +3 -0
  54. package/dist/extensions/otel.js +3 -0
  55. package/dist/extensions/pino.d.ts +3 -0
  56. package/dist/extensions/pino.js +3 -0
  57. package/dist/extensions/winston.d.ts +3 -0
  58. package/dist/extensions/winston.js +3 -0
  59. package/dist/index.d.ts +2 -0
  60. package/dist/index.js +2 -0
  61. package/dist/server/express/errors.js +4 -8
  62. package/dist/server/express/middeware.js +17 -38
  63. package/dist/server/express/server.d.ts +20 -1
  64. package/dist/server/express/server.js +44 -12
  65. package/dist/services/a2a/execute.d.ts +5 -0
  66. package/dist/services/a2a/execute.js +7 -0
  67. package/dist/services/a2a/factory/context.d.ts +1 -1
  68. package/dist/services/a2a/factory/context.js +2 -3
  69. package/dist/services/a2a/factory/handler.js +3 -3
  70. package/dist/services/a2a/factory/index.d.ts +0 -1
  71. package/dist/services/a2a/factory/index.js +0 -1
  72. package/dist/services/a2a/factory/service.d.ts +2 -2
  73. package/dist/services/a2a/factory/service.js +2 -2
  74. package/dist/services/a2a/factory/state-machine.d.ts +1 -1
  75. package/dist/services/a2a/factory/state-machine.js +30 -8
  76. package/dist/services/a2a/handlers/artifact.d.ts +2 -5
  77. package/dist/services/a2a/handlers/artifact.js +21 -32
  78. package/dist/services/a2a/handlers/cancel-task.js +7 -5
  79. package/dist/services/a2a/handlers/resubscribe-task.d.ts +10 -2
  80. package/dist/services/a2a/handlers/resubscribe-task.js +21 -18
  81. package/dist/services/a2a/handlers/send-message.js +6 -10
  82. package/dist/services/a2a/handlers/stream-message.d.ts +10 -2
  83. package/dist/services/a2a/handlers/stream-message.js +5 -1
  84. package/dist/services/a2a/handlers/update.js +15 -12
  85. package/dist/services/a2a/helpers/content.d.ts +5 -1
  86. package/dist/services/a2a/helpers/content.js +5 -1
  87. package/dist/services/a2a/helpers/index.d.ts +2 -2
  88. package/dist/services/a2a/helpers/index.js +2 -2
  89. package/dist/services/a2a/index.d.ts +1 -1
  90. package/dist/services/a2a/index.js +1 -1
  91. package/dist/services/a2a/managers.js +7 -1
  92. package/dist/services/a2a/service.d.ts +6 -2
  93. package/dist/services/a2a/service.js +54 -61
  94. package/dist/services/a2a/state-machine.d.ts +3 -3
  95. package/dist/services/a2a/state-machine.js +2 -0
  96. package/dist/transport/rpc/parser.d.ts +1 -1
  97. package/dist/transport/rpc/parser.js +2 -1
  98. package/dist/transport/trpc/a2a/routes/message/route.js +2 -1
  99. package/dist/transport/trpc/a2a/routes/tasks/route.js +2 -1
  100. package/dist/types/a2a/a2a.d.ts +7 -3
  101. package/dist/types/a2a/index.d.ts +3 -1
  102. package/dist/types/a2a/index.js +0 -1
  103. package/dist/utils/common/constants.d.ts +0 -5
  104. package/dist/utils/common/constants.js +0 -27
  105. package/dist/utils/common/errors.d.ts +57 -1
  106. package/dist/utils/common/errors.js +68 -15
  107. package/dist/utils/common/parse.d.ts +1 -1
  108. package/dist/utils/common/schema-validation.d.ts +1 -1
  109. package/dist/utils/index.d.ts +0 -1
  110. package/dist/utils/index.js +0 -1
  111. package/package.json +8 -5
  112. package/dist/browser/services/a2a/helpers/message-builder.d.ts +0 -17
  113. package/dist/browser/services/a2a/helpers/message-builder.js +0 -66
  114. package/dist/browser/types/a2a/builder.d.ts +0 -43
  115. package/dist/browser/types/a2a/builder.js +0 -5
  116. package/dist/services/a2a/factory/builder.d.ts +0 -292
  117. package/dist/services/a2a/factory/builder.js +0 -370
  118. package/dist/services/a2a/helpers/agentcard-builder.d.ts +0 -11
  119. package/dist/services/a2a/helpers/agentcard-builder.js +0 -27
  120. package/dist/services/a2a/helpers/message-builder.d.ts +0 -17
  121. package/dist/services/a2a/helpers/message-builder.js +0 -66
  122. package/dist/types/a2a/builder.d.ts +0 -43
  123. package/dist/types/a2a/builder.js +0 -5
@@ -2,9 +2,9 @@
2
2
  * Copyright 2025 The Artinet Project
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
- import { A2A } from "../../types/index.js";
6
- import { PUSH_NOTIFICATION_NOT_SUPPORTED, INVALID_REQUEST, INVALID_PARAMS, METHOD_NOT_FOUND, AUTHENTICATED_EXTENDED_CARD_NOT_CONFIGURED, } from "../../utils/index.js";
7
5
  import { logger } from "../../config/index.js";
6
+ import { formatJson } from "../../utils/common/utils.js";
7
+ import { A2AError } from "@a2a-js/sdk/server";
8
8
  const isValidMethod = (method) => {
9
9
  return (method &&
10
10
  method !== "" &&
@@ -14,41 +14,35 @@ const isValidMethod = (method) => {
14
14
  };
15
15
  const checkParams = (params, method) => {
16
16
  if (!params || (typeof params !== "object" && !Array.isArray(params))) {
17
- throw INVALID_PARAMS({
18
- data: {
19
- message: "Invalid params",
20
- method,
21
- },
17
+ throw A2AError.invalidParams("Invalid Parameters", {
18
+ params,
19
+ method,
22
20
  });
23
21
  }
24
22
  else if (typeof params === "object" && Object.keys(params).length === 0) {
25
- throw INVALID_PARAMS({
26
- data: {
27
- message: "No params provided",
28
- method,
29
- },
23
+ throw A2AError.invalidParams("Params Required", {
24
+ params,
25
+ method,
30
26
  });
31
27
  }
32
28
  };
33
29
  export async function jsonRPCMiddleware(service, req, res, next, extendedAgentCard) {
34
30
  const { method, params, id, jsonrpc } = req.body;
35
31
  if (jsonrpc !== "2.0" ||
36
- (id && typeof id !== "string" && typeof id !== "number" && id !== null)) {
32
+ (typeof id !== "string" &&
33
+ typeof id === "number" &&
34
+ !Number.isInteger(id) &&
35
+ id !== null)) {
37
36
  res.json({
38
37
  jsonrpc: "2.0",
39
38
  id: id || null,
40
- error: { code: A2A.ErrorCodeInvalidRequest, message: "Invalid Request" },
39
+ error: A2AError.invalidRequest(`Invalid JSONRPC info: ${formatJson({ method, params, id, jsonrpc })}`).toJSONRPCError(),
41
40
  });
42
41
  return;
43
42
  }
44
43
  try {
45
44
  if (!isValidMethod(method)) {
46
- throw INVALID_REQUEST({
47
- data: {
48
- message: "No method provided",
49
- method,
50
- },
51
- });
45
+ throw A2AError.invalidRequest("No method provided");
52
46
  }
53
47
  let result;
54
48
  switch (method) {
@@ -101,34 +95,19 @@ export async function jsonRPCMiddleware(service, req, res, next, extendedAgentCa
101
95
  case "tasks/pushNotificationConfig/get":
102
96
  case "tasks/pushNotificationConfig/delete":
103
97
  case "task/pushNotificationConfig/list": {
104
- throw PUSH_NOTIFICATION_NOT_SUPPORTED({
105
- data: {
106
- message: "Push notifications not supported",
107
- method,
108
- },
109
- });
98
+ throw A2AError.pushNotificationNotSupported();
110
99
  }
111
100
  case "agent/getAuthenticatedExtendedCard": {
112
101
  if (!extendedAgentCard ||
113
102
  (await service.getAgentCard()).supportsAuthenticatedExtendedCard !==
114
103
  true) {
115
- throw AUTHENTICATED_EXTENDED_CARD_NOT_CONFIGURED({
116
- data: {
117
- message: "Authenticated Extended Card is not configured",
118
- method,
119
- },
120
- });
104
+ throw A2AError.authenticatedExtendedCardNotConfigured();
121
105
  }
122
106
  result = extendedAgentCard;
123
107
  break;
124
108
  }
125
109
  default:
126
- throw METHOD_NOT_FOUND({
127
- data: {
128
- message: "Method not found",
129
- method,
130
- },
131
- });
110
+ throw A2AError.methodNotFound(method);
132
111
  }
133
112
  res.json({ jsonrpc: "2.0", id, result });
134
113
  }
@@ -11,14 +11,33 @@ export interface ServerParams {
11
11
  app?: express.Express;
12
12
  corsOptions?: CorsOptions;
13
13
  basePath?: string;
14
+ port?: number;
15
+ /**
16
+ * Your agentCard must have supportsAuthenticatedExtendedCard set to true
17
+ */
14
18
  extendedAgentCard?: A2A.AgentCard;
15
19
  agent: Agent | CreateAgentParams;
16
20
  agentCardPath?: string;
17
21
  register?: boolean;
18
22
  }
19
23
  export declare function rpcParser(req: express.Request, res: express.Response, next: express.NextFunction): void;
20
- export declare function createAgentServer({ app, basePath, agentCardPath, agent, corsOptions, extendedAgentCard, }: ServerParams): {
24
+ /**
25
+ * @note Best used with the `cr8` builder.
26
+ * @param {ServerParams} params - The server parameters
27
+ * @returns {ExpressAgentServer} - The express agent server
28
+ * @example
29
+ * ```typescript
30
+ * const { app, agent, start } = createAgentServer({
31
+ * agent: cr8("MyAgent")
32
+ * .text("Hello, world!")
33
+ * .agent,
34
+ * basePath: "/a2a",
35
+ * });
36
+ * ```
37
+ */
38
+ export declare function createAgentServer({ app, basePath, agentCardPath, agent, corsOptions, extendedAgentCard, register, port, }: ServerParams): {
21
39
  app: express.Express;
22
40
  agent: Agent;
41
+ start: (_port?: number) => import("node:http").Server<typeof import("node:http").IncomingMessage, typeof import("node:http").ServerResponse>;
23
42
  };
24
43
  export type ExpressAgentServer = ReturnType<typeof createAgentServer>;
@@ -3,26 +3,24 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import express from "express";
6
- import { INVALID_REQUEST, PARSE_ERROR } from "../../utils/index.js";
7
6
  import { createAgent, Service, } from "../../services/index.js";
8
7
  import cors from "cors";
9
8
  import { jsonRPCMiddleware } from "./middeware.js";
10
9
  import { errorHandler } from "./errors.js";
10
+ import { logger } from "../../config/index.js";
11
+ import { A2AError } from "@a2a-js/sdk/server";
12
+ import { formatJson } from "../../utils/common/utils.js";
11
13
  export function rpcParser(req, res, next) {
12
14
  express.json()(req, res, (err) => {
13
15
  if (!req.body || typeof req.body !== "object") {
14
- return next(PARSE_ERROR({
15
- data: { message: "Invalid request body" },
16
- }));
16
+ return next(A2AError.parseError(`Invalid request body: ${formatJson(req.body)}`));
17
17
  }
18
18
  if (err) {
19
19
  if (err instanceof SyntaxError &&
20
20
  "status" in err &&
21
21
  err.status === 400 &&
22
22
  "body" in err) {
23
- return next(PARSE_ERROR({
24
- data: err.message,
25
- }));
23
+ return next(A2AError.parseError(`Invalid request body: ${formatJson(req.body)}`));
26
24
  }
27
25
  return next(err);
28
26
  }
@@ -46,7 +44,25 @@ const ensureAgent = (agentOrParams) => {
46
44
  }
47
45
  throw new Error("invalid agent or params");
48
46
  };
49
- export function createAgentServer({ app = express(), basePath = "/", agentCardPath = "/.well-known/agent-card.json", agent, corsOptions, extendedAgentCard, }) {
47
+ const registerAgent = async (agentCard) => {
48
+ logger.debug("registerAgent: not implemented", { agentCard });
49
+ return Promise.resolve(agentCard);
50
+ };
51
+ /**
52
+ * @note Best used with the `cr8` builder.
53
+ * @param {ServerParams} params - The server parameters
54
+ * @returns {ExpressAgentServer} - The express agent server
55
+ * @example
56
+ * ```typescript
57
+ * const { app, agent, start } = createAgentServer({
58
+ * agent: cr8("MyAgent")
59
+ * .text("Hello, world!")
60
+ * .agent,
61
+ * basePath: "/a2a",
62
+ * });
63
+ * ```
64
+ */
65
+ export function createAgentServer({ app = express(), basePath = "/", agentCardPath = "/.well-known/agent-card.json", agent, corsOptions, extendedAgentCard, register = false, port, }) {
50
66
  const agentInstance = ensureAgent(agent);
51
67
  app.use(cors(corsOptions));
52
68
  if (agentCardPath !== "/.well-known/agent-card.json") {
@@ -72,10 +88,10 @@ export function createAgentServer({ app = express(), basePath = "/", agentCardPa
72
88
  //a standard express middleware to handle json-rpc requests
73
89
  app.post(basePath, rpcParser, async (req, res, next) => {
74
90
  const { jsonrpc } = req.body;
75
- if (jsonrpc === "2.0") {
76
- return await jsonRPCMiddleware(agentInstance, req, res, next, extendedAgentCard);
91
+ if (jsonrpc !== "2.0") {
92
+ return next(A2AError.invalidRequest("Invalid JSON-RPC request"));
77
93
  }
78
- next(INVALID_REQUEST({ data: { message: "Invalid JSON-RPC request" } }));
94
+ return await jsonRPCMiddleware(agentInstance, req, res, next, extendedAgentCard);
79
95
  });
80
96
  app.use(errorHandler);
81
97
  /** this is an example of using trpc as express middleware
@@ -94,5 +110,21 @@ export function createAgentServer({ app = express(), basePath = "/", agentCardPa
94
110
  );
95
111
  * we could also use trpc directly or any other transport layer
96
112
  */
97
- return { app, agent: agentInstance };
113
+ const start = (_port) => {
114
+ try {
115
+ const listenPort = _port ?? port;
116
+ const server = app.listen(listenPort, () => {
117
+ logger.info(`Agent server started on port ${listenPort}`);
118
+ });
119
+ if (register) {
120
+ registerAgent(agentInstance.agentCard);
121
+ }
122
+ return server;
123
+ }
124
+ catch (error) {
125
+ logger.error(`Failed to start agent server`, error);
126
+ throw error;
127
+ }
128
+ };
129
+ return { app, agent: agentInstance, start };
98
130
  }
@@ -3,4 +3,9 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { A2A } from "../../types/index.js";
6
+ /**
7
+ * Our universal executor for {@link A2A.Engine}.
8
+ * @param engine - {@link A2A.Engine} to execute.
9
+ * @param context - {@link A2A.Context} provided to the engine.
10
+ */
6
11
  export declare const execute: (engine: A2A.Engine, context: A2A.Context) => Promise<void>;
@@ -2,6 +2,11 @@
2
2
  * Copyright 2025 The Artinet Project
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
+ /**
6
+ * Our universal executor for {@link A2A.Engine}.
7
+ * @param engine - {@link A2A.Engine} to execute.
8
+ * @param context - {@link A2A.Context} provided to the engine.
9
+ */
5
10
  export const execute = async (engine, context) => {
6
11
  try {
7
12
  if (context.publisher.onStart) {
@@ -16,7 +21,9 @@ export const execute = async (engine, context) => {
16
21
  }
17
22
  }
18
23
  catch (error) {
24
+ /* onError triggers completion internally */
19
25
  await context.publisher.onError(error);
26
+ /* rethrow the error to be handled by the caller */
20
27
  throw error;
21
28
  }
22
29
  finally {
@@ -6,7 +6,7 @@ import { A2A } from "../../../types/index.js";
6
6
  export declare function createBaseContext({ contextId, service, task, overrides, abortSignal, }: {
7
7
  contextId: string;
8
8
  service: A2A.Service;
9
- task?: A2A.Task;
9
+ task: A2A.Task;
10
10
  overrides?: Partial<Omit<A2A.EventConsumer, "contextId">>;
11
11
  abortSignal?: AbortSignal;
12
12
  }): A2A.BaseContext;
@@ -17,8 +17,7 @@
17
17
  import { createStateMachine } from "./state-machine.js";
18
18
  import { v4 as uuidv4 } from "uuid";
19
19
  export function createBaseContext({ contextId = uuidv4(), service, task, overrides, abortSignal = new AbortController().signal, }) {
20
- const isCancelled = async () => (await service.cancellations.has(task?.id ?? contextId)) ||
21
- abortSignal.aborted;
20
+ const isCancelled = async () => (await service.cancellations.has(task.id)) || abortSignal.aborted;
22
21
  const getState = async (args) => args ? await service.tasks.get(args) : task;
23
22
  const context = {
24
23
  contextId: contextId,
@@ -34,7 +33,7 @@ export function createContext({ baseContext, taskId, messenger, extensions, refe
34
33
  const getTask = async () => baseContext.publisher.currentTask;
35
34
  const context = {
36
35
  ...baseContext,
37
- taskId: baseContext.publisher.currentTask.id ?? taskId,
36
+ taskId,
38
37
  userMessage: messenger.message,
39
38
  messages: messenger,
40
39
  getTask,
@@ -2,13 +2,13 @@
2
2
  * Copyright 2025 The Artinet Project
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
- import { getTask, cancelTask, sendMessage, streamMessage, resubscribe, } from "../handlers/index.js";
5
+ import { getTask, cancelTask, sendMessage, sendMessageStream, subscribeToTask, } from "../handlers/index.js";
6
6
  export function createHandler(methods) {
7
7
  return {
8
8
  getTask: methods?.getTask ?? getTask,
9
9
  cancelTask: methods?.cancelTask ?? cancelTask,
10
10
  sendMessage: methods?.sendMessage ?? sendMessage,
11
- streamMessage: methods?.streamMessage ?? streamMessage,
12
- resubscribe: methods?.resubscribe ?? resubscribe,
11
+ streamMessage: methods?.streamMessage ?? sendMessageStream,
12
+ resubscribe: methods?.resubscribe ?? subscribeToTask,
13
13
  };
14
14
  }
@@ -1,4 +1,3 @@
1
1
  export * from "./context.js";
2
- export * from "./builder.js";
3
2
  export * from "./state-machine.js";
4
3
  export * from "./service.js";
@@ -1,4 +1,3 @@
1
1
  export * from "./context.js";
2
- export * from "./builder.js";
3
2
  export * from "./state-machine.js";
4
3
  export * from "./service.js";
@@ -1,8 +1,8 @@
1
+ import * as describe from "../../../create/agentcard-builder.js";
1
2
  import { A2A } from "../../../types/index.js";
2
3
  import { Service } from "../service.js";
3
- export type AgentCardParams = (Partial<A2A.AgentCard> & Required<Pick<A2A.AgentCard, "name">>) | string;
4
4
  export interface ServiceParams {
5
- agentCard: AgentCardParams;
5
+ agentCard: describe.AgentCardParams;
6
6
  engine: A2A.Engine;
7
7
  contexts?: A2A.Contexts;
8
8
  streams?: A2A.Streams;
@@ -3,10 +3,10 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { createHandler } from "./handler.js";
6
- import { createAgentCard } from "../helpers/agentcard-builder.js";
6
+ import * as describe from "../../../create/agentcard-builder.js";
7
7
  import { Service } from "../service.js";
8
8
  import { Contexts, Streams, Connections, Cancellations, Tasks, } from "../managers.js";
9
9
  export function createService(params) {
10
- return new Service(createAgentCard(params.agentCard), params.engine, params.connections ?? new Connections(), params.cancellations ?? new Cancellations(), params.tasks ?? new Tasks(), params.contexts ?? new Contexts(), params.streams ?? new Streams(), createHandler(params.methods), params.overrides);
10
+ return new Service(describe.card(params.agentCard), params.engine, params.connections ?? new Connections(), params.cancellations ?? new Cancellations(), params.tasks ?? new Tasks(), params.contexts ?? new Contexts(), params.streams ?? new Streams(), createHandler(params.methods), params.overrides);
11
11
  }
12
12
  export const createAgent = createService;
@@ -6,6 +6,6 @@ import { A2A } from "../../../types/index.js";
6
6
  export declare function createStateMachine({ contextId, service, task: currentTask, overrides, }: {
7
7
  contextId: string;
8
8
  service: A2A.Service;
9
- task?: A2A.Task;
9
+ task: A2A.Task;
10
10
  overrides?: Partial<Omit<A2A.EventConsumer, "contextId">>;
11
11
  }): A2A.EventPublisher;
@@ -4,7 +4,8 @@
4
4
  */
5
5
  import { StateMachine } from "../state-machine.js";
6
6
  import { logger } from "../../../config/index.js";
7
- import { TASK_NOT_FOUND, CANCEL_UPDATE, FAILED_UPDATE } from "../../../utils/index.js";
7
+ import * as describe from "../../../create/describe.js";
8
+ import { TASK_NOT_FOUND } from "../../../utils/index.js";
8
9
  import assert from "assert";
9
10
  export function createStateMachine({ contextId, service, task: currentTask, overrides, }) {
10
11
  const handler = {
@@ -25,7 +26,11 @@ export function createStateMachine({ contextId, service, task: currentTask, over
25
26
  logger.info(`onCancel[ctx:${contextId}]:`, "cancellation triggered");
26
27
  logger.debug(`onCancel[ctx:${contextId}]:`, "arguments", update, task);
27
28
  await service.cancellations.set(task.id);
28
- const cancellation = CANCEL_UPDATE(task.id, task.contextId, update.status?.message);
29
+ const cancellation = describe.update.canceled({
30
+ taskId: task.id,
31
+ contextId: task.contextId,
32
+ message: update.status?.message,
33
+ });
29
34
  await service.tasks.update((await service.contexts.get(contextId)), cancellation);
30
35
  },
31
36
  onUpdate: async (update, task) => {
@@ -43,17 +48,34 @@ export function createStateMachine({ contextId, service, task: currentTask, over
43
48
  logger.error(`onError[ctx:${contextId}]:`, new Error("task not found"));
44
49
  return;
45
50
  }
46
- const errorUpdate = FAILED_UPDATE(task.id, contextId, undefined, error instanceof Error ? error.message : String(error));
47
- await service.tasks
48
- .update((await service.contexts.get(contextId)), errorUpdate)
49
- .catch((error) => {
51
+ const errorUpdate = describe.update.failed({
52
+ taskId: task.id,
53
+ contextId,
54
+ message: describe.message({
55
+ messageId: "failed-update",
56
+ parts: [
57
+ {
58
+ kind: "text",
59
+ text: error instanceof Error ? error.message : String(error),
60
+ },
61
+ ],
62
+ }),
63
+ });
64
+ const context = await service.contexts.get(contextId);
65
+ if (!context) {
66
+ logger.error(`onError[ctx:${contextId}]:`, new Error("context not found"));
67
+ return;
68
+ }
69
+ await service.tasks.update(context, errorUpdate).catch((error) => {
50
70
  //we capture errors thrown during error handling to ensure we trigger completion gracefully
51
- logger.error(`onError[ctx:${contextId}]:`, error);
71
+ logger.error(`onError: task update error[ctx:${contextId}]:`, error);
52
72
  });
73
+ // we trigger completion here to ensure the context is cleaned up
74
+ await context.publisher.onComplete();
53
75
  },
54
76
  onComplete: async (task) => {
55
77
  assert(task.contextId === contextId, "context mismatch");
56
- logger.info(`onComplete[ctx:${contextId}]:`, { taskId: task.id });
78
+ logger.info(`onComplete[ctx:${contextId}]: `, { taskId: task.id });
57
79
  await service.cancellations.delete(task.id);
58
80
  await service.connections.delete(task.contextId);
59
81
  await service.contexts.delete(task.contextId);
@@ -3,8 +3,5 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { A2A } from "../../../types/index.js";
6
- export declare function updateByIndex(append: boolean, artifacts: A2A.Artifact[], index: number, artifactUpdate: A2A.Artifact): {
7
- artifacts: A2A.Artifact[];
8
- replaced: boolean;
9
- };
10
- export declare function processArtifactUpdate(append: boolean, artifacts: A2A.Artifact[], artifactUpdate: A2A.Artifact): A2A.Artifact[];
6
+ export declare function updateArtifact(_artifact: A2A.Artifact, update: A2A.TaskArtifactUpdateEvent): A2A.Artifact;
7
+ export declare function upsertArtifact(artifacts: A2A.Artifact[], update: A2A.TaskArtifactUpdateEvent): A2A.Artifact[];
@@ -2,38 +2,27 @@
2
2
  * Copyright 2025 The Artinet Project
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
- export function updateByIndex(append, artifacts, index, artifactUpdate) {
6
- if (append) {
7
- const existingArtifact = artifacts[index];
8
- existingArtifact.parts.push(...artifactUpdate.parts);
9
- if (artifactUpdate.metadata) {
10
- existingArtifact.metadata = {
11
- ...(existingArtifact.metadata || {}),
12
- ...artifactUpdate.metadata,
13
- };
14
- }
15
- if (artifactUpdate.description) {
16
- existingArtifact.description = artifactUpdate.description;
17
- }
18
- if (artifactUpdate.name) {
19
- existingArtifact.name = artifactUpdate.name;
20
- }
21
- artifacts[index] = existingArtifact;
22
- }
23
- else {
24
- artifacts[index] = { ...artifactUpdate };
25
- }
26
- return { artifacts, replaced: true };
5
+ export function updateArtifact(_artifact, update) {
6
+ if (!update.append)
7
+ return update.artifact;
8
+ const artifactUpdate = update.artifact;
9
+ const artifact = _artifact;
10
+ artifact.metadata = {
11
+ ...(artifact.metadata || {}),
12
+ ...artifactUpdate.metadata,
13
+ };
14
+ artifact.description = artifactUpdate.description;
15
+ artifact.name = artifactUpdate.name;
16
+ artifact.parts.push(...artifactUpdate.parts);
17
+ return artifact;
27
18
  }
28
- export function processArtifactUpdate(append, artifacts, artifactUpdate) {
29
- const existingIndex = artifacts.findIndex((a) => a.artifactId === artifactUpdate.artifactId);
30
- let replaced = false;
31
- let newArtifacts = artifacts;
32
- if (existingIndex !== -1) {
33
- ({ artifacts: newArtifacts, replaced } = updateByIndex(append, artifacts, existingIndex, artifactUpdate));
34
- }
35
- if (!replaced) {
36
- newArtifacts.push({ ...artifactUpdate });
19
+ export function upsertArtifact(artifacts, update) {
20
+ const updateId = update.artifact.artifactId;
21
+ const index = artifacts.findIndex((a) => a.artifactId === updateId);
22
+ if (index === -1) {
23
+ artifacts.push({ ...update.artifact });
24
+ return artifacts;
37
25
  }
38
- return newArtifacts;
26
+ artifacts[index] = updateArtifact(artifacts[index], update);
27
+ return artifacts;
39
28
  }
@@ -16,7 +16,13 @@ export const cancelTask = async ({ id: taskId }, context) => {
16
16
  if (FINAL_STATES.includes(task.status.state)) {
17
17
  throw TASK_NOT_CANCELABLE("Task is in a final state: " + task.status.state);
18
18
  }
19
- service.cancellations.set(taskId);
19
+ /**
20
+ * By triggering onCancel, we're guaranteed that:
21
+ * - No further updates will be processed other than errors
22
+ * - The task will be cancelled
23
+ * - The task will be completed
24
+ * - The cancellations will be cleaned up
25
+ */
20
26
  const cancelledTask = {
21
27
  ...task,
22
28
  status: {
@@ -24,10 +30,6 @@ export const cancelTask = async ({ id: taskId }, context) => {
24
30
  state: A2A.TaskState.canceled,
25
31
  },
26
32
  };
27
- context.publisher?.on("complete", async () => {
28
- await service.cancellations.delete(taskId);
29
- await service.contexts.delete(context.contextId);
30
- });
31
33
  await context.publisher.onCancel(cancelledTask);
32
34
  return cancelledTask;
33
35
  };
@@ -3,9 +3,17 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { A2A } from "../../../types/index.js";
6
- export declare const resubscribe: A2A.RequestHandler["resubscribe"];
6
+ export declare const subscribeToTask: A2A.RequestHandler["resubscribe"];
7
+ export type SubscribeToTaskHandler = typeof subscribeToTask;
8
+ /**
9
+ * @deprecated Use SubscribeToTaskHandler instead
10
+ */
11
+ export declare const resubscribe: (input: A2A.TaskIdParams, context?: A2A.Context, options?: A2A.ServiceOptions) => AsyncGenerator<A2A.Update>;
12
+ /**
13
+ * @deprecated Use SubscribeToTaskHandler instead
14
+ */
7
15
  export type TaskResubscribeHandler = typeof resubscribe;
8
16
  /**
9
- * @deprecated Use ResubscribeTaskHandler instead
17
+ * @deprecated Use SubscribeToTaskHandler instead
10
18
  */
11
19
  export type ResubscribeTaskMethod = TaskResubscribeHandler;
@@ -5,22 +5,22 @@
5
5
  import { A2A } from "../../../types/index.js";
6
6
  import { FINAL_STATES, INTERNAL_ERROR, TASK_NOT_FOUND } from "../../../utils/index.js";
7
7
  const flush = (task, stream) => {
8
- if (task.artifacts && task.artifacts.length > 0) {
9
- for (const artifact of task.artifacts) {
10
- const artifactUpdate = {
11
- kind: A2A.Kind["artifact-update"],
12
- taskId: task.id,
13
- contextId: task.contextId,
14
- artifact,
15
- lastChunk: task.artifacts.length === 1,
16
- metadata: task.metadata,
17
- };
18
- stream.updates.push(artifactUpdate);
19
- task.artifacts.shift();
20
- }
8
+ if (!task.artifacts || task.artifacts.length === 0)
9
+ return;
10
+ for (const artifact of task.artifacts) {
11
+ const artifactUpdate = {
12
+ kind: A2A.Kind["artifact-update"],
13
+ taskId: task.id,
14
+ contextId: task.contextId,
15
+ artifact,
16
+ lastChunk: task.artifacts.length === 1,
17
+ metadata: task.metadata,
18
+ };
19
+ stream.updates.push(artifactUpdate);
20
+ task.artifacts.shift();
21
21
  }
22
22
  };
23
- export const resubscribe = async function* ({ id: taskId }, context) {
23
+ export const subscribeToTask = async function* ({ id: taskId }, context) {
24
24
  if (!context) {
25
25
  throw INTERNAL_ERROR({ error: { message: "Context is required" } });
26
26
  }
@@ -33,9 +33,7 @@ export const resubscribe = async function* ({ id: taskId }, context) {
33
33
  contextId: context.contextId,
34
34
  context,
35
35
  }));
36
- // since we already checked if the task exists
37
- // then we dont care about the upsert in onStart
38
- // so we can just push the update and continue
36
+ // onStart no longer inserts a new task, so we can safely push the update and continue
39
37
  context.publisher.on("start", async (_, task) => {
40
38
  stream.updates.push({
41
39
  kind: "status-update",
@@ -46,7 +44,8 @@ export const resubscribe = async function* ({ id: taskId }, context) {
46
44
  metadata: task.metadata,
47
45
  });
48
46
  if (FINAL_STATES.includes(task.status.state)) {
49
- flush(task, stream);
47
+ /* We create a new task object to avoid mutating the original task */
48
+ flush({ ...task }, stream);
50
49
  await stream.kill();
51
50
  }
52
51
  });
@@ -54,3 +53,7 @@ export const resubscribe = async function* ({ id: taskId }, context) {
54
53
  service: service,
55
54
  });
56
55
  };
56
+ /**
57
+ * @deprecated Use SubscribeToTaskHandler instead
58
+ */
59
+ export const resubscribe = subscribeToTask;
@@ -8,15 +8,10 @@ export const sendMessage = async ({ configuration }, context) => {
8
8
  if (!context) {
9
9
  throw INTERNAL_ERROR({ error: { message: "Context is required" } });
10
10
  }
11
- context.publisher.on("complete", () => {
12
- context.service.contexts.delete(context.contextId);
13
- });
14
- context.publisher.on("error", () => {
15
- context.publisher.onComplete();
16
- });
17
11
  const service = context.service;
12
+ let task;
18
13
  if (configuration?.blocking === false) {
19
- const result = await Promise.race([
14
+ task = await Promise.race([
20
15
  service.execute({ engine: service.engine, context }).then(async () => {
21
16
  return await context.getTask();
22
17
  }),
@@ -26,10 +21,11 @@ export const sendMessage = async ({ configuration }, context) => {
26
21
  });
27
22
  }),
28
23
  ]);
29
- return result;
30
24
  }
31
- await service.execute({ engine: service.engine, context });
32
- const task = await context.getTask();
25
+ else {
26
+ await service.execute({ engine: service.engine, context });
27
+ task = await context.getTask();
28
+ }
33
29
  task.history = getLatestHistory(task, configuration?.historyLength);
34
30
  return task;
35
31
  };