@artinet/sdk 0.6.10 → 0.6.12

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.
@@ -7,20 +7,16 @@
7
7
  */
8
8
  import { SystemError } from "../../utils/index.js";
9
9
  import { logger } from "../../config/index.js";
10
- import escapeHtml from "escape-html";
11
- import { A2AError } from "@a2a-js/sdk/server";
12
- import { formatJson } from "../../utils/index.js";
13
- import express from "express";
10
+ import { A2AError } from '@a2a-js/sdk/server';
11
+ import { formatJson, sanitizeString } from "../../utils/index.js";
12
+ import express from 'express';
14
13
  export function rpcParser(req, res, next) {
15
14
  express.json()(req, res, (err) => {
16
- if (!req.body || typeof req.body !== "object") {
15
+ if (!req.body || typeof req.body !== 'object') {
17
16
  return next(A2AError.parseError(`Invalid request body: ${formatJson(req.body)}`));
18
17
  }
19
18
  if (err) {
20
- if (err instanceof SyntaxError &&
21
- "status" in err &&
22
- err.status === 400 &&
23
- "body" in err) {
19
+ if (err instanceof SyntaxError && 'status' in err && err.status === 400 && 'body' in err) {
24
20
  return next(A2AError.parseError(`Invalid request body: ${formatJson(req.body)}`));
25
21
  }
26
22
  return next(err);
@@ -36,15 +32,15 @@ export const errorHandler = (err, req, res, _) => {
36
32
  if (res.headersSent) {
37
33
  headersSent = true;
38
34
  }
39
- logger.error("errorHandler", err);
35
+ logger.error('errorHandler', err);
40
36
  let reqId = null;
41
37
  try {
42
- if (req.body && typeof req.body === "object" && "id" in req.body) {
38
+ if (req.body && typeof req.body === 'object' && 'id' in req.body) {
43
39
  reqId = req.body.id;
44
40
  }
45
41
  }
46
42
  catch (e) {
47
- logger.error("errorHandler: Error extracting request ID", e);
43
+ logger.error('errorHandler: Error extracting request ID', e);
48
44
  }
49
45
  let jsonRpcError;
50
46
  if (err instanceof A2AError || err instanceof SystemError) {
@@ -54,8 +50,8 @@ export const errorHandler = (err, req, res, _) => {
54
50
  jsonRpcError = A2AError.internalError(err.message, err.data).toJSONRPCError();
55
51
  }
56
52
  const errorResponse = {
57
- jsonrpc: "2.0",
58
- id: escapeHtml(reqId),
53
+ jsonrpc: '2.0',
54
+ id: sanitizeString(reqId),
59
55
  error: jsonRpcError,
60
56
  };
61
57
  if (!headersSent) {
@@ -7,5 +7,5 @@ export declare function createStateMachine({ contextId, service, task: currentTa
7
7
  contextId: string;
8
8
  service: A2A.Service;
9
9
  task: A2A.Task;
10
- overrides?: Partial<Omit<A2A.EventConsumer, "contextId">>;
10
+ overrides?: Partial<Omit<A2A.EventConsumer, 'contextId'>>;
11
11
  }): A2A.EventPublisher;
@@ -7,12 +7,12 @@ import { logger } from "../../../config/index.js";
7
7
  import * as describe from "../../../create/describe.js";
8
8
  import { TASK_NOT_FOUND } from "../../../utils/errors.js";
9
9
  import { formatJson } from "../../../utils/utils.js";
10
- import assert from "assert";
10
+ // import assert from "assert";
11
11
  export function createStateMachine({ contextId, service, task: currentTask, overrides, }) {
12
12
  const handler = {
13
13
  contextId: contextId,
14
14
  onStart: async (context) => {
15
- assert(context.contextId === contextId, "context mismatch");
15
+ // assert(context.contextId === contextId, 'context mismatch');
16
16
  logger.info(`onStart[ctx:${contextId}]:`, { taskId: context.taskId });
17
17
  await service.connections.set(context.contextId);
18
18
  const task = await service.tasks.get(context.taskId);
@@ -24,8 +24,8 @@ export function createStateMachine({ contextId, service, task: currentTask, over
24
24
  return task;
25
25
  },
26
26
  onCancel: async (update, task) => {
27
- logger.info(`onCancel[ctx:${contextId}]:`, "cancellation triggered");
28
- logger.debug(`onCancel[ctx:${contextId}]:`, "arguments", update, task);
27
+ logger.info(`onCancel[ctx:${contextId}]:`, 'cancellation triggered');
28
+ logger.debug(`onCancel[ctx:${contextId}]:`, 'arguments', update, task);
29
29
  await service.cancellations.set(task.id);
30
30
  const cancellation = describe.update.canceled({
31
31
  taskId: task.id,
@@ -34,13 +34,13 @@ export function createStateMachine({ contextId, service, task: currentTask, over
34
34
  });
35
35
  /**We've intentionally blocked further updates, so the first cancellation update is responsible for updating stored task state and notifying listeners*/
36
36
  const updatedTask = await service.tasks.update((await service.contexts.get(contextId)), cancellation);
37
- (await service.contexts.get(contextId))?.publisher.emit("update", updatedTask, cancellation);
37
+ (await service.contexts.get(contextId))?.publisher.emit('update', updatedTask, cancellation);
38
38
  },
39
39
  onUpdate: async (update, task) => {
40
40
  logger.info(`onUpdate[ctx:${contextId}]:`);
41
41
  logger.debug(`onUpdate[ctx:${contextId}]:`, { taskId: task.id });
42
42
  if (await service.cancellations.has(task.id)) {
43
- logger.warn(`onUpdate[ctx:${contextId}]:`, { taskId: task.id }, "task is cancelled, no longer processing updates");
43
+ logger.warn(`onUpdate[ctx:${contextId}]:`, { taskId: task.id }, 'task is cancelled, no longer processing updates');
44
44
  return task;
45
45
  }
46
46
  return await service.tasks.update((await service.contexts.get(contextId)), update);
@@ -48,7 +48,7 @@ export function createStateMachine({ contextId, service, task: currentTask, over
48
48
  onError: async (error, task) => {
49
49
  logger.error(`onError[ctx:${contextId}]:`, error);
50
50
  if (!task) {
51
- logger.error(`onError[ctx:${contextId}]:`, new Error("task not found"));
51
+ logger.error(`onError[ctx:${contextId}]:`, new Error('task not found'));
52
52
  return;
53
53
  }
54
54
  const errorUpdate = describe.update.failed({
@@ -58,7 +58,7 @@ export function createStateMachine({ contextId, service, task: currentTask, over
58
58
  messageId: `failed:${task.id}`,
59
59
  parts: [
60
60
  {
61
- kind: "text",
61
+ kind: 'text',
62
62
  text: error instanceof Error ? error.message : formatJson(error),
63
63
  },
64
64
  ],
@@ -66,7 +66,7 @@ export function createStateMachine({ contextId, service, task: currentTask, over
66
66
  });
67
67
  const context = await service.contexts.get(contextId);
68
68
  if (!context) {
69
- logger.error(`onError[ctx:${contextId}]:`, new Error("context not found"));
69
+ logger.error(`onError[ctx:${contextId}]:`, new Error('context not found'));
70
70
  return;
71
71
  }
72
72
  /**triggering onUpdate here with a catch instead of a raw tasks.update call*/
@@ -77,7 +77,7 @@ export function createStateMachine({ contextId, service, task: currentTask, over
77
77
  await context.publisher.onComplete();
78
78
  },
79
79
  onComplete: async (task) => {
80
- assert(task.contextId === contextId, "context mismatch");
80
+ // assert(task.contextId === contextId, 'context mismatch');
81
81
  logger.info(`onComplete[ctx:${contextId}]: `, { taskId: task.id });
82
82
  await service.cancellations.delete(task.id);
83
83
  await service.connections.delete(task.contextId);
@@ -17,7 +17,7 @@ export declare class Streams extends Manager<A2A.Stream> implements A2A.Streams
17
17
  }): Promise<A2A.Stream>;
18
18
  }
19
19
  export declare class Tasks extends Manager<A2A.Task> implements A2A.Tasks {
20
- constructor(tasks?: Map<string, A2A.Task>);
20
+ constructor(tasks?: Map<string, A2A.Task>, storage?: core.Manager<A2A.Task>);
21
21
  update(context: A2A.Context, update: A2A.Update): Promise<A2A.Task>;
22
22
  create(params: Partial<A2A.Task>): Promise<A2A.Task>;
23
23
  }
@@ -1,7 +1,7 @@
1
1
  import { A2A } from "../../types/index.js";
2
- import { Stream } from "./streams.js";
3
- import { createBaseContext, createContext, } from "./factory/context.js";
4
- import { v4 } from "uuid";
2
+ import { Stream } from './streams.js';
3
+ import { createBaseContext, createContext } from "./factory/context.js";
4
+ import { v4 } from 'uuid';
5
5
  import { getCurrentTimestamp } from "../../utils/index.js";
6
6
  import { handleUpdate } from "./handlers/update.js";
7
7
  import { Manager } from "../core/manager.js";
@@ -22,7 +22,7 @@ export class Contexts extends Manager {
22
22
  }
23
23
  async create(params) {
24
24
  if (await this.has(params.contextId)) {
25
- logger.warn("Contexts[create]: context already exists", {
25
+ logger.warn('Contexts[create]: context already exists', {
26
26
  contextId: params.contextId,
27
27
  });
28
28
  return (await this.get(params.contextId));
@@ -54,26 +54,30 @@ export class Streams extends Manager {
54
54
  }
55
55
  async create({ contextId, context, updates, }) {
56
56
  if (await this.has(contextId)) {
57
- throw new Error("Stream already exists");
57
+ throw new Error('Stream already exists');
58
58
  }
59
59
  await this.set(contextId, new Stream(contextId, context, updates));
60
60
  return (await this.get(contextId));
61
61
  }
62
62
  }
63
63
  export class Tasks extends Manager {
64
- constructor(tasks = new Map()) {
65
- super(tasks, true);
64
+ constructor(tasks = new Map(), storage) {
65
+ super(tasks, true, storage);
66
66
  }
67
67
  async update(context, update) {
68
68
  logger.info(`Tasks[update]: updating task`, { taskId: context.taskId });
69
69
  logger.debug(`Tasks[update]: update`, { update });
70
70
  logger.debug(`Tasks[update]: context`, { context });
71
+ const currentTask = await context.getTask();
72
+ if (!currentTask) {
73
+ throw new Error(`Task not found: ${context.taskId}`);
74
+ }
71
75
  logger.debug(`Tasks[update]: context task`, {
72
- task: await context.getTask(),
76
+ task: currentTask,
73
77
  });
74
78
  const task = await handleUpdate({
75
79
  context,
76
- task: await context.getTask(),
80
+ task: currentTask,
77
81
  update,
78
82
  });
79
83
  logger.debug(`Tasks[update]: task`, task);
@@ -81,15 +85,18 @@ export class Tasks extends Manager {
81
85
  return task;
82
86
  }
83
87
  async create(params) {
84
- if (params.id && (await this.has(params.id))) {
85
- return (await this.get(params.id));
88
+ if (params.id) {
89
+ const task = await this.get(params.id);
90
+ if (task) {
91
+ return task;
92
+ }
86
93
  }
87
94
  logger.info(`Tasks[create]: creating task`, { id: params.id });
88
95
  const task = {
89
96
  ...params,
90
97
  id: params.id ?? v4(),
91
98
  contextId: params.contextId ?? v4(),
92
- kind: "task",
99
+ kind: 'task',
93
100
  status: {
94
101
  state: A2A.TaskState.submitted,
95
102
  timestamp: getCurrentTimestamp(),
@@ -88,14 +88,11 @@ export class Manager {
88
88
  }
89
89
  const results = [];
90
90
  if (filter) {
91
- const items = Array.from(this.cache.values());
92
- const filterResults = await Promise.all(items.map(async (item) => {
91
+ for (const item of this.cache.values()) {
93
92
  if (await filter(item)) {
94
- return item;
93
+ results.push(item);
95
94
  }
96
- return undefined;
97
- }));
98
- results.push(...filterResults.filter((item) => item !== undefined));
95
+ }
99
96
  }
100
97
  if (this.storage) {
101
98
  const storageFilter = async (item) => {
@@ -3,8 +3,8 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { A2A } from "../types/index.js";
6
- import fs from "fs/promises";
7
- import path from "path";
6
+ import fs from 'node:fs/promises';
7
+ import path from 'node:path';
8
8
  import { logger } from "../config/index.js";
9
9
  import { Tasks } from "../services/a2a/managers.js";
10
10
  import { formatJson, safeParseSchema } from "../utils/index.js";
@@ -19,6 +19,7 @@ export class Files extends Tasks {
19
19
  * @param baseDir The base directory to store task files in.
20
20
  */
21
21
  constructor(baseDir) {
22
+ //TODO: we're inverting the dependency injection pattern here. Tasks should take the storage instance
22
23
  super(new Map());
23
24
  this.baseDir = baseDir;
24
25
  logger.info(`FileStore[init]: baseDir`, { baseDir });
@@ -54,7 +55,7 @@ export class Files extends Tasks {
54
55
  try {
55
56
  await this.ensureBaseDir();
56
57
  await fs.writeFile(filePath, formatJson(task), {
57
- encoding: "utf8",
58
+ encoding: 'utf8',
58
59
  });
59
60
  }
60
61
  catch (error) {
@@ -69,11 +70,11 @@ export class Files extends Tasks {
69
70
  */
70
71
  async readJsonFile(filePath) {
71
72
  try {
72
- const content = await fs.readFile(filePath, { encoding: "utf8" });
73
+ const content = await fs.readFile(filePath, { encoding: 'utf8' });
73
74
  return await safeParseSchema(content, A2A.TaskSchema);
74
75
  }
75
76
  catch (error) {
76
- if (error.code === "ENOENT") {
77
+ if (error.code === 'ENOENT') {
77
78
  logger.warn(`FileStore[readJsonFile]: ${filePath} not found`);
78
79
  return null;
79
80
  }
@@ -86,8 +87,7 @@ export class Files extends Tasks {
86
87
  * @returns A promise resolving to the task and history, or null if not found.
87
88
  */
88
89
  async get(taskId) {
89
- const task = (await super.get(taskId)) ??
90
- (await this.readJsonFile(this.getTaskFilePath(taskId)).catch(() => undefined));
90
+ const task = (await super.get(taskId)) ?? (await this.readJsonFile(this.getTaskFilePath(taskId)).catch(() => undefined));
91
91
  return task ?? undefined;
92
92
  }
93
93
  /**
@@ -101,8 +101,8 @@ export class Files extends Tasks {
101
101
  return;
102
102
  }
103
103
  if (task && taskId !== task.id) {
104
- logger.warn("FileStore", `Task ID mismatch: ${taskId} !== ${task.id}`);
105
- throw new Error("Task ID mismatch");
104
+ logger.warn('FileStore', `Task ID mismatch: ${taskId} !== ${task.id}`);
105
+ throw new Error('Task ID mismatch');
106
106
  }
107
107
  const taskFilePath = this.getTaskFilePath(taskId);
108
108
  await this.writeJsonFile(taskFilePath, task);
@@ -6,7 +6,7 @@ import { A2A } from "../types/index.js";
6
6
  import { Tasks } from "../services/a2a/managers.js";
7
7
  import { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core';
8
8
  export declare const TABLE_NAME = "artinet_tasks";
9
- export declare const createTaskTable: (db: BaseSQLiteDatabase<`sync` | `async`, any, TaskTable>) => Promise<void>;
9
+ export declare const createTaskTable: (db: BaseSQLiteDatabase<`sync` | `async`, any, TaskTable>, tableName?: string) => Promise<void>;
10
10
  export declare const TaskTable: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
11
11
  name: "artinet_tasks";
12
12
  schema: undefined;
@@ -345,7 +345,7 @@ export declare const TaskTable: import("drizzle-orm/sqlite-core").SQLiteTableWit
345
345
  export type TaskTable = typeof TaskTable.$inferSelect;
346
346
  export declare class SQLiteStore extends Tasks {
347
347
  private db;
348
- constructor(db: BaseSQLiteDatabase<`sync` | `async`, any, TaskTable>, tasks?: Map<string, A2A.Task>);
348
+ constructor(db: BaseSQLiteDatabase<`sync` | `async`, any, TaskTable>, tasks?: Map<string, A2A.Task>, tableName?: string);
349
349
  has(id: string): Promise<boolean>;
350
350
  list(): Promise<A2A.Task[]>;
351
351
  get(id: string): Promise<A2A.Task | undefined>;
@@ -7,14 +7,14 @@ import { eq, like, or /*Table, TableConfig*/ } from 'drizzle-orm';
7
7
  import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
8
8
  import { logger } from "../config/index.js";
9
9
  export const TABLE_NAME = 'artinet_tasks';
10
- const CREATE_TASKS_TABLE_SQL = `CREATE TABLE IF NOT EXISTS ${TABLE_NAME}\
10
+ const CREATE_TASKS_TABLE_SQL = (tableName = TABLE_NAME) => `CREATE TABLE IF NOT EXISTS ${tableName}\
11
11
  (id TEXT PRIMARY KEY, contextId TEXT NOT NULL,\
12
12
  kind TEXT NOT NULL, status TEXT NOT NULL,\
13
13
  history TEXT NOT NULL,\
14
14
  artifacts TEXT NOT NULL,\
15
15
  metadata TEXT NOT NULL)`;
16
- export const createTaskTable = async (db) => {
17
- await db.run(CREATE_TASKS_TABLE_SQL);
16
+ export const createTaskTable = async (db, tableName = TABLE_NAME) => {
17
+ await db.run(CREATE_TASKS_TABLE_SQL(tableName));
18
18
  };
19
19
  /*export type TTable = Table<TableConfig>; //TODO: Unwind Config/Column types */
20
20
  export const TaskTable = sqliteTable(TABLE_NAME, {
@@ -28,10 +28,12 @@ export const TaskTable = sqliteTable(TABLE_NAME, {
28
28
  });
29
29
  export class SQLiteStore extends Tasks {
30
30
  db;
31
- constructor(db, tasks = new Map()) {
31
+ //TODO: better for this to be a factory function? We're forcing the table/table name onto the user.
32
+ constructor(db, tasks = new Map(), tableName = TABLE_NAME) {
33
+ //TODO: we're inverting the dependency injection pattern here. Tasks should take the storage instance
32
34
  super(tasks);
33
35
  this.db = db;
34
- createTaskTable(db);
36
+ createTaskTable(db, tableName);
35
37
  }
36
38
  async has(id) {
37
39
  return ((await super.has(id)) ||
@@ -1,7 +1,9 @@
1
1
  /**
2
- * Copyright 2025 The Artinet Project
3
- * SPDX-License-Identifier: Apache-2.0
2
+ * Sanitizes a string by escaping HTML characters to prevent XSS attacks.
3
+ * @param str - The string to sanitize.
4
+ * @returns The sanitized string.
4
5
  */
6
+ export declare function sanitizeString(str: string): string;
5
7
  /**
6
8
  * Generates a timestamp in ISO 8601 format.
7
9
  * @returns The current timestamp as a string.
@@ -2,6 +2,16 @@
2
2
  * Copyright 2025 The Artinet Project
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
+ import escapeHtml from 'escape-html';
6
+ import { Buffer } from 'node:buffer';
7
+ /**
8
+ * Sanitizes a string by escaping HTML characters to prevent XSS attacks.
9
+ * @param str - The string to sanitize.
10
+ * @returns The sanitized string.
11
+ */
12
+ export function sanitizeString(str) {
13
+ return escapeHtml(str).trim();
14
+ }
5
15
  /**
6
16
  * Generates a timestamp in ISO 8601 format.
7
17
  * @returns The current timestamp as a string.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@artinet/sdk",
3
- "version": "0.6.10",
3
+ "version": "0.6.12",
4
4
  "description": "A TypeScript SDK for building collaborative AI agents.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -38,9 +38,9 @@
38
38
  "default": "./dist/storage/sqlite.js"
39
39
  },
40
40
  "./express": {
41
- "types": "./dist/server/express/server.d.ts",
42
- "import": "./dist/server/express/server.js",
43
- "default": "./dist/server/express/server.js"
41
+ "types": "./dist/server/express/module.d.ts",
42
+ "import": "./dist/server/express/module.js",
43
+ "default": "./dist/server/express/module.js"
44
44
  },
45
45
  "./trpc": {
46
46
  "types": "./dist/transport/trpc/index.d.ts",
@@ -152,6 +152,9 @@
152
152
  },
153
153
  "drizzle-orm": {
154
154
  "optional": true
155
+ },
156
+ "serverless-http": {
157
+ "optional": true
155
158
  }
156
159
  },
157
160
  "devDependencies": {