@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
145 lines • 5.09 kB
JavaScript
/**
* Artifact Store
*
* Pluggable storage for externalized MCP tool outputs.
*
* When `mcp.outputLimits.strategy = "externalize"` the full tool payload is
* written here instead of being sent inline to the LLM. The model receives a
* compact surrogate with a preview and an artifact ID. The full payload can be
* retrieved on demand via the `retrieve_context` tool.
*
* Architecture:
* ArtifactStore (interface) — canonical types in src/lib/types/artifactTypes.ts
* LocalTempArtifactStore — single-process, filesystem-backed implementation
*
* Distributed backends (S3, Redis blobs) can be added later by implementing
* ArtifactStore from types/artifactTypes.ts.
*
* @module artifacts/artifactStore
*/
import { randomUUID } from "node:crypto";
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { logger } from "../utils/logger.js";
// Re-export so callers can import everything from one place
// ---------------------------------------------------------------------------
// LocalTempArtifactStore
// ---------------------------------------------------------------------------
/** Characters used for the quick preview embedded in surrogate results. */
const DEFAULT_PREVIEW_CHARS = 500;
/**
* Filesystem-backed artifact store using the OS temp directory.
*
* Files are written with mode 0o600 (owner read/write only).
* An in-memory index tracks metadata without a separate index file.
*
* Suitable for:
* - CLI usage
* - Single-process SDK deployments
* - Multi-process deployments where each process manages its own artifacts
* (artifacts created in one process are not visible to others)
*
* @example
* ```typescript
* const store = new LocalTempArtifactStore();
* const ref = await store.store(largeJson, {
* toolName: "list_files",
* serverId: "filesystem-server",
* sizeBytes: Buffer.byteLength(largeJson),
* contentType: "json",
* });
* // Later, via retrieve_context:
* const full = await store.retrieve(ref.id);
* ```
*/
export class LocalTempArtifactStore {
dir;
index = new Map();
constructor(dir) {
this.dir = dir ?? join(tmpdir(), "neurolink-artifacts");
}
generatePreview(payload) {
if (payload.length <= DEFAULT_PREVIEW_CHARS) {
return payload;
}
return `${payload.slice(0, DEFAULT_PREVIEW_CHARS)}…`;
}
async store(payload, meta) {
await mkdir(this.dir, { recursive: true, mode: 0o700 });
const id = randomUUID();
const ext = meta.contentType === "json" ? ".json" : ".txt";
const filePath = join(this.dir, `${id}${ext}`);
await writeFile(filePath, payload, { encoding: "utf-8", mode: 0o600 });
const fullMeta = {
...meta,
createdAt: Date.now(),
path: filePath,
};
this.index.set(id, fullMeta);
logger.debug(`[ArtifactStore] Stored artifact ${id} for tool "${meta.toolName}" ` +
`(${formatBytes(meta.sizeBytes)})`);
return {
id,
preview: this.generatePreview(payload),
sizeBytes: meta.sizeBytes,
meta: { ...meta, createdAt: fullMeta.createdAt },
};
}
async retrieve(id) {
const entry = this.index.get(id);
if (!entry) {
logger.debug(`[ArtifactStore] Artifact ${id} not in index`);
return null;
}
try {
const content = await readFile(entry.path, "utf-8");
logger.debug(`[ArtifactStore] Retrieved artifact ${id} (${formatBytes(entry.sizeBytes)})`);
return content;
}
catch (err) {
logger.warn(`[ArtifactStore] Failed to read artifact ${id}: ${err instanceof Error ? err.message : String(err)}`);
return null;
}
}
async delete(id) {
const entry = this.index.get(id);
if (!entry) {
return;
}
try {
await rm(entry.path, { force: true });
}
catch {
// Suppress — file may already be gone
}
this.index.delete(id);
}
async cleanup(olderThanMs) {
const cutoff = Date.now() - olderThanMs;
let count = 0;
for (const [id, entry] of this.index.entries()) {
if (entry.createdAt < cutoff) {
await this.delete(id);
count++;
}
}
if (count > 0) {
logger.debug(`[ArtifactStore] Cleaned up ${count} expired artifact(s)`);
}
return count;
}
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function formatBytes(bytes) {
if (bytes < 1024) {
return `${bytes} B`;
}
if (bytes < 1024 * 1024) {
return `${(bytes / 1024).toFixed(1)} KB`;
}
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}
//# sourceMappingURL=artifactStore.js.map