import { type Express } from "express"; import { z } from "zod"; import { executeMemoryManager, executeSkillManager } from "../learning/toolManagers.js"; import { type MemoryStore } from "../memory/store.js"; import { isTrustedInternalBridgeRequest } from "../product/internal-bridge-auth.js"; import { getRuntimeSessionContext } from "../runtime/sessionContext.js"; import { type SkillStore } from "../skills/store.js"; const memoryInput = z.object({ action: z.enum(["add", "list", "replace", "remove"]), scope: z.string().max(32), reason: z.string().trim().min(1).max(2000).optional(), content: z.string().max(20000).optional(), target_id: z.string().max(128).optional(), }).strict().refine((input) => input.action === "list" || Boolean(input.reason), { path: ["reason"], message: "reason is required for writes, updates and removals", }); const skillInput = z.object({ action: z.enum(["list", "write_skill", "remove_skill", "append_pattern", "remove_pattern", "write_reference", "remove_reference", "write_script", "remove_script"]), skill_path: z.string().max(512), reason: z.string().trim().min(1).max(2000).optional(), content: z.string().max(200000).optional(), file_path: z.string().max(512).optional(), pattern: z.string().max(20000).optional(), target_id: z.string().max(128).optional(), }).strict().refine((input) => input.action === "list" || Boolean(input.reason), { path: ["reason"], message: "reason is required for writes, updates and removals", }); export const registerLearningToolBridge = (app: Express, options: { internalToken: string; memoryStore: MemoryStore; skillStore: SkillStore; }) => { for (const name of ["memory-manager", "skill-manager"] as const) { app.post(`/internal/tools/${name}`, async (req, res) => { if (!isTrustedInternalBridgeRequest(req, options.internalToken)) { res.status(403).json({ message: "forbidden" }); return; } const context = typeof req.body?.session_id === "string" ? getRuntimeSessionContext(req.body.session_id) : null; if (!context) { res.status(404).json({ message: "session context not found" }); return; } if (name === "memory-manager") { const input = memoryInput.safeParse(req.body.input); if (!input.success) { res.status(400).json({ message: "invalid memory input" }); return; } res.json(await executeMemoryManager(options.memoryStore, context, input.data)); } else { const input = skillInput.safeParse(req.body.input); if (!input.success) { res.status(400).json({ message: "invalid skill input" }); return; } res.json(await executeSkillManager(options.skillStore, context, input.data)); } }); } };