@artinet/sdk
Version:
A TypeScript SDK for building collaborative AI agents.
133 lines (132 loc) • 4.4 kB
JavaScript
/**
* Copyright 2025 The Artinet Project
* SPDX-License-Identifier: Apache-2.0
*/
import { A2A } from "../types/index.js";
import fs from "fs/promises";
import path from "path";
import { logger } from "../config/index.js";
import { Tasks } from "../services/a2a/managers.js";
import { formatJson, safeParseSchema } from "../utils/index.js";
/**
* File-based implementation of the TaskStore interface.
* Stores tasks and their history as JSON files on disk.
*/
export class Files extends Tasks {
baseDir;
/**
* Creates a new FileStore.
* @param baseDir The base directory to store task files in.
*/
constructor(baseDir) {
super(new Map());
this.baseDir = baseDir;
logger.info(`FileStore[init]: baseDir`, { baseDir });
}
/**
* Constructs the file path for a task.
* @param taskId The task ID
* @returns The full file path for the task JSON file
*/
getTaskFilePath(taskId) {
return path.join(this.baseDir, `${taskId}.task.json`);
}
/**
* Ensures the base directory exists.
* @returns A promise that resolves when the directory exists.
*/
async ensureBaseDir() {
try {
await fs.mkdir(this.baseDir, { recursive: true });
}
catch (error) {
logger.warn(`FileStore[ensureBaseDir]: ${this.baseDir}`, error);
throw error;
}
}
/**
* Writes data to a JSON file.
* @param filePath The path to write to
* @param data The data to write
* @returns A promise that resolves when the write is complete
*/
async writeJsonFile(filePath, task) {
try {
await this.ensureBaseDir();
await fs.writeFile(filePath, formatJson(task), {
encoding: "utf8",
});
}
catch (error) {
logger.warn(`FileStore[writeJsonFile]: ${filePath}`, error);
throw error;
}
}
/**
* Reads data from a JSON file.
* @param filePath The path to read from
* @returns A promise resolving to the parsed data, or null if the file doesn't exist
*/
async readJsonFile(filePath) {
try {
const content = await fs.readFile(filePath, { encoding: "utf8" });
return await safeParseSchema(content, A2A.TaskSchema);
}
catch (error) {
if (error.code === "ENOENT") {
logger.warn(`FileStore[readJsonFile]: ${filePath} not found`);
return null;
}
throw error;
}
}
/**
* Loads a task and its history by task ID.
* @param taskId The ID of the task to load.
* @returns A promise resolving to the task and history, or null if not found.
*/
async get(taskId) {
const task = (await super.get(taskId)) ??
(await this.readJsonFile(this.getTaskFilePath(taskId)).catch(() => undefined));
return task ?? undefined;
}
/**
* Saves a task and its history.
* @param data The task and history to save.
* @returns A promise that resolves when the save is complete.
*/
async set(taskId, task) {
logger.debug(`FileStore[set]: ${taskId}`);
if (!task) {
return;
}
if (task && taskId !== task.id) {
logger.warn("FileStore", `Task ID mismatch: ${taskId} !== ${task.id}`);
throw new Error("Task ID mismatch");
}
const taskFilePath = this.getTaskFilePath(taskId);
await this.writeJsonFile(taskFilePath, task);
await super.set(taskId, task);
}
async delete(taskId) {
logger.debug(`FileStore[delete]: ${taskId}`);
const taskFilePath = this.getTaskFilePath(taskId);
await fs.unlink(taskFilePath);
await super.delete(taskId);
}
async has(taskId) {
if (await super.has(taskId)) {
return true;
}
const taskFilePath = this.getTaskFilePath(taskId);
return fs
.access(taskFilePath)
.then(() => true)
.catch(() => false);
}
async list() {
const taskIds = await fs.readdir(this.baseDir);
return Promise.all(taskIds.map((taskId) => this.get(taskId).catch(() => undefined))).then((tasks) => tasks.filter((task) => task !== undefined));
}
}
export const FileStore = Files;