/** * ReferenceParser — resolve referências /tipo/caminho usadas no body das skills. * * Formatos suportados: * /skill/namespace/slug → executa outra skill * /kg/node-id → lê nó do Knowledge Graph * /data/table/filter → query ao banco Arcádia * /var/nome → variável de contexto da execução * /tool/nome → chama uma ferramenta registrada * /agent/nome → delega para um agente * /file/caminho → lê arquivo do storage */ import { db } from "../../db/index"; import { arcadiaSkills, graphNodes } from "@shared/schema"; import { eq, and } from "drizzle-orm"; export type ReferenceType = "skill" | "kg" | "data" | "var" | "tool" | "agent" | "file"; export interface ParsedReference { raw: string; // texto original, ex: "/skill/system/base_report" type: ReferenceType; path: string[]; // segmentos após o tipo } export interface ResolvedReference { ref: ParsedReference; value: unknown; error?: string; } export interface ExecutionContext { tenantId?: number; companyId?: number; userId?: string; vars?: Record; tools?: Record Promise>; } // ─── Parser ────────────────────────────────────────────────────────────────── const REF_REGEX = /\/([a-z]+)((?:\/[^\s/,})"']+)+)/g; export function parseReferences(text: string): ParsedReference[] { const refs: ParsedReference[] = []; const seen = new Set(); let match: RegExpExecArray | null; while ((match = REF_REGEX.exec(text)) !== null) { const raw = match[0]; if (seen.has(raw)) continue; seen.add(raw); const type = match[1] as ReferenceType; const path = match[2].split("/").filter(Boolean); if (isValidType(type)) { refs.push({ raw, type, path }); } } return refs; } function isValidType(t: string): t is ReferenceType { return ["skill", "kg", "data", "var", "tool", "agent", "file"].includes(t); } // ─── Resolver ───────────────────────────────────────────────────────────────── export class ReferenceResolver { constructor(private ctx: ExecutionContext) {} async resolveAll(refs: ParsedReference[]): Promise> { const results: Record = {}; await Promise.all( refs.map(async (ref) => { try { const value = await this.resolve(ref); results[ref.raw] = { ref, value }; } catch (err: any) { results[ref.raw] = { ref, value: null, error: err.message }; } }) ); return results; } async resolve(ref: ParsedReference): Promise { switch (ref.type) { case "skill": return this.resolveSkill(ref.path); case "kg": return this.resolveKg(ref.path); case "var": return this.resolveVar(ref.path); case "tool": return this.resolveTool(ref.path); case "data": return this.resolveData(ref.path); case "agent": return { type: "agent_reference", name: ref.path.join("/") }; case "file": return { type: "file_reference", path: ref.path.join("/") }; default: throw new Error(`Tipo de referência desconhecido: ${(ref as any).type}`); } } private async resolveSkill(path: string[]): Promise { // /skill/namespace/slug ou /skill/slug const [ns, slug] = path.length >= 2 ? [path[0], path[1]] : ["tenant", path[0]]; const conditions = [eq(arcadiaSkills.slug, slug), eq(arcadiaSkills.namespace, ns)]; if (this.ctx.tenantId) { conditions.push(eq(arcadiaSkills.tenantId, this.ctx.tenantId)); } const [skill] = await db .select() .from(arcadiaSkills) .where(and(...conditions)) .limit(1); if (!skill) throw new Error(`Skill não encontrada: /${ns}/${slug}`); return { type: "skill_ref", id: skill.id, slug: skill.slug, body: skill.body }; } private async resolveKg(path: string[]): Promise { const nodeId = parseInt(path[0], 10); if (isNaN(nodeId)) throw new Error(`ID de nó KG inválido: ${path[0]}`); const [node] = await db .select() .from(graphNodes) .where(eq(graphNodes.id, nodeId)) .limit(1); if (!node) throw new Error(`Nó KG não encontrado: ${nodeId}`); return node; } private resolveVar(path: string[]): unknown { const name = path.join("."); const vars = this.ctx.vars ?? {}; if (!(name in vars)) throw new Error(`Variável não definida: /var/${name}`); return vars[name]; } private async resolveTool(path: string[]): Promise { const name = path.join("/"); const tool = this.ctx.tools?.[name]; if (!tool) throw new Error(`Ferramenta não registrada: /tool/${name}`); return { type: "tool_ref", name, callable: true }; } private async resolveData(path: string[]): Promise { // /data/table/filter — placeholder; implementação por módulo const [table, ...filterParts] = path; return { type: "data_ref", table, filter: filterParts.join("/") }; } } // ─── Interpolação de template ───────────────────────────────────────────────── /** * Substitui referências resolvidas no body da skill. * Ex: "Resultado de /var/input" → "Resultado de " */ export function interpolate( body: string, resolved: Record ): string { let result = body; for (const [raw, res] of Object.entries(resolved)) { if (res.error) continue; const replacement = typeof res.value === "string" ? res.value : JSON.stringify(res.value); result = result.replaceAll(raw, replacement); } return result; }