@vorim/sdk 3.4.3 → 3.6.0

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/replay.ts","../../src/integrations/langchain.ts"],"sourcesContent":["/**\n * Replayable agent decision evidence helpers.\n *\n * Canonical-form hashing for the VAIP -02 schema fields that the SDK\n * attaches to audit events. The hashes recorded in audit_events.tool_catalogue_hash\n * and audit_events.system_prompt_hash use these functions, so the bytes\n * an auditor or counterparty reconstructs must match what the SDK produced.\n *\n * These helpers are intentionally separate from the signing path. The\n * v0 canonical signature form (event_type|action|resource|input_hash|\n * output_hash|result) does NOT cover model_version, tool_catalogue_hash,\n * or system_prompt_hash. They will enter the canonical bytes in v1\n * (RFC 8785 JCS) in a follow-up release.\n *\n * Stable across SDK versions: the canonical-form version is documented\n * in CANONICAL_TOOL_CATALOGUE_VERSION. Future changes get a v2 etc;\n * never edit the existing v1 logic, or already-recorded hashes lose\n * their meaning.\n */\n\n// ─── Versioning ───────────────────────────────────────────────────────────\n\n/**\n * Canonical-form version for tool catalogue hashes produced by this SDK.\n * Recorded in tool_catalogue_canon_version on the event metadata (when\n * the metadata field is used) so verifiers know which hash recipe to\n * reproduce. Increment ONLY if the algorithm changes in a way that\n * would change the hash for the same logical catalogue.\n */\nexport const CANONICAL_TOOL_CATALOGUE_VERSION = 'v1' as const;\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\n/**\n * Minimum shape a tool needs for catalogue hashing. The framework\n * integrations adapt their native tool objects to this shape before\n * calling hashToolCatalogue.\n */\nexport interface CatalogueTool {\n /** The name the model sees and calls. Required. */\n name: string;\n /** Human-readable description shown to the model. Optional; absent ↔ empty string. */\n description?: string;\n /**\n * JSON Schema describing the tool's input parameters. Optional;\n * absent ↔ empty object `{}`. The schema gets RFC 8785 JCS-canonicalised\n * before hashing so semantically-equivalent variations (key order,\n * whitespace) produce the same hash.\n */\n schema?: Record<string, unknown> | null;\n}\n\n// ─── RFC 8785 JCS subset ──────────────────────────────────────────────────\n\n/**\n * RFC 8785 JSON Canonicalization Scheme, sufficient subset for tool\n * catalogue values.\n *\n * Rules:\n * - Object keys sorted lexicographically by UTF-16 code units (which\n * is what JS string comparison does naturally).\n * - No whitespace between tokens.\n * - Numbers: integers as integers, finite floats per ECMAScript\n * Number.prototype.toString. JCS forbids NaN and Infinity.\n * - Strings: JSON-escape using minimal set per RFC 8259 § 7.\n * - null, true, false, arrays: as JSON.stringify produces them, since\n * JSON.stringify already produces the canonical form for these.\n *\n * Not vendoring a full library because tool schemas don't carry\n * non-integer numbers in practice and the JS spec for Number.toString\n * happens to coincide with JCS § 3.2.2.2 for the integer case.\n */\nexport function jcsCanonicalise(value: unknown): string {\n if (value === null) return 'null';\n if (value === true) return 'true';\n if (value === false) return 'false';\n\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) {\n throw new Error('jcsCanonicalise: NaN and Infinity are not JCS-valid');\n }\n // For integers in safe range, .toString() matches JCS. For\n // non-integer floats, .toString() also matches in modern JS\n // engines (V8, JavaScriptCore, SpiderMonkey all use the shortest\n // round-trip representation, which is what JCS § 3.2.2.2 requires).\n return value.toString();\n }\n\n if (typeof value === 'string') {\n return JSON.stringify(value);\n }\n\n if (Array.isArray(value)) {\n return '[' + value.map(jcsCanonicalise).join(',') + ']';\n }\n\n if (typeof value === 'object') {\n const keys = Object.keys(value as Record<string, unknown>).sort();\n const parts = keys.map(k => {\n return JSON.stringify(k) + ':' + jcsCanonicalise((value as Record<string, unknown>)[k]);\n });\n return '{' + parts.join(',') + '}';\n }\n\n // undefined, function, symbol, bigint — not JSON-representable\n throw new Error(`jcsCanonicalise: unsupported value type: ${typeof value}`);\n}\n\n// ─── SHA-256 ──────────────────────────────────────────────────────────────\n\nasync function sha256Hex(input: string | Uint8Array): Promise<string> {\n const bytes = typeof input === 'string' ? new TextEncoder().encode(input) : input;\n\n // Node.js Web Crypto (Node 18+) supports digest. Browser Web Crypto does too.\n // Fall back to node:crypto if Web Crypto is unavailable.\n const subtle = (globalThis as any).crypto?.subtle;\n if (subtle) {\n const buf = await subtle.digest('SHA-256', bytes);\n return Array.from(new Uint8Array(buf))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n }\n\n // Node fallback\n const nodeCrypto = await import('node:crypto');\n return nodeCrypto.createHash('sha256').update(bytes).digest('hex');\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * Hash a single tool definition. Returns `sha256:<hex>`.\n *\n * Canonical form (v1):\n * JCS-canonicalised JSON of `{name, description, schema}` where\n * absent fields substitute `description: \"\"` and `schema: {}`.\n */\nexport async function hashTool(tool: CatalogueTool): Promise<string> {\n const normalised = {\n name: tool.name,\n description: tool.description ?? '',\n schema: tool.schema ?? {},\n };\n const hex = await sha256Hex(jcsCanonicalise(normalised));\n return `sha256:${hex}`;\n}\n\n/**\n * Hash an entire tool catalogue. Returns `sha256:<hex>`.\n *\n * Reordering tools does NOT change the hash (tool hashes sorted\n * lexicographically before concatenation). Adding, removing, or\n * modifying a tool DOES change the hash.\n *\n * Per-tool hashing first means a verifier comparing two catalogue\n * hashes that differ can also be given the per-tool hashes to\n * identify which specific tool changed.\n */\nexport async function hashToolCatalogue(tools: CatalogueTool[]): Promise<string> {\n if (tools.length === 0) {\n // Empty catalogue has a deterministic, stable hash distinct from \"no field\"\n return `sha256:${await sha256Hex('[]')}`;\n }\n const perTool = await Promise.all(tools.map(hashTool));\n perTool.sort();\n const hex = await sha256Hex(perTool.join(''));\n return `sha256:${hex}`;\n}\n\n/**\n * Hash a system prompt. Returns `sha256:<hex>`.\n *\n * The prompt is UTF-8 encoded and hashed verbatim — no normalisation.\n * If a caller wants to ignore whitespace or comment differences, they\n * should normalise before calling. The intent here is deterministic\n * reproducibility, not semantic equivalence.\n */\nexport async function hashSystemPrompt(prompt: string): Promise<string> {\n const hex = await sha256Hex(prompt);\n return `sha256:${hex}`;\n}\n\n/**\n * Convenience: hash the previous event's canonical bytes for use in\n * the prev_event_hash field of hash-chained ingest. Caller provides\n * the canonical bytes (use canonicalPayloadV0 from the main module).\n */\nexport async function hashPreviousEvent(canonicalBytes: string): Promise<string> {\n const hex = await sha256Hex(canonicalBytes);\n return `sha256:${hex}`;\n}\n\n// ─── Replay context — framework integration helper ────────────────────────\n\n/**\n * Raw inputs the integration captures from the framework. Set by the\n * integration's config; turned into hashes by {@link prepareReplayContext}.\n */\nexport interface ReplayInputs {\n /** Stable identifier for the model. E.g. `\"anthropic:claude-opus-4-8\"`. */\n modelVersion?: string;\n /** Tools available to the agent at call time. Hashed via {@link hashToolCatalogue}. */\n tools?: CatalogueTool[];\n /** System prompt active at call time. Hashed via {@link hashSystemPrompt}. */\n systemPrompt?: string;\n}\n\n/**\n * Pre-computed hashes ready to attach to audit events. The three keys\n * match the audit_events column names.\n */\nexport interface ReplayContext {\n model_version?: string;\n tool_catalogue_hash?: string;\n system_prompt_hash?: string;\n}\n\n/**\n * Compute replay context once from raw inputs. Use at integration\n * setup time so each emit can attach the hashes without re-hashing.\n *\n * Returns an object suitable for spreading into an AuditEventInput:\n * `await vorim.emit({ ...event, ...replayContext })`\n *\n * If a field is absent in the inputs, it is absent in the result\n * (not the empty string). That keeps the event lean.\n */\nexport async function prepareReplayContext(\n inputs: ReplayInputs,\n): Promise<ReplayContext> {\n const ctx: ReplayContext = {};\n if (inputs.modelVersion) ctx.model_version = inputs.modelVersion;\n if (inputs.tools) ctx.tool_catalogue_hash = await hashToolCatalogue(inputs.tools);\n if (inputs.systemPrompt) ctx.system_prompt_hash = await hashSystemPrompt(inputs.systemPrompt);\n return ctx;\n}\n","// ============================================================================\n// VORIM SDK — LangChain / LangGraph JS Integration\n// Provides tool wrapping with permission checks + audit trail,\n// a callback handler for observability, and a factory for creating\n// Vorim-secured LangGraph ReAct agents.\n//\n// Peer dependencies:\n// @langchain/core >=0.3.0\n// @langchain/langgraph >=0.2.0\n// ============================================================================\n\nimport type { VorimSDK } from '../index.js';\nimport type { PermissionScope, AuditEventInput } from '../types.js';\nimport {\n prepareReplayContext,\n type ReplayInputs,\n type ReplayContext,\n type CatalogueTool,\n} from '../replay.js';\n\n// ─── Re-declared LangChain types (peer dependency — not bundled) ──────────\n// These mirror the actual types from @langchain/core so consumers don't\n// need to import them separately for basic usage.\n\n/** Minimal subset of @langchain/core Serialized */\ninterface Serialized {\n lc: number;\n type: string;\n id: string[];\n kwargs?: Record<string, unknown>;\n}\n\n// ─── Configuration ────────────────────────────────────────────────────────\n\nexport interface VorimLangChainConfig {\n /** Vorim SDK instance. */\n vorim: VorimSDK;\n /** The Vorim agent_id to associate with this LangChain agent. */\n agentId: string;\n /** Map tool names → Vorim permission scopes. Unmapped tools default to 'agent:execute'. */\n permissionMap?: Record<string, PermissionScope>;\n /** Default permission scope for tools not in permissionMap. @default 'agent:execute' */\n defaultPermission?: PermissionScope;\n /** Whether to emit audit events asynchronously (fire-and-forget). @default true */\n asyncAudit?: boolean;\n /**\n * Replayable agent decision evidence (VAIP -02). When provided, the\n * hashes of the model version, tool catalogue, and system prompt are\n * attached to every emitted audit event so an auditor can later\n * reproduce the agent's decision context.\n *\n * Hashes are computed once on first emit and cached for subsequent\n * emits in this config's lifetime.\n *\n * These fields are NOT covered by the v0 canonical signature form;\n * advisory only until v1 (RFC 8785 JCS) lands.\n */\n replay?: ReplayInputs;\n}\n\n// ─── Tool Wrapper ─────────────────────────────────────────────────────────\n\n/**\n * Wraps a LangChain `StructuredTool` (or `DynamicStructuredTool`) with\n * Vorim permission checks before execution and audit event emission after.\n *\n * Works with any tool that has `name`, `description`, `schema`, and `_call`.\n *\n * @example\n * ```ts\n * import { DynamicStructuredTool } from \"@langchain/core/tools\";\n * import { wrapTool } from \"@vorim/sdk/integrations/langchain\";\n *\n * const guarded = wrapTool(myTool, {\n * vorim, agentId: \"agid_acme_a1b2c3d4\",\n * permissionMap: { search_docs: \"agent:read\" },\n * });\n * ```\n */\nexport function wrapTool<T extends LangChainTool>(\n tool: T,\n config: VorimLangChainConfig,\n): T {\n const { vorim, agentId, permissionMap = {}, defaultPermission = 'agent:execute', asyncAudit = true, replay } = config;\n\n const originalCall = tool._call.bind(tool);\n const getReplayCtx = makeReplayContextGetter(replay);\n\n // Override _call to inject permission check + audit\n (tool as any)._call = async function vorimGuardedCall(\n arg: unknown,\n runManager?: unknown,\n parentConfig?: unknown,\n ): Promise<unknown> {\n const scope = permissionMap[tool.name] ?? defaultPermission;\n\n // 1. Permission check\n const { allowed, reason } = await vorim.check(agentId, scope);\n const replayCtx = await getReplayCtx();\n\n if (!allowed) {\n const event: AuditEventInput = {\n agent_id: agentId,\n event_type: 'tool_call',\n action: tool.name,\n resource: truncate(JSON.stringify(arg), 500),\n permission: scope,\n result: 'denied',\n metadata: { reason, framework: 'langchain' },\n ...replayCtx,\n };\n emitAudit(vorim, event, asyncAudit);\n throw new Error(`Vorim: permission denied for \"${tool.name}\" — scope \"${scope}\"${reason ? `: ${reason}` : ''}`);\n }\n\n // 2. Execute the original tool\n const start = Date.now();\n let result: unknown;\n try {\n result = await originalCall(arg, runManager, parentConfig);\n } catch (err) {\n const event: AuditEventInput = {\n agent_id: agentId,\n event_type: 'tool_call',\n action: tool.name,\n resource: truncate(JSON.stringify(arg), 500),\n permission: scope,\n result: 'error',\n latency_ms: Date.now() - start,\n error_code: err instanceof Error ? err.name : 'UNKNOWN',\n metadata: { error: err instanceof Error ? err.message : String(err), framework: 'langchain' },\n ...replayCtx,\n };\n emitAudit(vorim, event, asyncAudit);\n throw err;\n }\n\n // 3. Audit success\n const event: AuditEventInput = {\n agent_id: agentId,\n event_type: 'tool_call',\n action: tool.name,\n resource: truncate(JSON.stringify(arg), 500),\n permission: scope,\n result: 'success',\n latency_ms: Date.now() - start,\n metadata: { framework: 'langchain' },\n ...replayCtx,\n };\n emitAudit(vorim, event, asyncAudit);\n\n return result;\n };\n\n return tool;\n}\n\n/**\n * Lazy-cached replay context getter. The hashes are computed once on\n * first call and reused for subsequent calls in this closure's lifetime.\n * Returns an empty object when no `replay` config was provided so the\n * spread is a no-op.\n */\nfunction makeReplayContextGetter(replay: ReplayInputs | undefined): () => Promise<ReplayContext> {\n if (!replay) return async () => ({});\n let cached: Promise<ReplayContext> | null = null;\n return () => {\n if (!cached) cached = prepareReplayContext(replay);\n return cached;\n };\n}\n\n/**\n * Wraps an array of LangChain tools with Vorim permission + audit.\n */\nexport function wrapTools<T extends LangChainTool>(\n tools: T[],\n config: VorimLangChainConfig,\n): T[] {\n return tools.map(t => wrapTool(t, config));\n}\n\n// ─── Callback Handler ─────────────────────────────────────────────────────\n\n/**\n * LangChain callback handler that emits Vorim audit events for every\n * tool invocation. This is a **non-intrusive** observability layer —\n * it does not block or modify tool execution.\n *\n * Attach it to any LangChain invoke/stream call:\n * ```ts\n * const handler = new VorimCallbackHandler(vorim, \"agid_acme_a1b2c3d4\");\n * await agent.invoke({ messages }, { callbacks: [handler] });\n * ```\n */\nexport class VorimCallbackHandler {\n name = 'VorimCallbackHandler';\n private vorim: VorimSDK;\n private agentId: string;\n private runMap = new Map<string, { tool: string; input: string; startTime: number }>();\n private getReplayCtx: () => Promise<ReplayContext>;\n\n // Flags to satisfy BaseCallbackHandler interface\n ignoreLLM = true;\n ignoreChain = true;\n ignoreAgent = true;\n ignoreRetriever = true;\n ignoreCustomEvent = true;\n lc_serializable = false;\n\n get lc_id(): string[] { return ['vorim', 'callbacks', 'VorimCallbackHandler']; }\n get lc_namespace(): string[] { return ['vorim', 'callbacks']; }\n\n /**\n * @param vorim Vorim SDK instance.\n * @param agentId The Vorim agent_id whose actions this callback audits.\n * @param replay Optional replayable-evidence inputs. Same shape as\n * {@link VorimLangChainConfig.replay}; hashes are\n * computed once and attached to every audit event.\n */\n constructor(vorim: VorimSDK, agentId: string, replay?: ReplayInputs) {\n this.vorim = vorim;\n this.agentId = agentId;\n this.getReplayCtx = makeReplayContextGetter(replay);\n }\n\n async handleToolStart(\n tool: Serialized,\n input: string,\n runId: string,\n _parentRunId?: string,\n _tags?: string[],\n _metadata?: Record<string, unknown>,\n runName?: string,\n ): Promise<void> {\n const toolName = runName ?? tool.id?.[tool.id.length - 1] ?? 'unknown';\n this.runMap.set(runId, { tool: toolName, input, startTime: Date.now() });\n }\n\n async handleToolEnd(\n _output: unknown,\n runId: string,\n ): Promise<void> {\n const run = this.runMap.get(runId);\n if (!run) return;\n this.runMap.delete(runId);\n const replayCtx = await this.getReplayCtx();\n\n await this.vorim.emit({\n agent_id: this.agentId,\n event_type: 'tool_call',\n action: run.tool,\n resource: truncate(run.input, 500),\n result: 'success',\n latency_ms: Date.now() - run.startTime,\n metadata: { framework: 'langchain' },\n ...replayCtx,\n }).catch(() => {}); // never throw from callback\n }\n\n async handleToolError(\n err: Error,\n runId: string,\n ): Promise<void> {\n const run = this.runMap.get(runId);\n if (!run) return;\n this.runMap.delete(runId);\n const replayCtx = await this.getReplayCtx();\n\n await this.vorim.emit({\n agent_id: this.agentId,\n event_type: 'tool_call',\n action: run.tool,\n resource: truncate(run.input, 500),\n result: 'error',\n latency_ms: Date.now() - run.startTime,\n error_code: err.name,\n metadata: { error: err.message, framework: 'langchain' },\n ...replayCtx,\n }).catch(() => {});\n }\n\n // Required by BaseCallbackHandler — no-op for events we don't need\n copy(): VorimCallbackHandler {\n return new VorimCallbackHandler(this.vorim, this.agentId);\n }\n\n toJSON() {\n return { lc: 1, type: 'not_implemented', id: this.lc_id };\n }\n\n toJSONNotImplemented() {\n return this.toJSON();\n }\n}\n\n// ─── Agent Factory ────────────────────────────────────────────────────────\n\nexport interface CreateVorimAgentConfig extends VorimLangChainConfig {\n /** Display name for the agent when registering with Vorim. */\n name: string;\n /** Agent capabilities (e.g. [\"web_search\", \"code_execution\"]). */\n capabilities: string[];\n /** Initial permission scopes to grant. */\n scopes: PermissionScope[];\n /** Optional description. */\n description?: string;\n}\n\n/**\n * Registers a new agent with Vorim and returns wrapped tools + callback handler\n * ready to use with `createReactAgent` or any LangChain agent.\n *\n * @example\n * ```ts\n * import { createReactAgent } from \"@langchain/langgraph/prebuilt\";\n * import { ChatOpenAI } from \"@langchain/openai\";\n * import { createVorimAgent } from \"@vorim/sdk/integrations/langchain\";\n *\n * const { agentId, tools, callbackHandler } = await createVorimAgent({\n * vorim,\n * name: \"research-agent\",\n * capabilities: [\"web_search\"],\n * scopes: [\"agent:read\", \"agent:execute\"],\n * tools: [searchTool, analysisTool],\n * });\n *\n * const agent = createReactAgent({\n * llm: new ChatOpenAI({ model: \"gpt-4o\" }),\n * tools,\n * });\n *\n * const result = await agent.invoke(\n * { messages: [{ role: \"user\", content: \"Research AI trends\" }] },\n * { callbacks: [callbackHandler] },\n * );\n * ```\n */\nexport async function createVorimAgent<T extends LangChainTool>(config: CreateVorimAgentConfig & { tools: T[] }) {\n const { vorim, name, capabilities, scopes, description, tools: rawTools, permissionMap, defaultPermission, asyncAudit, replay } = config;\n\n // Register agent with Vorim\n const registration = await vorim.register({\n name,\n description,\n capabilities,\n scopes,\n });\n\n const agentId = registration.agent.agent_id;\n\n // If the caller passed `replay.tools`, use those. Otherwise derive a\n // tool catalogue from the rawTools we're about to wrap so the\n // replayable-evidence story works automatically.\n const derivedReplay: ReplayInputs | undefined = replay\n ? { ...replay, tools: replay.tools ?? deriveCatalogueFromLangChainTools(rawTools) }\n : { tools: deriveCatalogueFromLangChainTools(rawTools) };\n\n const agentConfig: VorimLangChainConfig = {\n vorim, agentId, permissionMap, defaultPermission, asyncAudit, replay: derivedReplay,\n };\n\n // Wrap tools with permission checks\n const tools = wrapTools(rawTools, agentConfig);\n\n // Create callback handler for audit trail\n const callbackHandler = new VorimCallbackHandler(vorim, agentId, derivedReplay);\n\n return {\n /** The Vorim agent_id. */\n agentId,\n /** The full agent registration result. */\n registration,\n /** Tools wrapped with Vorim permission checks + audit. */\n tools,\n /** Callback handler for audit trail observability. */\n callbackHandler,\n /** The private key (store securely — shown once). */\n privateKey: registration.private_key,\n };\n}\n\n// ─── Internal helpers ─────────────────────────────────────────────────────\n\n/** Minimal interface matching any LangChain StructuredTool / DynamicStructuredTool. */\ninterface LangChainTool {\n name: string;\n description: string;\n schema: unknown;\n _call(arg: any, runManager?: any, parentConfig?: any): Promise<any>;\n}\n\nfunction truncate(str: string, max: number): string {\n return str.length > max ? str.slice(0, max) + '…' : str;\n}\n\nfunction emitAudit(vorim: VorimSDK, event: AuditEventInput, async: boolean): void {\n if (async) {\n vorim.emit(event).catch(() => {});\n } else {\n vorim.emit(event).catch(() => {});\n }\n}\n\n/**\n * Convert LangChain tools to the catalogue shape used by hashToolCatalogue.\n * Best-effort: LangChain tool schemas can be Zod or plain JSON Schema.\n * If we can't get a serialisable shape, we fall back to `null` schema\n * which the hashing treats as `{}`.\n */\nfunction deriveCatalogueFromLangChainTools(tools: LangChainTool[]): CatalogueTool[] {\n return tools.map(t => {\n let schema: Record<string, unknown> | null = null;\n const raw = t.schema as unknown;\n if (raw && typeof raw === 'object') {\n // If it's already plain JSON-serialisable, use it. Otherwise null.\n try {\n schema = JSON.parse(JSON.stringify(raw));\n } catch {\n schema = null;\n }\n }\n return {\n name: t.name,\n description: t.description ?? '',\n schema,\n };\n });\n}\n"],"mappings":"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","names":["event"]}
1
+ {"version":3,"sources":["../../src/replay.ts","../../src/integrations/langchain.ts"],"sourcesContent":["/**\n * Replayable agent decision evidence helpers.\n *\n * Canonical-form hashing for the VAIP -02 schema fields that the SDK\n * attaches to audit events. The hashes recorded in audit_events.tool_catalogue_hash\n * and audit_events.system_prompt_hash use these functions, so the bytes\n * an auditor or counterparty reconstructs must match what the SDK produced.\n *\n * These helpers are intentionally separate from the signing path. The\n * v0 canonical signature form (event_type|action|resource|input_hash|\n * output_hash|result) does NOT cover model_version, tool_catalogue_hash,\n * or system_prompt_hash. They will enter the canonical bytes in v1\n * (RFC 8785 JCS) in a follow-up release.\n *\n * Stable across SDK versions: the canonical-form version is documented\n * in CANONICAL_TOOL_CATALOGUE_VERSION. Future changes get a v2 etc;\n * never edit the existing v1 logic, or already-recorded hashes lose\n * their meaning.\n */\n\n// ─── Versioning ───────────────────────────────────────────────────────────\n\n/**\n * Canonical-form version for tool catalogue hashes produced by this SDK.\n * Recorded in tool_catalogue_canon_version on the event metadata (when\n * the metadata field is used) so verifiers know which hash recipe to\n * reproduce. Increment ONLY if the algorithm changes in a way that\n * would change the hash for the same logical catalogue.\n */\nexport const CANONICAL_TOOL_CATALOGUE_VERSION = 'v1' as const;\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\n/**\n * Minimum shape a tool needs for catalogue hashing. The framework\n * integrations adapt their native tool objects to this shape before\n * calling hashToolCatalogue.\n */\nexport interface CatalogueTool {\n /** The name the model sees and calls. Required. */\n name: string;\n /** Human-readable description shown to the model. Optional; absent ↔ empty string. */\n description?: string;\n /**\n * JSON Schema describing the tool's input parameters. Optional;\n * absent ↔ empty object `{}`. The schema gets RFC 8785 JCS-canonicalised\n * before hashing so semantically-equivalent variations (key order,\n * whitespace) produce the same hash.\n */\n schema?: Record<string, unknown> | null;\n}\n\n// ─── RFC 8785 JCS subset ──────────────────────────────────────────────────\n\n/**\n * RFC 8785 JSON Canonicalization Scheme, sufficient subset for tool\n * catalogue values.\n *\n * Rules:\n * - Object keys sorted lexicographically by UTF-16 code units (which\n * is what JS string comparison does naturally).\n * - No whitespace between tokens.\n * - Numbers: integers as integers, finite floats per ECMAScript\n * Number.prototype.toString. JCS forbids NaN and Infinity.\n * - Strings: JSON-escape using minimal set per RFC 8259 § 7.\n * - null, true, false, arrays: as JSON.stringify produces them, since\n * JSON.stringify already produces the canonical form for these.\n *\n * Not vendoring a full library because tool schemas don't carry\n * non-integer numbers in practice and the JS spec for Number.toString\n * happens to coincide with JCS § 3.2.2.2 for the integer case.\n */\nexport function jcsCanonicalise(value: unknown): string {\n if (value === null) return 'null';\n if (value === true) return 'true';\n if (value === false) return 'false';\n\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) {\n throw new Error('jcsCanonicalise: NaN and Infinity are not JCS-valid');\n }\n // For integers in safe range, .toString() matches JCS. For\n // non-integer floats, .toString() also matches in modern JS\n // engines (V8, JavaScriptCore, SpiderMonkey all use the shortest\n // round-trip representation, which is what JCS § 3.2.2.2 requires).\n return value.toString();\n }\n\n if (typeof value === 'string') {\n return JSON.stringify(value);\n }\n\n if (Array.isArray(value)) {\n return '[' + value.map(jcsCanonicalise).join(',') + ']';\n }\n\n if (typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n // Filter undefined-valued fields, matching @vorim/verify and\n // @vorim/shared-types. Without this the SDK throws on { a: 1, b: undefined }\n // while the verifier silently drops b — a cross-module canonical-form\n // divergence that would break signature verification on such events.\n const keys = Object.keys(obj).filter(k => obj[k] !== undefined).sort();\n const parts = keys.map(k => JSON.stringify(k) + ':' + jcsCanonicalise(obj[k]));\n return '{' + parts.join(',') + '}';\n }\n\n // undefined, function, symbol, bigint — not JSON-representable\n throw new Error(`jcsCanonicalise: unsupported value type: ${typeof value}`);\n}\n\n// ─── SHA-256 ──────────────────────────────────────────────────────────────\n\nasync function sha256Hex(input: string | Uint8Array): Promise<string> {\n const bytes = typeof input === 'string' ? new TextEncoder().encode(input) : input;\n\n // Node.js Web Crypto (Node 18+) supports digest. Browser Web Crypto does too.\n // Fall back to node:crypto if Web Crypto is unavailable.\n const subtle = (globalThis as any).crypto?.subtle;\n if (subtle) {\n const buf = await subtle.digest('SHA-256', bytes);\n return Array.from(new Uint8Array(buf))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n }\n\n // Node fallback\n const nodeCrypto = await import('node:crypto');\n return nodeCrypto.createHash('sha256').update(bytes).digest('hex');\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * Hash a single tool definition. Returns `sha256:<hex>`.\n *\n * Canonical form (v1):\n * JCS-canonicalised JSON of `{name, description, schema}` where\n * absent fields substitute `description: \"\"` and `schema: {}`.\n */\nexport async function hashTool(tool: CatalogueTool): Promise<string> {\n const normalised = {\n name: tool.name,\n description: tool.description ?? '',\n schema: tool.schema ?? {},\n };\n const hex = await sha256Hex(jcsCanonicalise(normalised));\n return `sha256:${hex}`;\n}\n\n/**\n * Hash an entire tool catalogue. Returns `sha256:<hex>`.\n *\n * Reordering tools does NOT change the hash (tool hashes sorted\n * lexicographically before concatenation). Adding, removing, or\n * modifying a tool DOES change the hash.\n *\n * Per-tool hashing first means a verifier comparing two catalogue\n * hashes that differ can also be given the per-tool hashes to\n * identify which specific tool changed.\n */\nexport async function hashToolCatalogue(tools: CatalogueTool[]): Promise<string> {\n if (tools.length === 0) {\n // Empty catalogue has a deterministic, stable hash distinct from \"no field\"\n return `sha256:${await sha256Hex('[]')}`;\n }\n const perTool = await Promise.all(tools.map(hashTool));\n perTool.sort();\n const hex = await sha256Hex(perTool.join(''));\n return `sha256:${hex}`;\n}\n\n/**\n * Hash a system prompt. Returns `sha256:<hex>`.\n *\n * The prompt is UTF-8 encoded and hashed verbatim — no normalisation.\n * If a caller wants to ignore whitespace or comment differences, they\n * should normalise before calling. The intent here is deterministic\n * reproducibility, not semantic equivalence.\n */\nexport async function hashSystemPrompt(prompt: string): Promise<string> {\n const hex = await sha256Hex(prompt);\n return `sha256:${hex}`;\n}\n\n/**\n * Convenience: hash the previous event's canonical bytes for use in\n * the prev_event_hash field of hash-chained ingest. Caller provides\n * the canonical bytes (use canonicalPayloadV0 from the main module).\n */\nexport async function hashPreviousEvent(canonicalBytes: string): Promise<string> {\n const hex = await sha256Hex(canonicalBytes);\n return `sha256:${hex}`;\n}\n\n// ─── Replay context — framework integration helper ────────────────────────\n\n/**\n * Raw inputs the integration captures from the framework. Set by the\n * integration's config; turned into hashes by {@link prepareReplayContext}.\n */\nexport interface ReplayInputs {\n /** Stable identifier for the model. E.g. `\"anthropic:claude-opus-4-8\"`. */\n modelVersion?: string;\n /** Tools available to the agent at call time. Hashed via {@link hashToolCatalogue}. */\n tools?: CatalogueTool[];\n /** System prompt active at call time. Hashed via {@link hashSystemPrompt}. */\n systemPrompt?: string;\n}\n\n/**\n * Pre-computed hashes ready to attach to audit events. The three keys\n * match the audit_events column names.\n */\nexport interface ReplayContext {\n model_version?: string;\n tool_catalogue_hash?: string;\n system_prompt_hash?: string;\n}\n\n/**\n * Compute replay context once from raw inputs. Use at integration\n * setup time so each emit can attach the hashes without re-hashing.\n *\n * Returns an object suitable for spreading into an AuditEventInput:\n * `await vorim.emit({ ...event, ...replayContext })`\n *\n * If a field is absent in the inputs, it is absent in the result\n * (not the empty string). That keeps the event lean.\n */\nexport async function prepareReplayContext(\n inputs: ReplayInputs,\n): Promise<ReplayContext> {\n const ctx: ReplayContext = {};\n if (inputs.modelVersion) ctx.model_version = inputs.modelVersion;\n if (inputs.tools) ctx.tool_catalogue_hash = await hashToolCatalogue(inputs.tools);\n if (inputs.systemPrompt) ctx.system_prompt_hash = await hashSystemPrompt(inputs.systemPrompt);\n return ctx;\n}\n","// ============================================================================\n// VORIM SDK — LangChain / LangGraph JS Integration\n// Provides tool wrapping with permission checks + audit trail,\n// a callback handler for observability, and a factory for creating\n// Vorim-secured LangGraph ReAct agents.\n//\n// Peer dependencies:\n// @langchain/core >=0.3.0\n// @langchain/langgraph >=0.2.0\n// ============================================================================\n\nimport type { VorimSDK } from '../index.js';\nimport type { PermissionScope, AuditEventInput } from '../types.js';\nimport {\n prepareReplayContext,\n type ReplayInputs,\n type ReplayContext,\n type CatalogueTool,\n} from '../replay.js';\n\n// ─── Re-declared LangChain types (peer dependency — not bundled) ──────────\n// These mirror the actual types from @langchain/core so consumers don't\n// need to import them separately for basic usage.\n\n/** Minimal subset of @langchain/core Serialized */\ninterface Serialized {\n lc: number;\n type: string;\n id: string[];\n kwargs?: Record<string, unknown>;\n}\n\n// ─── Configuration ────────────────────────────────────────────────────────\n\nexport interface VorimLangChainConfig {\n /** Vorim SDK instance. */\n vorim: VorimSDK;\n /** The Vorim agent_id to associate with this LangChain agent. */\n agentId: string;\n /** Map tool names → Vorim permission scopes. Unmapped tools default to 'agent:execute'. */\n permissionMap?: Record<string, PermissionScope>;\n /** Default permission scope for tools not in permissionMap. @default 'agent:execute' */\n defaultPermission?: PermissionScope;\n /** Whether to emit audit events asynchronously (fire-and-forget). @default true */\n asyncAudit?: boolean;\n /**\n * Replayable agent decision evidence (VAIP -02). When provided, the\n * hashes of the model version, tool catalogue, and system prompt are\n * attached to every emitted audit event so an auditor can later\n * reproduce the agent's decision context.\n *\n * Hashes are computed once on first emit and cached for subsequent\n * emits in this config's lifetime.\n *\n * These fields are NOT covered by the v0 canonical signature form;\n * advisory only until v1 (RFC 8785 JCS) lands.\n */\n replay?: ReplayInputs;\n}\n\n// ─── Tool Wrapper ─────────────────────────────────────────────────────────\n\n/**\n * Wraps a LangChain `StructuredTool` (or `DynamicStructuredTool`) with\n * Vorim permission checks before execution and audit event emission after.\n *\n * Works with any tool that has `name`, `description`, `schema`, and `_call`.\n *\n * @example\n * ```ts\n * import { DynamicStructuredTool } from \"@langchain/core/tools\";\n * import { wrapTool } from \"@vorim/sdk/integrations/langchain\";\n *\n * const guarded = wrapTool(myTool, {\n * vorim, agentId: \"agid_acme_a1b2c3d4\",\n * permissionMap: { search_docs: \"agent:read\" },\n * });\n * ```\n */\nexport function wrapTool<T extends LangChainTool>(\n tool: T,\n config: VorimLangChainConfig,\n): T {\n const { vorim, agentId, permissionMap = {}, defaultPermission = 'agent:execute', asyncAudit = true, replay } = config;\n\n const originalCall = tool._call.bind(tool);\n const getReplayCtx = makeReplayContextGetter(replay);\n\n // Override _call to inject permission check + audit\n (tool as any)._call = async function vorimGuardedCall(\n arg: unknown,\n runManager?: unknown,\n parentConfig?: unknown,\n ): Promise<unknown> {\n const scope = permissionMap[tool.name] ?? defaultPermission;\n\n // 1. Permission check\n const { allowed, reason } = await vorim.check(agentId, scope);\n const replayCtx = await getReplayCtx();\n\n if (!allowed) {\n const event: AuditEventInput = {\n agent_id: agentId,\n event_type: 'tool_call',\n action: tool.name,\n resource: truncate(JSON.stringify(arg), 500),\n permission: scope,\n result: 'denied',\n metadata: { reason, framework: 'langchain' },\n ...replayCtx,\n };\n emitAudit(vorim, event, asyncAudit);\n throw new Error(`Vorim: permission denied for \"${tool.name}\" — scope \"${scope}\"${reason ? `: ${reason}` : ''}`);\n }\n\n // 2. Execute the original tool\n const start = Date.now();\n let result: unknown;\n try {\n result = await originalCall(arg, runManager, parentConfig);\n } catch (err) {\n const event: AuditEventInput = {\n agent_id: agentId,\n event_type: 'tool_call',\n action: tool.name,\n resource: truncate(JSON.stringify(arg), 500),\n permission: scope,\n result: 'error',\n latency_ms: Date.now() - start,\n error_code: err instanceof Error ? err.name : 'UNKNOWN',\n metadata: { error: err instanceof Error ? err.message : String(err), framework: 'langchain' },\n ...replayCtx,\n };\n emitAudit(vorim, event, asyncAudit);\n throw err;\n }\n\n // 3. Audit success\n const event: AuditEventInput = {\n agent_id: agentId,\n event_type: 'tool_call',\n action: tool.name,\n resource: truncate(JSON.stringify(arg), 500),\n permission: scope,\n result: 'success',\n latency_ms: Date.now() - start,\n metadata: { framework: 'langchain' },\n ...replayCtx,\n };\n emitAudit(vorim, event, asyncAudit);\n\n return result;\n };\n\n return tool;\n}\n\n/**\n * Lazy-cached replay context getter. The hashes are computed once on\n * first call and reused for subsequent calls in this closure's lifetime.\n * Returns an empty object when no `replay` config was provided so the\n * spread is a no-op.\n */\nfunction makeReplayContextGetter(replay: ReplayInputs | undefined): () => Promise<ReplayContext> {\n if (!replay) return async () => ({});\n let cached: Promise<ReplayContext> | null = null;\n return () => {\n if (!cached) cached = prepareReplayContext(replay);\n return cached;\n };\n}\n\n/**\n * Wraps an array of LangChain tools with Vorim permission + audit.\n */\nexport function wrapTools<T extends LangChainTool>(\n tools: T[],\n config: VorimLangChainConfig,\n): T[] {\n return tools.map(t => wrapTool(t, config));\n}\n\n// ─── Callback Handler ─────────────────────────────────────────────────────\n\n/**\n * LangChain callback handler that emits Vorim audit events for every\n * tool invocation. This is a **non-intrusive** observability layer —\n * it does not block or modify tool execution.\n *\n * Attach it to any LangChain invoke/stream call:\n * ```ts\n * const handler = new VorimCallbackHandler(vorim, \"agid_acme_a1b2c3d4\");\n * await agent.invoke({ messages }, { callbacks: [handler] });\n * ```\n */\nexport class VorimCallbackHandler {\n name = 'VorimCallbackHandler';\n private vorim: VorimSDK;\n private agentId: string;\n private runMap = new Map<string, { tool: string; input: string; startTime: number }>();\n private getReplayCtx: () => Promise<ReplayContext>;\n\n // Flags to satisfy BaseCallbackHandler interface\n ignoreLLM = true;\n ignoreChain = true;\n ignoreAgent = true;\n ignoreRetriever = true;\n ignoreCustomEvent = true;\n lc_serializable = false;\n\n get lc_id(): string[] { return ['vorim', 'callbacks', 'VorimCallbackHandler']; }\n get lc_namespace(): string[] { return ['vorim', 'callbacks']; }\n\n /**\n * @param vorim Vorim SDK instance.\n * @param agentId The Vorim agent_id whose actions this callback audits.\n * @param replay Optional replayable-evidence inputs. Same shape as\n * {@link VorimLangChainConfig.replay}; hashes are\n * computed once and attached to every audit event.\n */\n constructor(vorim: VorimSDK, agentId: string, replay?: ReplayInputs) {\n this.vorim = vorim;\n this.agentId = agentId;\n this.getReplayCtx = makeReplayContextGetter(replay);\n }\n\n async handleToolStart(\n tool: Serialized,\n input: string,\n runId: string,\n _parentRunId?: string,\n _tags?: string[],\n _metadata?: Record<string, unknown>,\n runName?: string,\n ): Promise<void> {\n const toolName = runName ?? tool.id?.[tool.id.length - 1] ?? 'unknown';\n this.runMap.set(runId, { tool: toolName, input, startTime: Date.now() });\n }\n\n async handleToolEnd(\n _output: unknown,\n runId: string,\n ): Promise<void> {\n const run = this.runMap.get(runId);\n if (!run) return;\n this.runMap.delete(runId);\n const replayCtx = await this.getReplayCtx();\n\n await this.vorim.emit({\n agent_id: this.agentId,\n event_type: 'tool_call',\n action: run.tool,\n resource: truncate(run.input, 500),\n result: 'success',\n latency_ms: Date.now() - run.startTime,\n metadata: { framework: 'langchain' },\n ...replayCtx,\n }).catch(() => {}); // never throw from callback\n }\n\n async handleToolError(\n err: Error,\n runId: string,\n ): Promise<void> {\n const run = this.runMap.get(runId);\n if (!run) return;\n this.runMap.delete(runId);\n const replayCtx = await this.getReplayCtx();\n\n await this.vorim.emit({\n agent_id: this.agentId,\n event_type: 'tool_call',\n action: run.tool,\n resource: truncate(run.input, 500),\n result: 'error',\n latency_ms: Date.now() - run.startTime,\n error_code: err.name,\n metadata: { error: err.message, framework: 'langchain' },\n ...replayCtx,\n }).catch(() => {});\n }\n\n // Required by BaseCallbackHandler — no-op for events we don't need\n copy(): VorimCallbackHandler {\n return new VorimCallbackHandler(this.vorim, this.agentId);\n }\n\n toJSON() {\n return { lc: 1, type: 'not_implemented', id: this.lc_id };\n }\n\n toJSONNotImplemented() {\n return this.toJSON();\n }\n}\n\n// ─── Agent Factory ────────────────────────────────────────────────────────\n\nexport interface CreateVorimAgentConfig extends VorimLangChainConfig {\n /** Display name for the agent when registering with Vorim. */\n name: string;\n /** Agent capabilities (e.g. [\"web_search\", \"code_execution\"]). */\n capabilities: string[];\n /** Initial permission scopes to grant. */\n scopes: PermissionScope[];\n /** Optional description. */\n description?: string;\n}\n\n/**\n * Registers a new agent with Vorim and returns wrapped tools + callback handler\n * ready to use with `createReactAgent` or any LangChain agent.\n *\n * @example\n * ```ts\n * import { createReactAgent } from \"@langchain/langgraph/prebuilt\";\n * import { ChatOpenAI } from \"@langchain/openai\";\n * import { createVorimAgent } from \"@vorim/sdk/integrations/langchain\";\n *\n * const { agentId, tools, callbackHandler } = await createVorimAgent({\n * vorim,\n * name: \"research-agent\",\n * capabilities: [\"web_search\"],\n * scopes: [\"agent:read\", \"agent:execute\"],\n * tools: [searchTool, analysisTool],\n * });\n *\n * const agent = createReactAgent({\n * llm: new ChatOpenAI({ model: \"gpt-4o\" }),\n * tools,\n * });\n *\n * const result = await agent.invoke(\n * { messages: [{ role: \"user\", content: \"Research AI trends\" }] },\n * { callbacks: [callbackHandler] },\n * );\n * ```\n */\nexport async function createVorimAgent<T extends LangChainTool>(config: CreateVorimAgentConfig & { tools: T[] }) {\n const { vorim, name, capabilities, scopes, description, tools: rawTools, permissionMap, defaultPermission, asyncAudit, replay } = config;\n\n // Register agent with Vorim\n const registration = await vorim.register({\n name,\n description,\n capabilities,\n scopes,\n });\n\n const agentId = registration.agent.agent_id;\n\n // If the caller passed `replay.tools`, use those. Otherwise derive a\n // tool catalogue from the rawTools we're about to wrap so the\n // replayable-evidence story works automatically.\n const derivedReplay: ReplayInputs | undefined = replay\n ? { ...replay, tools: replay.tools ?? deriveCatalogueFromLangChainTools(rawTools) }\n : { tools: deriveCatalogueFromLangChainTools(rawTools) };\n\n const agentConfig: VorimLangChainConfig = {\n vorim, agentId, permissionMap, defaultPermission, asyncAudit, replay: derivedReplay,\n };\n\n // Wrap tools with permission checks\n const tools = wrapTools(rawTools, agentConfig);\n\n // Create callback handler for audit trail\n const callbackHandler = new VorimCallbackHandler(vorim, agentId, derivedReplay);\n\n return {\n /** The Vorim agent_id. */\n agentId,\n /** The full agent registration result. */\n registration,\n /** Tools wrapped with Vorim permission checks + audit. */\n tools,\n /** Callback handler for audit trail observability. */\n callbackHandler,\n /** The private key (store securely — shown once). */\n privateKey: registration.private_key,\n };\n}\n\n// ─── Internal helpers ─────────────────────────────────────────────────────\n\n/** Minimal interface matching any LangChain StructuredTool / DynamicStructuredTool. */\ninterface LangChainTool {\n name: string;\n description: string;\n schema: unknown;\n _call(arg: any, runManager?: any, parentConfig?: any): Promise<any>;\n}\n\nfunction truncate(str: string, max: number): string {\n return str.length > max ? str.slice(0, max) + '…' : str;\n}\n\nfunction emitAudit(vorim: VorimSDK, event: AuditEventInput, async: boolean): void {\n if (async) {\n vorim.emit(event).catch(() => {});\n } else {\n vorim.emit(event).catch(() => {});\n }\n}\n\n/**\n * Convert LangChain tools to the catalogue shape used by hashToolCatalogue.\n * Best-effort: LangChain tool schemas can be Zod or plain JSON Schema.\n * If we can't get a serialisable shape, we fall back to `null` schema\n * which the hashing treats as `{}`.\n */\nfunction deriveCatalogueFromLangChainTools(tools: LangChainTool[]): CatalogueTool[] {\n return tools.map(t => {\n let schema: Record<string, unknown> | null = null;\n const raw = t.schema as unknown;\n if (raw && typeof raw === 'object') {\n // If it's already plain JSON-serialisable, use it. Otherwise null.\n try {\n schema = JSON.parse(JSON.stringify(raw));\n } catch {\n schema = null;\n }\n }\n return {\n name: t.name,\n description: t.description ?? '',\n schema,\n };\n });\n}\n"],"mappings":"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","names":["event"]}
@@ -55,10 +55,9 @@ function jcsCanonicalise(value) {
55
55
  return "[" + value.map(jcsCanonicalise).join(",") + "]";
56
56
  }
57
57
  if (typeof value === "object") {
58
- const keys = Object.keys(value).sort();
59
- const parts = keys.map((k) => {
60
- return JSON.stringify(k) + ":" + jcsCanonicalise(value[k]);
61
- });
58
+ const obj = value;
59
+ const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
60
+ const parts = keys.map((k) => JSON.stringify(k) + ":" + jcsCanonicalise(obj[k]));
62
61
  return "{" + parts.join(",") + "}";
63
62
  }
64
63
  throw new Error(`jcsCanonicalise: unsupported value type: ${typeof value}`);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/integrations/llamaindex.ts","../../src/replay.ts"],"sourcesContent":["// ============================================================================\n// VORIM SDK — LlamaIndex TS Integration\n// Wraps LlamaIndex tools with Vorim permission checks + audit trails.\n// Provides a tool wrapper and agent registration factory.\n//\n// Peer dependency: llamaindex >=0.4.0 (or @llamaindex/core)\n// ============================================================================\n\nimport type { VorimSDK } from '../index.js';\nimport type { PermissionScope, AuditEventInput } from '../types.js';\nimport {\n prepareReplayContext,\n type ReplayInputs,\n type ReplayContext,\n type CatalogueTool,\n} from '../replay.js';\n\n// ─── Re-declared LlamaIndex types (peer dependency — not bundled) ─────────\n// These mirror the actual interfaces from @llamaindex/core/llms and\n// @llamaindex/core/tools so consumers don't need to wrangle imports.\n\n/** Matches @llamaindex/core ToolMetadata */\ninterface ToolMetadata<P extends Record<string, unknown> = Record<string, unknown>> {\n name: string;\n description: string;\n parameters?: P;\n}\n\n/**\n * Matches @llamaindex/core BaseTool.\n * In LlamaIndex, `call` is optional on BaseTool but required on BaseToolWithCall.\n */\ninterface LlamaIndexTool<Input = any> {\n metadata: ToolMetadata;\n call: (input: Input) => any | Promise<any>;\n}\n\n// ─── Configuration ────────────────────────────────────────────────────────\n\nexport interface VorimLlamaIndexConfig {\n /** Vorim SDK instance. */\n vorim: VorimSDK;\n /** The Vorim agent_id to associate. */\n agentId: string;\n /** Map tool names → Vorim permission scopes. */\n permissionMap?: Record<string, PermissionScope>;\n /** Default permission scope for unmapped tools. @default 'agent:execute' */\n defaultPermission?: PermissionScope;\n /** Whether to emit audit events asynchronously (fire-and-forget). @default true */\n asyncAudit?: boolean;\n /**\n * Replayable agent decision evidence (VAIP -02). Hashes attached to\n * every audit event the wrapper emits. Not covered by v0 canonical\n * signature form.\n */\n replay?: ReplayInputs;\n}\n\n// ─── Tool Wrapper ─────────────────────────────────────────────────────────\n\n/**\n * Wraps a LlamaIndex tool (`BaseTool` / `BaseToolWithCall` / `FunctionTool`)\n * with Vorim permission checks before execution and audit event emission after.\n *\n * The wrapper implements the same `BaseTool` interface so it's a drop-in\n * replacement anywhere LlamaIndex expects a tool.\n *\n * @example\n * ```ts\n * import { FunctionTool } from \"llamaindex\";\n * import createVorim from \"@vorim/sdk\";\n * import { wrapTool } from \"@vorim/sdk/integrations/llamaindex\";\n *\n * const vorim = createVorim({ apiKey: \"agid_sk_live_...\" });\n *\n * const searchTool = FunctionTool.from(\n * async ({ query }: { query: string }) => `Results for: ${query}`,\n * { name: \"search\", description: \"Search documents\", parameters: { ... } }\n * );\n *\n * const guarded = wrapTool(searchTool, {\n * vorim,\n * agentId: \"agid_acme_a1b2c3d4\",\n * permissionMap: { search: \"agent:read\" },\n * });\n *\n * // Use with any LlamaIndex agent\n * const agent = new OpenAIAgent({ tools: [guarded] });\n * ```\n */\nexport function wrapTool<T extends LlamaIndexTool>(\n tool: T,\n config: VorimLlamaIndexConfig,\n): T {\n const {\n vorim, agentId,\n permissionMap = {},\n defaultPermission = 'agent:execute',\n asyncAudit = true,\n replay,\n } = config;\n\n const originalCall = tool.call.bind(tool);\n const toolName = tool.metadata.name;\n const scope = permissionMap[toolName] ?? defaultPermission;\n const getReplayCtx = makeReplayContextGetter(replay);\n\n // Create a new object that preserves the tool's prototype chain\n // and all properties, but overrides `call`\n const wrapped = Object.create(Object.getPrototypeOf(tool), {\n ...Object.getOwnPropertyDescriptors(tool),\n call: {\n value: async function vorimGuardedCall(input: any): Promise<any> {\n // 1. Permission check\n const { allowed, reason } = await vorim.check(agentId, scope);\n const replayCtx = await getReplayCtx();\n\n if (!allowed) {\n const event: AuditEventInput = {\n agent_id: agentId,\n event_type: 'tool_call',\n action: toolName,\n resource: truncate(JSON.stringify(input), 500),\n permission: scope,\n result: 'denied',\n metadata: { reason, framework: 'llamaindex' },\n ...replayCtx,\n };\n emitAudit(vorim, event, asyncAudit);\n throw new Error(\n `Vorim: permission denied for \"${toolName}\" — scope \"${scope}\"${reason ? `: ${reason}` : ''}`,\n );\n }\n\n // 2. Execute the original tool\n const start = Date.now();\n let result: any;\n try {\n result = await originalCall(input);\n } catch (err) {\n const event: AuditEventInput = {\n agent_id: agentId,\n event_type: 'tool_call',\n action: toolName,\n resource: truncate(JSON.stringify(input), 500),\n permission: scope,\n result: 'error',\n latency_ms: Date.now() - start,\n error_code: err instanceof Error ? err.name : 'UNKNOWN',\n metadata: {\n error: err instanceof Error ? err.message : String(err),\n framework: 'llamaindex',\n },\n ...replayCtx,\n };\n emitAudit(vorim, event, asyncAudit);\n throw err;\n }\n\n // 3. Audit success\n const event: AuditEventInput = {\n agent_id: agentId,\n event_type: 'tool_call',\n action: toolName,\n resource: truncate(JSON.stringify(input), 500),\n permission: scope,\n result: 'success',\n latency_ms: Date.now() - start,\n metadata: { framework: 'llamaindex' },\n ...replayCtx,\n };\n emitAudit(vorim, event, asyncAudit);\n\n return result;\n },\n writable: true,\n configurable: true,\n enumerable: true,\n },\n }) as T;\n\n return wrapped;\n}\n\n/**\n * Wraps an array of LlamaIndex tools with Vorim permission + audit.\n */\nexport function wrapTools<T extends LlamaIndexTool>(\n tools: T[],\n config: VorimLlamaIndexConfig,\n): T[] {\n return tools.map(t => wrapTool(t, config));\n}\n\n// ─── Agent Factory ────────────────────────────────────────────────────────\n\nexport interface CreateVorimLlamaIndexAgentConfig extends VorimLlamaIndexConfig {\n /** Display name for the agent. */\n name: string;\n /** Agent capabilities. */\n capabilities: string[];\n /** Initial permission scopes. */\n scopes: PermissionScope[];\n /** Optional description. */\n description?: string;\n}\n\n/**\n * Registers a new agent with Vorim and returns wrapped tools ready for\n * use with any LlamaIndex agent (OpenAIAgent, ReActAgent, etc.).\n *\n * @example\n * ```ts\n * import { OpenAIAgent, FunctionTool } from \"llamaindex\";\n * import createVorim from \"@vorim/sdk\";\n * import { createVorimAgent } from \"@vorim/sdk/integrations/llamaindex\";\n *\n * const vorim = createVorim({ apiKey: \"agid_sk_live_...\" });\n *\n * const searchTool = FunctionTool.from(...);\n * const writeTool = FunctionTool.from(...);\n *\n * const { agentId, tools } = await createVorimAgent({\n * vorim,\n * name: \"research-agent\",\n * capabilities: [\"search\", \"write\"],\n * scopes: [\"agent:read\", \"agent:write\", \"agent:execute\"],\n * tools: [searchTool, writeTool],\n * permissionMap: {\n * search: \"agent:read\",\n * write: \"agent:write\",\n * },\n * });\n *\n * // Create LlamaIndex agent with Vorim-wrapped tools\n * const agent = new OpenAIAgent({ tools });\n * const response = await agent.chat({ message: \"Research AI trends\" });\n * ```\n */\nexport async function createVorimAgent<T extends LlamaIndexTool>(\n config: CreateVorimLlamaIndexAgentConfig & { tools: T[] },\n) {\n const { vorim, name, capabilities, scopes, description, tools: rawTools, permissionMap, defaultPermission, asyncAudit } = config;\n\n // Register agent with Vorim\n const registration = await vorim.register({\n name,\n description,\n capabilities,\n scopes,\n });\n\n const agentId = registration.agent.agent_id;\n const toolConfig: VorimLlamaIndexConfig = { vorim, agentId, permissionMap, defaultPermission, asyncAudit };\n\n // Wrap tools with permission checks\n const tools = wrapTools(rawTools, toolConfig);\n\n return {\n /** The Vorim agent_id. */\n agentId,\n /** Full registration result. */\n registration,\n /** Tools wrapped with Vorim permission checks + audit. */\n tools,\n /** The private key (store securely — shown once). */\n privateKey: registration.private_key,\n };\n}\n\n// ─── Audit Helpers ────────────────────────────────────────────────────────\n\n/**\n * Emit a manual audit event for a LlamaIndex agent action that isn't\n * captured by the tool wrapper (e.g. RAG retrieval, chat responses).\n */\nexport async function emitLlamaIndexEvent(\n vorim: VorimSDK,\n agentId: string,\n event: {\n action: string;\n resource?: string;\n result: 'success' | 'denied' | 'error';\n latencyMs?: number;\n error?: string;\n metadata?: Record<string, unknown>;\n },\n): Promise<void> {\n await vorim.emit({\n agent_id: agentId,\n event_type: 'api_request',\n action: event.action,\n resource: event.resource,\n result: event.result,\n latency_ms: event.latencyMs,\n error_code: event.error ? 'LLAMAINDEX_ERROR' : undefined,\n metadata: {\n framework: 'llamaindex',\n ...(event.error ? { error: event.error } : {}),\n ...event.metadata,\n },\n });\n}\n\n// ─── Internal helpers ─────────────────────────────────────────────────────\n\nfunction truncate(str: string, max: number): string {\n return str.length > max ? str.slice(0, max) + '…' : str;\n}\n\nfunction emitAudit(vorim: VorimSDK, event: AuditEventInput, async: boolean): void {\n if (async) {\n vorim.emit(event).catch(() => {});\n } else {\n vorim.emit(event).catch(() => {});\n }\n}\n\n/**\n * Lazy-cached replay context getter. Hashes computed once on first\n * call, reused thereafter. Returns empty object when no replay config\n * was given, so the spread is a no-op.\n */\nfunction makeReplayContextGetter(replay: ReplayInputs | undefined): () => Promise<ReplayContext> {\n if (!replay) return async () => ({});\n let cached: Promise<ReplayContext> | null = null;\n return () => {\n if (!cached) cached = prepareReplayContext(replay);\n return cached;\n };\n}\n","/**\n * Replayable agent decision evidence helpers.\n *\n * Canonical-form hashing for the VAIP -02 schema fields that the SDK\n * attaches to audit events. The hashes recorded in audit_events.tool_catalogue_hash\n * and audit_events.system_prompt_hash use these functions, so the bytes\n * an auditor or counterparty reconstructs must match what the SDK produced.\n *\n * These helpers are intentionally separate from the signing path. The\n * v0 canonical signature form (event_type|action|resource|input_hash|\n * output_hash|result) does NOT cover model_version, tool_catalogue_hash,\n * or system_prompt_hash. They will enter the canonical bytes in v1\n * (RFC 8785 JCS) in a follow-up release.\n *\n * Stable across SDK versions: the canonical-form version is documented\n * in CANONICAL_TOOL_CATALOGUE_VERSION. Future changes get a v2 etc;\n * never edit the existing v1 logic, or already-recorded hashes lose\n * their meaning.\n */\n\n// ─── Versioning ───────────────────────────────────────────────────────────\n\n/**\n * Canonical-form version for tool catalogue hashes produced by this SDK.\n * Recorded in tool_catalogue_canon_version on the event metadata (when\n * the metadata field is used) so verifiers know which hash recipe to\n * reproduce. Increment ONLY if the algorithm changes in a way that\n * would change the hash for the same logical catalogue.\n */\nexport const CANONICAL_TOOL_CATALOGUE_VERSION = 'v1' as const;\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\n/**\n * Minimum shape a tool needs for catalogue hashing. The framework\n * integrations adapt their native tool objects to this shape before\n * calling hashToolCatalogue.\n */\nexport interface CatalogueTool {\n /** The name the model sees and calls. Required. */\n name: string;\n /** Human-readable description shown to the model. Optional; absent ↔ empty string. */\n description?: string;\n /**\n * JSON Schema describing the tool's input parameters. Optional;\n * absent ↔ empty object `{}`. The schema gets RFC 8785 JCS-canonicalised\n * before hashing so semantically-equivalent variations (key order,\n * whitespace) produce the same hash.\n */\n schema?: Record<string, unknown> | null;\n}\n\n// ─── RFC 8785 JCS subset ──────────────────────────────────────────────────\n\n/**\n * RFC 8785 JSON Canonicalization Scheme, sufficient subset for tool\n * catalogue values.\n *\n * Rules:\n * - Object keys sorted lexicographically by UTF-16 code units (which\n * is what JS string comparison does naturally).\n * - No whitespace between tokens.\n * - Numbers: integers as integers, finite floats per ECMAScript\n * Number.prototype.toString. JCS forbids NaN and Infinity.\n * - Strings: JSON-escape using minimal set per RFC 8259 § 7.\n * - null, true, false, arrays: as JSON.stringify produces them, since\n * JSON.stringify already produces the canonical form for these.\n *\n * Not vendoring a full library because tool schemas don't carry\n * non-integer numbers in practice and the JS spec for Number.toString\n * happens to coincide with JCS § 3.2.2.2 for the integer case.\n */\nexport function jcsCanonicalise(value: unknown): string {\n if (value === null) return 'null';\n if (value === true) return 'true';\n if (value === false) return 'false';\n\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) {\n throw new Error('jcsCanonicalise: NaN and Infinity are not JCS-valid');\n }\n // For integers in safe range, .toString() matches JCS. For\n // non-integer floats, .toString() also matches in modern JS\n // engines (V8, JavaScriptCore, SpiderMonkey all use the shortest\n // round-trip representation, which is what JCS § 3.2.2.2 requires).\n return value.toString();\n }\n\n if (typeof value === 'string') {\n return JSON.stringify(value);\n }\n\n if (Array.isArray(value)) {\n return '[' + value.map(jcsCanonicalise).join(',') + ']';\n }\n\n if (typeof value === 'object') {\n const keys = Object.keys(value as Record<string, unknown>).sort();\n const parts = keys.map(k => {\n return JSON.stringify(k) + ':' + jcsCanonicalise((value as Record<string, unknown>)[k]);\n });\n return '{' + parts.join(',') + '}';\n }\n\n // undefined, function, symbol, bigint — not JSON-representable\n throw new Error(`jcsCanonicalise: unsupported value type: ${typeof value}`);\n}\n\n// ─── SHA-256 ──────────────────────────────────────────────────────────────\n\nasync function sha256Hex(input: string | Uint8Array): Promise<string> {\n const bytes = typeof input === 'string' ? new TextEncoder().encode(input) : input;\n\n // Node.js Web Crypto (Node 18+) supports digest. Browser Web Crypto does too.\n // Fall back to node:crypto if Web Crypto is unavailable.\n const subtle = (globalThis as any).crypto?.subtle;\n if (subtle) {\n const buf = await subtle.digest('SHA-256', bytes);\n return Array.from(new Uint8Array(buf))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n }\n\n // Node fallback\n const nodeCrypto = await import('node:crypto');\n return nodeCrypto.createHash('sha256').update(bytes).digest('hex');\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * Hash a single tool definition. Returns `sha256:<hex>`.\n *\n * Canonical form (v1):\n * JCS-canonicalised JSON of `{name, description, schema}` where\n * absent fields substitute `description: \"\"` and `schema: {}`.\n */\nexport async function hashTool(tool: CatalogueTool): Promise<string> {\n const normalised = {\n name: tool.name,\n description: tool.description ?? '',\n schema: tool.schema ?? {},\n };\n const hex = await sha256Hex(jcsCanonicalise(normalised));\n return `sha256:${hex}`;\n}\n\n/**\n * Hash an entire tool catalogue. Returns `sha256:<hex>`.\n *\n * Reordering tools does NOT change the hash (tool hashes sorted\n * lexicographically before concatenation). Adding, removing, or\n * modifying a tool DOES change the hash.\n *\n * Per-tool hashing first means a verifier comparing two catalogue\n * hashes that differ can also be given the per-tool hashes to\n * identify which specific tool changed.\n */\nexport async function hashToolCatalogue(tools: CatalogueTool[]): Promise<string> {\n if (tools.length === 0) {\n // Empty catalogue has a deterministic, stable hash distinct from \"no field\"\n return `sha256:${await sha256Hex('[]')}`;\n }\n const perTool = await Promise.all(tools.map(hashTool));\n perTool.sort();\n const hex = await sha256Hex(perTool.join(''));\n return `sha256:${hex}`;\n}\n\n/**\n * Hash a system prompt. Returns `sha256:<hex>`.\n *\n * The prompt is UTF-8 encoded and hashed verbatim — no normalisation.\n * If a caller wants to ignore whitespace or comment differences, they\n * should normalise before calling. The intent here is deterministic\n * reproducibility, not semantic equivalence.\n */\nexport async function hashSystemPrompt(prompt: string): Promise<string> {\n const hex = await sha256Hex(prompt);\n return `sha256:${hex}`;\n}\n\n/**\n * Convenience: hash the previous event's canonical bytes for use in\n * the prev_event_hash field of hash-chained ingest. Caller provides\n * the canonical bytes (use canonicalPayloadV0 from the main module).\n */\nexport async function hashPreviousEvent(canonicalBytes: string): Promise<string> {\n const hex = await sha256Hex(canonicalBytes);\n return `sha256:${hex}`;\n}\n\n// ─── Replay context — framework integration helper ────────────────────────\n\n/**\n * Raw inputs the integration captures from the framework. Set by the\n * integration's config; turned into hashes by {@link prepareReplayContext}.\n */\nexport interface ReplayInputs {\n /** Stable identifier for the model. E.g. `\"anthropic:claude-opus-4-8\"`. */\n modelVersion?: string;\n /** Tools available to the agent at call time. Hashed via {@link hashToolCatalogue}. */\n tools?: CatalogueTool[];\n /** System prompt active at call time. Hashed via {@link hashSystemPrompt}. */\n systemPrompt?: string;\n}\n\n/**\n * Pre-computed hashes ready to attach to audit events. The three keys\n * match the audit_events column names.\n */\nexport interface ReplayContext {\n model_version?: string;\n tool_catalogue_hash?: string;\n system_prompt_hash?: string;\n}\n\n/**\n * Compute replay context once from raw inputs. Use at integration\n * setup time so each emit can attach the hashes without re-hashing.\n *\n * Returns an object suitable for spreading into an AuditEventInput:\n * `await vorim.emit({ ...event, ...replayContext })`\n *\n * If a field is absent in the inputs, it is absent in the result\n * (not the empty string). That keeps the event lean.\n */\nexport async function prepareReplayContext(\n inputs: ReplayInputs,\n): Promise<ReplayContext> {\n const ctx: ReplayContext = {};\n if (inputs.modelVersion) ctx.model_version = inputs.modelVersion;\n if (inputs.tools) ctx.tool_catalogue_hash = await hashToolCatalogue(inputs.tools);\n if (inputs.systemPrompt) ctx.system_prompt_hash = await hashSystemPrompt(inputs.systemPrompt);\n return ctx;\n}\n"],"mappings":"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","names":["event"]}
1
+ {"version":3,"sources":["../../src/integrations/llamaindex.ts","../../src/replay.ts"],"sourcesContent":["// ============================================================================\n// VORIM SDK — LlamaIndex TS Integration\n// Wraps LlamaIndex tools with Vorim permission checks + audit trails.\n// Provides a tool wrapper and agent registration factory.\n//\n// Peer dependency: llamaindex >=0.4.0 (or @llamaindex/core)\n// ============================================================================\n\nimport type { VorimSDK } from '../index.js';\nimport type { PermissionScope, AuditEventInput } from '../types.js';\nimport {\n prepareReplayContext,\n type ReplayInputs,\n type ReplayContext,\n type CatalogueTool,\n} from '../replay.js';\n\n// ─── Re-declared LlamaIndex types (peer dependency — not bundled) ─────────\n// These mirror the actual interfaces from @llamaindex/core/llms and\n// @llamaindex/core/tools so consumers don't need to wrangle imports.\n\n/** Matches @llamaindex/core ToolMetadata */\ninterface ToolMetadata<P extends Record<string, unknown> = Record<string, unknown>> {\n name: string;\n description: string;\n parameters?: P;\n}\n\n/**\n * Matches @llamaindex/core BaseTool.\n * In LlamaIndex, `call` is optional on BaseTool but required on BaseToolWithCall.\n */\ninterface LlamaIndexTool<Input = any> {\n metadata: ToolMetadata;\n call: (input: Input) => any | Promise<any>;\n}\n\n// ─── Configuration ────────────────────────────────────────────────────────\n\nexport interface VorimLlamaIndexConfig {\n /** Vorim SDK instance. */\n vorim: VorimSDK;\n /** The Vorim agent_id to associate. */\n agentId: string;\n /** Map tool names → Vorim permission scopes. */\n permissionMap?: Record<string, PermissionScope>;\n /** Default permission scope for unmapped tools. @default 'agent:execute' */\n defaultPermission?: PermissionScope;\n /** Whether to emit audit events asynchronously (fire-and-forget). @default true */\n asyncAudit?: boolean;\n /**\n * Replayable agent decision evidence (VAIP -02). Hashes attached to\n * every audit event the wrapper emits. Not covered by v0 canonical\n * signature form.\n */\n replay?: ReplayInputs;\n}\n\n// ─── Tool Wrapper ─────────────────────────────────────────────────────────\n\n/**\n * Wraps a LlamaIndex tool (`BaseTool` / `BaseToolWithCall` / `FunctionTool`)\n * with Vorim permission checks before execution and audit event emission after.\n *\n * The wrapper implements the same `BaseTool` interface so it's a drop-in\n * replacement anywhere LlamaIndex expects a tool.\n *\n * @example\n * ```ts\n * import { FunctionTool } from \"llamaindex\";\n * import createVorim from \"@vorim/sdk\";\n * import { wrapTool } from \"@vorim/sdk/integrations/llamaindex\";\n *\n * const vorim = createVorim({ apiKey: \"agid_sk_live_...\" });\n *\n * const searchTool = FunctionTool.from(\n * async ({ query }: { query: string }) => `Results for: ${query}`,\n * { name: \"search\", description: \"Search documents\", parameters: { ... } }\n * );\n *\n * const guarded = wrapTool(searchTool, {\n * vorim,\n * agentId: \"agid_acme_a1b2c3d4\",\n * permissionMap: { search: \"agent:read\" },\n * });\n *\n * // Use with any LlamaIndex agent\n * const agent = new OpenAIAgent({ tools: [guarded] });\n * ```\n */\nexport function wrapTool<T extends LlamaIndexTool>(\n tool: T,\n config: VorimLlamaIndexConfig,\n): T {\n const {\n vorim, agentId,\n permissionMap = {},\n defaultPermission = 'agent:execute',\n asyncAudit = true,\n replay,\n } = config;\n\n const originalCall = tool.call.bind(tool);\n const toolName = tool.metadata.name;\n const scope = permissionMap[toolName] ?? defaultPermission;\n const getReplayCtx = makeReplayContextGetter(replay);\n\n // Create a new object that preserves the tool's prototype chain\n // and all properties, but overrides `call`\n const wrapped = Object.create(Object.getPrototypeOf(tool), {\n ...Object.getOwnPropertyDescriptors(tool),\n call: {\n value: async function vorimGuardedCall(input: any): Promise<any> {\n // 1. Permission check\n const { allowed, reason } = await vorim.check(agentId, scope);\n const replayCtx = await getReplayCtx();\n\n if (!allowed) {\n const event: AuditEventInput = {\n agent_id: agentId,\n event_type: 'tool_call',\n action: toolName,\n resource: truncate(JSON.stringify(input), 500),\n permission: scope,\n result: 'denied',\n metadata: { reason, framework: 'llamaindex' },\n ...replayCtx,\n };\n emitAudit(vorim, event, asyncAudit);\n throw new Error(\n `Vorim: permission denied for \"${toolName}\" — scope \"${scope}\"${reason ? `: ${reason}` : ''}`,\n );\n }\n\n // 2. Execute the original tool\n const start = Date.now();\n let result: any;\n try {\n result = await originalCall(input);\n } catch (err) {\n const event: AuditEventInput = {\n agent_id: agentId,\n event_type: 'tool_call',\n action: toolName,\n resource: truncate(JSON.stringify(input), 500),\n permission: scope,\n result: 'error',\n latency_ms: Date.now() - start,\n error_code: err instanceof Error ? err.name : 'UNKNOWN',\n metadata: {\n error: err instanceof Error ? err.message : String(err),\n framework: 'llamaindex',\n },\n ...replayCtx,\n };\n emitAudit(vorim, event, asyncAudit);\n throw err;\n }\n\n // 3. Audit success\n const event: AuditEventInput = {\n agent_id: agentId,\n event_type: 'tool_call',\n action: toolName,\n resource: truncate(JSON.stringify(input), 500),\n permission: scope,\n result: 'success',\n latency_ms: Date.now() - start,\n metadata: { framework: 'llamaindex' },\n ...replayCtx,\n };\n emitAudit(vorim, event, asyncAudit);\n\n return result;\n },\n writable: true,\n configurable: true,\n enumerable: true,\n },\n }) as T;\n\n return wrapped;\n}\n\n/**\n * Wraps an array of LlamaIndex tools with Vorim permission + audit.\n */\nexport function wrapTools<T extends LlamaIndexTool>(\n tools: T[],\n config: VorimLlamaIndexConfig,\n): T[] {\n return tools.map(t => wrapTool(t, config));\n}\n\n// ─── Agent Factory ────────────────────────────────────────────────────────\n\nexport interface CreateVorimLlamaIndexAgentConfig extends VorimLlamaIndexConfig {\n /** Display name for the agent. */\n name: string;\n /** Agent capabilities. */\n capabilities: string[];\n /** Initial permission scopes. */\n scopes: PermissionScope[];\n /** Optional description. */\n description?: string;\n}\n\n/**\n * Registers a new agent with Vorim and returns wrapped tools ready for\n * use with any LlamaIndex agent (OpenAIAgent, ReActAgent, etc.).\n *\n * @example\n * ```ts\n * import { OpenAIAgent, FunctionTool } from \"llamaindex\";\n * import createVorim from \"@vorim/sdk\";\n * import { createVorimAgent } from \"@vorim/sdk/integrations/llamaindex\";\n *\n * const vorim = createVorim({ apiKey: \"agid_sk_live_...\" });\n *\n * const searchTool = FunctionTool.from(...);\n * const writeTool = FunctionTool.from(...);\n *\n * const { agentId, tools } = await createVorimAgent({\n * vorim,\n * name: \"research-agent\",\n * capabilities: [\"search\", \"write\"],\n * scopes: [\"agent:read\", \"agent:write\", \"agent:execute\"],\n * tools: [searchTool, writeTool],\n * permissionMap: {\n * search: \"agent:read\",\n * write: \"agent:write\",\n * },\n * });\n *\n * // Create LlamaIndex agent with Vorim-wrapped tools\n * const agent = new OpenAIAgent({ tools });\n * const response = await agent.chat({ message: \"Research AI trends\" });\n * ```\n */\nexport async function createVorimAgent<T extends LlamaIndexTool>(\n config: CreateVorimLlamaIndexAgentConfig & { tools: T[] },\n) {\n const { vorim, name, capabilities, scopes, description, tools: rawTools, permissionMap, defaultPermission, asyncAudit } = config;\n\n // Register agent with Vorim\n const registration = await vorim.register({\n name,\n description,\n capabilities,\n scopes,\n });\n\n const agentId = registration.agent.agent_id;\n const toolConfig: VorimLlamaIndexConfig = { vorim, agentId, permissionMap, defaultPermission, asyncAudit };\n\n // Wrap tools with permission checks\n const tools = wrapTools(rawTools, toolConfig);\n\n return {\n /** The Vorim agent_id. */\n agentId,\n /** Full registration result. */\n registration,\n /** Tools wrapped with Vorim permission checks + audit. */\n tools,\n /** The private key (store securely — shown once). */\n privateKey: registration.private_key,\n };\n}\n\n// ─── Audit Helpers ────────────────────────────────────────────────────────\n\n/**\n * Emit a manual audit event for a LlamaIndex agent action that isn't\n * captured by the tool wrapper (e.g. RAG retrieval, chat responses).\n */\nexport async function emitLlamaIndexEvent(\n vorim: VorimSDK,\n agentId: string,\n event: {\n action: string;\n resource?: string;\n result: 'success' | 'denied' | 'error';\n latencyMs?: number;\n error?: string;\n metadata?: Record<string, unknown>;\n },\n): Promise<void> {\n await vorim.emit({\n agent_id: agentId,\n event_type: 'api_request',\n action: event.action,\n resource: event.resource,\n result: event.result,\n latency_ms: event.latencyMs,\n error_code: event.error ? 'LLAMAINDEX_ERROR' : undefined,\n metadata: {\n framework: 'llamaindex',\n ...(event.error ? { error: event.error } : {}),\n ...event.metadata,\n },\n });\n}\n\n// ─── Internal helpers ─────────────────────────────────────────────────────\n\nfunction truncate(str: string, max: number): string {\n return str.length > max ? str.slice(0, max) + '…' : str;\n}\n\nfunction emitAudit(vorim: VorimSDK, event: AuditEventInput, async: boolean): void {\n if (async) {\n vorim.emit(event).catch(() => {});\n } else {\n vorim.emit(event).catch(() => {});\n }\n}\n\n/**\n * Lazy-cached replay context getter. Hashes computed once on first\n * call, reused thereafter. Returns empty object when no replay config\n * was given, so the spread is a no-op.\n */\nfunction makeReplayContextGetter(replay: ReplayInputs | undefined): () => Promise<ReplayContext> {\n if (!replay) return async () => ({});\n let cached: Promise<ReplayContext> | null = null;\n return () => {\n if (!cached) cached = prepareReplayContext(replay);\n return cached;\n };\n}\n","/**\n * Replayable agent decision evidence helpers.\n *\n * Canonical-form hashing for the VAIP -02 schema fields that the SDK\n * attaches to audit events. The hashes recorded in audit_events.tool_catalogue_hash\n * and audit_events.system_prompt_hash use these functions, so the bytes\n * an auditor or counterparty reconstructs must match what the SDK produced.\n *\n * These helpers are intentionally separate from the signing path. The\n * v0 canonical signature form (event_type|action|resource|input_hash|\n * output_hash|result) does NOT cover model_version, tool_catalogue_hash,\n * or system_prompt_hash. They will enter the canonical bytes in v1\n * (RFC 8785 JCS) in a follow-up release.\n *\n * Stable across SDK versions: the canonical-form version is documented\n * in CANONICAL_TOOL_CATALOGUE_VERSION. Future changes get a v2 etc;\n * never edit the existing v1 logic, or already-recorded hashes lose\n * their meaning.\n */\n\n// ─── Versioning ───────────────────────────────────────────────────────────\n\n/**\n * Canonical-form version for tool catalogue hashes produced by this SDK.\n * Recorded in tool_catalogue_canon_version on the event metadata (when\n * the metadata field is used) so verifiers know which hash recipe to\n * reproduce. Increment ONLY if the algorithm changes in a way that\n * would change the hash for the same logical catalogue.\n */\nexport const CANONICAL_TOOL_CATALOGUE_VERSION = 'v1' as const;\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\n/**\n * Minimum shape a tool needs for catalogue hashing. The framework\n * integrations adapt their native tool objects to this shape before\n * calling hashToolCatalogue.\n */\nexport interface CatalogueTool {\n /** The name the model sees and calls. Required. */\n name: string;\n /** Human-readable description shown to the model. Optional; absent ↔ empty string. */\n description?: string;\n /**\n * JSON Schema describing the tool's input parameters. Optional;\n * absent ↔ empty object `{}`. The schema gets RFC 8785 JCS-canonicalised\n * before hashing so semantically-equivalent variations (key order,\n * whitespace) produce the same hash.\n */\n schema?: Record<string, unknown> | null;\n}\n\n// ─── RFC 8785 JCS subset ──────────────────────────────────────────────────\n\n/**\n * RFC 8785 JSON Canonicalization Scheme, sufficient subset for tool\n * catalogue values.\n *\n * Rules:\n * - Object keys sorted lexicographically by UTF-16 code units (which\n * is what JS string comparison does naturally).\n * - No whitespace between tokens.\n * - Numbers: integers as integers, finite floats per ECMAScript\n * Number.prototype.toString. JCS forbids NaN and Infinity.\n * - Strings: JSON-escape using minimal set per RFC 8259 § 7.\n * - null, true, false, arrays: as JSON.stringify produces them, since\n * JSON.stringify already produces the canonical form for these.\n *\n * Not vendoring a full library because tool schemas don't carry\n * non-integer numbers in practice and the JS spec for Number.toString\n * happens to coincide with JCS § 3.2.2.2 for the integer case.\n */\nexport function jcsCanonicalise(value: unknown): string {\n if (value === null) return 'null';\n if (value === true) return 'true';\n if (value === false) return 'false';\n\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) {\n throw new Error('jcsCanonicalise: NaN and Infinity are not JCS-valid');\n }\n // For integers in safe range, .toString() matches JCS. For\n // non-integer floats, .toString() also matches in modern JS\n // engines (V8, JavaScriptCore, SpiderMonkey all use the shortest\n // round-trip representation, which is what JCS § 3.2.2.2 requires).\n return value.toString();\n }\n\n if (typeof value === 'string') {\n return JSON.stringify(value);\n }\n\n if (Array.isArray(value)) {\n return '[' + value.map(jcsCanonicalise).join(',') + ']';\n }\n\n if (typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n // Filter undefined-valued fields, matching @vorim/verify and\n // @vorim/shared-types. Without this the SDK throws on { a: 1, b: undefined }\n // while the verifier silently drops b — a cross-module canonical-form\n // divergence that would break signature verification on such events.\n const keys = Object.keys(obj).filter(k => obj[k] !== undefined).sort();\n const parts = keys.map(k => JSON.stringify(k) + ':' + jcsCanonicalise(obj[k]));\n return '{' + parts.join(',') + '}';\n }\n\n // undefined, function, symbol, bigint — not JSON-representable\n throw new Error(`jcsCanonicalise: unsupported value type: ${typeof value}`);\n}\n\n// ─── SHA-256 ──────────────────────────────────────────────────────────────\n\nasync function sha256Hex(input: string | Uint8Array): Promise<string> {\n const bytes = typeof input === 'string' ? new TextEncoder().encode(input) : input;\n\n // Node.js Web Crypto (Node 18+) supports digest. Browser Web Crypto does too.\n // Fall back to node:crypto if Web Crypto is unavailable.\n const subtle = (globalThis as any).crypto?.subtle;\n if (subtle) {\n const buf = await subtle.digest('SHA-256', bytes);\n return Array.from(new Uint8Array(buf))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n }\n\n // Node fallback\n const nodeCrypto = await import('node:crypto');\n return nodeCrypto.createHash('sha256').update(bytes).digest('hex');\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * Hash a single tool definition. Returns `sha256:<hex>`.\n *\n * Canonical form (v1):\n * JCS-canonicalised JSON of `{name, description, schema}` where\n * absent fields substitute `description: \"\"` and `schema: {}`.\n */\nexport async function hashTool(tool: CatalogueTool): Promise<string> {\n const normalised = {\n name: tool.name,\n description: tool.description ?? '',\n schema: tool.schema ?? {},\n };\n const hex = await sha256Hex(jcsCanonicalise(normalised));\n return `sha256:${hex}`;\n}\n\n/**\n * Hash an entire tool catalogue. Returns `sha256:<hex>`.\n *\n * Reordering tools does NOT change the hash (tool hashes sorted\n * lexicographically before concatenation). Adding, removing, or\n * modifying a tool DOES change the hash.\n *\n * Per-tool hashing first means a verifier comparing two catalogue\n * hashes that differ can also be given the per-tool hashes to\n * identify which specific tool changed.\n */\nexport async function hashToolCatalogue(tools: CatalogueTool[]): Promise<string> {\n if (tools.length === 0) {\n // Empty catalogue has a deterministic, stable hash distinct from \"no field\"\n return `sha256:${await sha256Hex('[]')}`;\n }\n const perTool = await Promise.all(tools.map(hashTool));\n perTool.sort();\n const hex = await sha256Hex(perTool.join(''));\n return `sha256:${hex}`;\n}\n\n/**\n * Hash a system prompt. Returns `sha256:<hex>`.\n *\n * The prompt is UTF-8 encoded and hashed verbatim — no normalisation.\n * If a caller wants to ignore whitespace or comment differences, they\n * should normalise before calling. The intent here is deterministic\n * reproducibility, not semantic equivalence.\n */\nexport async function hashSystemPrompt(prompt: string): Promise<string> {\n const hex = await sha256Hex(prompt);\n return `sha256:${hex}`;\n}\n\n/**\n * Convenience: hash the previous event's canonical bytes for use in\n * the prev_event_hash field of hash-chained ingest. Caller provides\n * the canonical bytes (use canonicalPayloadV0 from the main module).\n */\nexport async function hashPreviousEvent(canonicalBytes: string): Promise<string> {\n const hex = await sha256Hex(canonicalBytes);\n return `sha256:${hex}`;\n}\n\n// ─── Replay context — framework integration helper ────────────────────────\n\n/**\n * Raw inputs the integration captures from the framework. Set by the\n * integration's config; turned into hashes by {@link prepareReplayContext}.\n */\nexport interface ReplayInputs {\n /** Stable identifier for the model. E.g. `\"anthropic:claude-opus-4-8\"`. */\n modelVersion?: string;\n /** Tools available to the agent at call time. Hashed via {@link hashToolCatalogue}. */\n tools?: CatalogueTool[];\n /** System prompt active at call time. Hashed via {@link hashSystemPrompt}. */\n systemPrompt?: string;\n}\n\n/**\n * Pre-computed hashes ready to attach to audit events. The three keys\n * match the audit_events column names.\n */\nexport interface ReplayContext {\n model_version?: string;\n tool_catalogue_hash?: string;\n system_prompt_hash?: string;\n}\n\n/**\n * Compute replay context once from raw inputs. Use at integration\n * setup time so each emit can attach the hashes without re-hashing.\n *\n * Returns an object suitable for spreading into an AuditEventInput:\n * `await vorim.emit({ ...event, ...replayContext })`\n *\n * If a field is absent in the inputs, it is absent in the result\n * (not the empty string). That keeps the event lean.\n */\nexport async function prepareReplayContext(\n inputs: ReplayInputs,\n): Promise<ReplayContext> {\n const ctx: ReplayContext = {};\n if (inputs.modelVersion) ctx.model_version = inputs.modelVersion;\n if (inputs.tools) ctx.tool_catalogue_hash = await hashToolCatalogue(inputs.tools);\n if (inputs.systemPrompt) ctx.system_prompt_hash = await hashSystemPrompt(inputs.systemPrompt);\n return ctx;\n}\n"],"mappings":"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","names":["event"]}
@@ -16,10 +16,9 @@ function jcsCanonicalise(value) {
16
16
  return "[" + value.map(jcsCanonicalise).join(",") + "]";
17
17
  }
18
18
  if (typeof value === "object") {
19
- const keys = Object.keys(value).sort();
20
- const parts = keys.map((k) => {
21
- return JSON.stringify(k) + ":" + jcsCanonicalise(value[k]);
22
- });
19
+ const obj = value;
20
+ const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
21
+ const parts = keys.map((k) => JSON.stringify(k) + ":" + jcsCanonicalise(obj[k]));
23
22
  return "{" + parts.join(",") + "}";
24
23
  }
25
24
  throw new Error(`jcsCanonicalise: unsupported value type: ${typeof value}`);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/replay.ts","../../src/integrations/llamaindex.ts"],"sourcesContent":["/**\n * Replayable agent decision evidence helpers.\n *\n * Canonical-form hashing for the VAIP -02 schema fields that the SDK\n * attaches to audit events. The hashes recorded in audit_events.tool_catalogue_hash\n * and audit_events.system_prompt_hash use these functions, so the bytes\n * an auditor or counterparty reconstructs must match what the SDK produced.\n *\n * These helpers are intentionally separate from the signing path. The\n * v0 canonical signature form (event_type|action|resource|input_hash|\n * output_hash|result) does NOT cover model_version, tool_catalogue_hash,\n * or system_prompt_hash. They will enter the canonical bytes in v1\n * (RFC 8785 JCS) in a follow-up release.\n *\n * Stable across SDK versions: the canonical-form version is documented\n * in CANONICAL_TOOL_CATALOGUE_VERSION. Future changes get a v2 etc;\n * never edit the existing v1 logic, or already-recorded hashes lose\n * their meaning.\n */\n\n// ─── Versioning ───────────────────────────────────────────────────────────\n\n/**\n * Canonical-form version for tool catalogue hashes produced by this SDK.\n * Recorded in tool_catalogue_canon_version on the event metadata (when\n * the metadata field is used) so verifiers know which hash recipe to\n * reproduce. Increment ONLY if the algorithm changes in a way that\n * would change the hash for the same logical catalogue.\n */\nexport const CANONICAL_TOOL_CATALOGUE_VERSION = 'v1' as const;\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\n/**\n * Minimum shape a tool needs for catalogue hashing. The framework\n * integrations adapt their native tool objects to this shape before\n * calling hashToolCatalogue.\n */\nexport interface CatalogueTool {\n /** The name the model sees and calls. Required. */\n name: string;\n /** Human-readable description shown to the model. Optional; absent ↔ empty string. */\n description?: string;\n /**\n * JSON Schema describing the tool's input parameters. Optional;\n * absent ↔ empty object `{}`. The schema gets RFC 8785 JCS-canonicalised\n * before hashing so semantically-equivalent variations (key order,\n * whitespace) produce the same hash.\n */\n schema?: Record<string, unknown> | null;\n}\n\n// ─── RFC 8785 JCS subset ──────────────────────────────────────────────────\n\n/**\n * RFC 8785 JSON Canonicalization Scheme, sufficient subset for tool\n * catalogue values.\n *\n * Rules:\n * - Object keys sorted lexicographically by UTF-16 code units (which\n * is what JS string comparison does naturally).\n * - No whitespace between tokens.\n * - Numbers: integers as integers, finite floats per ECMAScript\n * Number.prototype.toString. JCS forbids NaN and Infinity.\n * - Strings: JSON-escape using minimal set per RFC 8259 § 7.\n * - null, true, false, arrays: as JSON.stringify produces them, since\n * JSON.stringify already produces the canonical form for these.\n *\n * Not vendoring a full library because tool schemas don't carry\n * non-integer numbers in practice and the JS spec for Number.toString\n * happens to coincide with JCS § 3.2.2.2 for the integer case.\n */\nexport function jcsCanonicalise(value: unknown): string {\n if (value === null) return 'null';\n if (value === true) return 'true';\n if (value === false) return 'false';\n\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) {\n throw new Error('jcsCanonicalise: NaN and Infinity are not JCS-valid');\n }\n // For integers in safe range, .toString() matches JCS. For\n // non-integer floats, .toString() also matches in modern JS\n // engines (V8, JavaScriptCore, SpiderMonkey all use the shortest\n // round-trip representation, which is what JCS § 3.2.2.2 requires).\n return value.toString();\n }\n\n if (typeof value === 'string') {\n return JSON.stringify(value);\n }\n\n if (Array.isArray(value)) {\n return '[' + value.map(jcsCanonicalise).join(',') + ']';\n }\n\n if (typeof value === 'object') {\n const keys = Object.keys(value as Record<string, unknown>).sort();\n const parts = keys.map(k => {\n return JSON.stringify(k) + ':' + jcsCanonicalise((value as Record<string, unknown>)[k]);\n });\n return '{' + parts.join(',') + '}';\n }\n\n // undefined, function, symbol, bigint — not JSON-representable\n throw new Error(`jcsCanonicalise: unsupported value type: ${typeof value}`);\n}\n\n// ─── SHA-256 ──────────────────────────────────────────────────────────────\n\nasync function sha256Hex(input: string | Uint8Array): Promise<string> {\n const bytes = typeof input === 'string' ? new TextEncoder().encode(input) : input;\n\n // Node.js Web Crypto (Node 18+) supports digest. Browser Web Crypto does too.\n // Fall back to node:crypto if Web Crypto is unavailable.\n const subtle = (globalThis as any).crypto?.subtle;\n if (subtle) {\n const buf = await subtle.digest('SHA-256', bytes);\n return Array.from(new Uint8Array(buf))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n }\n\n // Node fallback\n const nodeCrypto = await import('node:crypto');\n return nodeCrypto.createHash('sha256').update(bytes).digest('hex');\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * Hash a single tool definition. Returns `sha256:<hex>`.\n *\n * Canonical form (v1):\n * JCS-canonicalised JSON of `{name, description, schema}` where\n * absent fields substitute `description: \"\"` and `schema: {}`.\n */\nexport async function hashTool(tool: CatalogueTool): Promise<string> {\n const normalised = {\n name: tool.name,\n description: tool.description ?? '',\n schema: tool.schema ?? {},\n };\n const hex = await sha256Hex(jcsCanonicalise(normalised));\n return `sha256:${hex}`;\n}\n\n/**\n * Hash an entire tool catalogue. Returns `sha256:<hex>`.\n *\n * Reordering tools does NOT change the hash (tool hashes sorted\n * lexicographically before concatenation). Adding, removing, or\n * modifying a tool DOES change the hash.\n *\n * Per-tool hashing first means a verifier comparing two catalogue\n * hashes that differ can also be given the per-tool hashes to\n * identify which specific tool changed.\n */\nexport async function hashToolCatalogue(tools: CatalogueTool[]): Promise<string> {\n if (tools.length === 0) {\n // Empty catalogue has a deterministic, stable hash distinct from \"no field\"\n return `sha256:${await sha256Hex('[]')}`;\n }\n const perTool = await Promise.all(tools.map(hashTool));\n perTool.sort();\n const hex = await sha256Hex(perTool.join(''));\n return `sha256:${hex}`;\n}\n\n/**\n * Hash a system prompt. Returns `sha256:<hex>`.\n *\n * The prompt is UTF-8 encoded and hashed verbatim — no normalisation.\n * If a caller wants to ignore whitespace or comment differences, they\n * should normalise before calling. The intent here is deterministic\n * reproducibility, not semantic equivalence.\n */\nexport async function hashSystemPrompt(prompt: string): Promise<string> {\n const hex = await sha256Hex(prompt);\n return `sha256:${hex}`;\n}\n\n/**\n * Convenience: hash the previous event's canonical bytes for use in\n * the prev_event_hash field of hash-chained ingest. Caller provides\n * the canonical bytes (use canonicalPayloadV0 from the main module).\n */\nexport async function hashPreviousEvent(canonicalBytes: string): Promise<string> {\n const hex = await sha256Hex(canonicalBytes);\n return `sha256:${hex}`;\n}\n\n// ─── Replay context — framework integration helper ────────────────────────\n\n/**\n * Raw inputs the integration captures from the framework. Set by the\n * integration's config; turned into hashes by {@link prepareReplayContext}.\n */\nexport interface ReplayInputs {\n /** Stable identifier for the model. E.g. `\"anthropic:claude-opus-4-8\"`. */\n modelVersion?: string;\n /** Tools available to the agent at call time. Hashed via {@link hashToolCatalogue}. */\n tools?: CatalogueTool[];\n /** System prompt active at call time. Hashed via {@link hashSystemPrompt}. */\n systemPrompt?: string;\n}\n\n/**\n * Pre-computed hashes ready to attach to audit events. The three keys\n * match the audit_events column names.\n */\nexport interface ReplayContext {\n model_version?: string;\n tool_catalogue_hash?: string;\n system_prompt_hash?: string;\n}\n\n/**\n * Compute replay context once from raw inputs. Use at integration\n * setup time so each emit can attach the hashes without re-hashing.\n *\n * Returns an object suitable for spreading into an AuditEventInput:\n * `await vorim.emit({ ...event, ...replayContext })`\n *\n * If a field is absent in the inputs, it is absent in the result\n * (not the empty string). That keeps the event lean.\n */\nexport async function prepareReplayContext(\n inputs: ReplayInputs,\n): Promise<ReplayContext> {\n const ctx: ReplayContext = {};\n if (inputs.modelVersion) ctx.model_version = inputs.modelVersion;\n if (inputs.tools) ctx.tool_catalogue_hash = await hashToolCatalogue(inputs.tools);\n if (inputs.systemPrompt) ctx.system_prompt_hash = await hashSystemPrompt(inputs.systemPrompt);\n return ctx;\n}\n","// ============================================================================\n// VORIM SDK — LlamaIndex TS Integration\n// Wraps LlamaIndex tools with Vorim permission checks + audit trails.\n// Provides a tool wrapper and agent registration factory.\n//\n// Peer dependency: llamaindex >=0.4.0 (or @llamaindex/core)\n// ============================================================================\n\nimport type { VorimSDK } from '../index.js';\nimport type { PermissionScope, AuditEventInput } from '../types.js';\nimport {\n prepareReplayContext,\n type ReplayInputs,\n type ReplayContext,\n type CatalogueTool,\n} from '../replay.js';\n\n// ─── Re-declared LlamaIndex types (peer dependency — not bundled) ─────────\n// These mirror the actual interfaces from @llamaindex/core/llms and\n// @llamaindex/core/tools so consumers don't need to wrangle imports.\n\n/** Matches @llamaindex/core ToolMetadata */\ninterface ToolMetadata<P extends Record<string, unknown> = Record<string, unknown>> {\n name: string;\n description: string;\n parameters?: P;\n}\n\n/**\n * Matches @llamaindex/core BaseTool.\n * In LlamaIndex, `call` is optional on BaseTool but required on BaseToolWithCall.\n */\ninterface LlamaIndexTool<Input = any> {\n metadata: ToolMetadata;\n call: (input: Input) => any | Promise<any>;\n}\n\n// ─── Configuration ────────────────────────────────────────────────────────\n\nexport interface VorimLlamaIndexConfig {\n /** Vorim SDK instance. */\n vorim: VorimSDK;\n /** The Vorim agent_id to associate. */\n agentId: string;\n /** Map tool names → Vorim permission scopes. */\n permissionMap?: Record<string, PermissionScope>;\n /** Default permission scope for unmapped tools. @default 'agent:execute' */\n defaultPermission?: PermissionScope;\n /** Whether to emit audit events asynchronously (fire-and-forget). @default true */\n asyncAudit?: boolean;\n /**\n * Replayable agent decision evidence (VAIP -02). Hashes attached to\n * every audit event the wrapper emits. Not covered by v0 canonical\n * signature form.\n */\n replay?: ReplayInputs;\n}\n\n// ─── Tool Wrapper ─────────────────────────────────────────────────────────\n\n/**\n * Wraps a LlamaIndex tool (`BaseTool` / `BaseToolWithCall` / `FunctionTool`)\n * with Vorim permission checks before execution and audit event emission after.\n *\n * The wrapper implements the same `BaseTool` interface so it's a drop-in\n * replacement anywhere LlamaIndex expects a tool.\n *\n * @example\n * ```ts\n * import { FunctionTool } from \"llamaindex\";\n * import createVorim from \"@vorim/sdk\";\n * import { wrapTool } from \"@vorim/sdk/integrations/llamaindex\";\n *\n * const vorim = createVorim({ apiKey: \"agid_sk_live_...\" });\n *\n * const searchTool = FunctionTool.from(\n * async ({ query }: { query: string }) => `Results for: ${query}`,\n * { name: \"search\", description: \"Search documents\", parameters: { ... } }\n * );\n *\n * const guarded = wrapTool(searchTool, {\n * vorim,\n * agentId: \"agid_acme_a1b2c3d4\",\n * permissionMap: { search: \"agent:read\" },\n * });\n *\n * // Use with any LlamaIndex agent\n * const agent = new OpenAIAgent({ tools: [guarded] });\n * ```\n */\nexport function wrapTool<T extends LlamaIndexTool>(\n tool: T,\n config: VorimLlamaIndexConfig,\n): T {\n const {\n vorim, agentId,\n permissionMap = {},\n defaultPermission = 'agent:execute',\n asyncAudit = true,\n replay,\n } = config;\n\n const originalCall = tool.call.bind(tool);\n const toolName = tool.metadata.name;\n const scope = permissionMap[toolName] ?? defaultPermission;\n const getReplayCtx = makeReplayContextGetter(replay);\n\n // Create a new object that preserves the tool's prototype chain\n // and all properties, but overrides `call`\n const wrapped = Object.create(Object.getPrototypeOf(tool), {\n ...Object.getOwnPropertyDescriptors(tool),\n call: {\n value: async function vorimGuardedCall(input: any): Promise<any> {\n // 1. Permission check\n const { allowed, reason } = await vorim.check(agentId, scope);\n const replayCtx = await getReplayCtx();\n\n if (!allowed) {\n const event: AuditEventInput = {\n agent_id: agentId,\n event_type: 'tool_call',\n action: toolName,\n resource: truncate(JSON.stringify(input), 500),\n permission: scope,\n result: 'denied',\n metadata: { reason, framework: 'llamaindex' },\n ...replayCtx,\n };\n emitAudit(vorim, event, asyncAudit);\n throw new Error(\n `Vorim: permission denied for \"${toolName}\" — scope \"${scope}\"${reason ? `: ${reason}` : ''}`,\n );\n }\n\n // 2. Execute the original tool\n const start = Date.now();\n let result: any;\n try {\n result = await originalCall(input);\n } catch (err) {\n const event: AuditEventInput = {\n agent_id: agentId,\n event_type: 'tool_call',\n action: toolName,\n resource: truncate(JSON.stringify(input), 500),\n permission: scope,\n result: 'error',\n latency_ms: Date.now() - start,\n error_code: err instanceof Error ? err.name : 'UNKNOWN',\n metadata: {\n error: err instanceof Error ? err.message : String(err),\n framework: 'llamaindex',\n },\n ...replayCtx,\n };\n emitAudit(vorim, event, asyncAudit);\n throw err;\n }\n\n // 3. Audit success\n const event: AuditEventInput = {\n agent_id: agentId,\n event_type: 'tool_call',\n action: toolName,\n resource: truncate(JSON.stringify(input), 500),\n permission: scope,\n result: 'success',\n latency_ms: Date.now() - start,\n metadata: { framework: 'llamaindex' },\n ...replayCtx,\n };\n emitAudit(vorim, event, asyncAudit);\n\n return result;\n },\n writable: true,\n configurable: true,\n enumerable: true,\n },\n }) as T;\n\n return wrapped;\n}\n\n/**\n * Wraps an array of LlamaIndex tools with Vorim permission + audit.\n */\nexport function wrapTools<T extends LlamaIndexTool>(\n tools: T[],\n config: VorimLlamaIndexConfig,\n): T[] {\n return tools.map(t => wrapTool(t, config));\n}\n\n// ─── Agent Factory ────────────────────────────────────────────────────────\n\nexport interface CreateVorimLlamaIndexAgentConfig extends VorimLlamaIndexConfig {\n /** Display name for the agent. */\n name: string;\n /** Agent capabilities. */\n capabilities: string[];\n /** Initial permission scopes. */\n scopes: PermissionScope[];\n /** Optional description. */\n description?: string;\n}\n\n/**\n * Registers a new agent with Vorim and returns wrapped tools ready for\n * use with any LlamaIndex agent (OpenAIAgent, ReActAgent, etc.).\n *\n * @example\n * ```ts\n * import { OpenAIAgent, FunctionTool } from \"llamaindex\";\n * import createVorim from \"@vorim/sdk\";\n * import { createVorimAgent } from \"@vorim/sdk/integrations/llamaindex\";\n *\n * const vorim = createVorim({ apiKey: \"agid_sk_live_...\" });\n *\n * const searchTool = FunctionTool.from(...);\n * const writeTool = FunctionTool.from(...);\n *\n * const { agentId, tools } = await createVorimAgent({\n * vorim,\n * name: \"research-agent\",\n * capabilities: [\"search\", \"write\"],\n * scopes: [\"agent:read\", \"agent:write\", \"agent:execute\"],\n * tools: [searchTool, writeTool],\n * permissionMap: {\n * search: \"agent:read\",\n * write: \"agent:write\",\n * },\n * });\n *\n * // Create LlamaIndex agent with Vorim-wrapped tools\n * const agent = new OpenAIAgent({ tools });\n * const response = await agent.chat({ message: \"Research AI trends\" });\n * ```\n */\nexport async function createVorimAgent<T extends LlamaIndexTool>(\n config: CreateVorimLlamaIndexAgentConfig & { tools: T[] },\n) {\n const { vorim, name, capabilities, scopes, description, tools: rawTools, permissionMap, defaultPermission, asyncAudit } = config;\n\n // Register agent with Vorim\n const registration = await vorim.register({\n name,\n description,\n capabilities,\n scopes,\n });\n\n const agentId = registration.agent.agent_id;\n const toolConfig: VorimLlamaIndexConfig = { vorim, agentId, permissionMap, defaultPermission, asyncAudit };\n\n // Wrap tools with permission checks\n const tools = wrapTools(rawTools, toolConfig);\n\n return {\n /** The Vorim agent_id. */\n agentId,\n /** Full registration result. */\n registration,\n /** Tools wrapped with Vorim permission checks + audit. */\n tools,\n /** The private key (store securely — shown once). */\n privateKey: registration.private_key,\n };\n}\n\n// ─── Audit Helpers ────────────────────────────────────────────────────────\n\n/**\n * Emit a manual audit event for a LlamaIndex agent action that isn't\n * captured by the tool wrapper (e.g. RAG retrieval, chat responses).\n */\nexport async function emitLlamaIndexEvent(\n vorim: VorimSDK,\n agentId: string,\n event: {\n action: string;\n resource?: string;\n result: 'success' | 'denied' | 'error';\n latencyMs?: number;\n error?: string;\n metadata?: Record<string, unknown>;\n },\n): Promise<void> {\n await vorim.emit({\n agent_id: agentId,\n event_type: 'api_request',\n action: event.action,\n resource: event.resource,\n result: event.result,\n latency_ms: event.latencyMs,\n error_code: event.error ? 'LLAMAINDEX_ERROR' : undefined,\n metadata: {\n framework: 'llamaindex',\n ...(event.error ? { error: event.error } : {}),\n ...event.metadata,\n },\n });\n}\n\n// ─── Internal helpers ─────────────────────────────────────────────────────\n\nfunction truncate(str: string, max: number): string {\n return str.length > max ? str.slice(0, max) + '…' : str;\n}\n\nfunction emitAudit(vorim: VorimSDK, event: AuditEventInput, async: boolean): void {\n if (async) {\n vorim.emit(event).catch(() => {});\n } else {\n vorim.emit(event).catch(() => {});\n }\n}\n\n/**\n * Lazy-cached replay context getter. Hashes computed once on first\n * call, reused thereafter. Returns empty object when no replay config\n * was given, so the spread is a no-op.\n */\nfunction makeReplayContextGetter(replay: ReplayInputs | undefined): () => Promise<ReplayContext> {\n if (!replay) return async () => ({});\n let cached: Promise<ReplayContext> | null = null;\n return () => {\n if (!cached) cached = prepareReplayContext(replay);\n return cached;\n };\n}\n"],"mappings":"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","names":["event"]}
1
+ {"version":3,"sources":["../../src/replay.ts","../../src/integrations/llamaindex.ts"],"sourcesContent":["/**\n * Replayable agent decision evidence helpers.\n *\n * Canonical-form hashing for the VAIP -02 schema fields that the SDK\n * attaches to audit events. The hashes recorded in audit_events.tool_catalogue_hash\n * and audit_events.system_prompt_hash use these functions, so the bytes\n * an auditor or counterparty reconstructs must match what the SDK produced.\n *\n * These helpers are intentionally separate from the signing path. The\n * v0 canonical signature form (event_type|action|resource|input_hash|\n * output_hash|result) does NOT cover model_version, tool_catalogue_hash,\n * or system_prompt_hash. They will enter the canonical bytes in v1\n * (RFC 8785 JCS) in a follow-up release.\n *\n * Stable across SDK versions: the canonical-form version is documented\n * in CANONICAL_TOOL_CATALOGUE_VERSION. Future changes get a v2 etc;\n * never edit the existing v1 logic, or already-recorded hashes lose\n * their meaning.\n */\n\n// ─── Versioning ───────────────────────────────────────────────────────────\n\n/**\n * Canonical-form version for tool catalogue hashes produced by this SDK.\n * Recorded in tool_catalogue_canon_version on the event metadata (when\n * the metadata field is used) so verifiers know which hash recipe to\n * reproduce. Increment ONLY if the algorithm changes in a way that\n * would change the hash for the same logical catalogue.\n */\nexport const CANONICAL_TOOL_CATALOGUE_VERSION = 'v1' as const;\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\n/**\n * Minimum shape a tool needs for catalogue hashing. The framework\n * integrations adapt their native tool objects to this shape before\n * calling hashToolCatalogue.\n */\nexport interface CatalogueTool {\n /** The name the model sees and calls. Required. */\n name: string;\n /** Human-readable description shown to the model. Optional; absent ↔ empty string. */\n description?: string;\n /**\n * JSON Schema describing the tool's input parameters. Optional;\n * absent ↔ empty object `{}`. The schema gets RFC 8785 JCS-canonicalised\n * before hashing so semantically-equivalent variations (key order,\n * whitespace) produce the same hash.\n */\n schema?: Record<string, unknown> | null;\n}\n\n// ─── RFC 8785 JCS subset ──────────────────────────────────────────────────\n\n/**\n * RFC 8785 JSON Canonicalization Scheme, sufficient subset for tool\n * catalogue values.\n *\n * Rules:\n * - Object keys sorted lexicographically by UTF-16 code units (which\n * is what JS string comparison does naturally).\n * - No whitespace between tokens.\n * - Numbers: integers as integers, finite floats per ECMAScript\n * Number.prototype.toString. JCS forbids NaN and Infinity.\n * - Strings: JSON-escape using minimal set per RFC 8259 § 7.\n * - null, true, false, arrays: as JSON.stringify produces them, since\n * JSON.stringify already produces the canonical form for these.\n *\n * Not vendoring a full library because tool schemas don't carry\n * non-integer numbers in practice and the JS spec for Number.toString\n * happens to coincide with JCS § 3.2.2.2 for the integer case.\n */\nexport function jcsCanonicalise(value: unknown): string {\n if (value === null) return 'null';\n if (value === true) return 'true';\n if (value === false) return 'false';\n\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) {\n throw new Error('jcsCanonicalise: NaN and Infinity are not JCS-valid');\n }\n // For integers in safe range, .toString() matches JCS. For\n // non-integer floats, .toString() also matches in modern JS\n // engines (V8, JavaScriptCore, SpiderMonkey all use the shortest\n // round-trip representation, which is what JCS § 3.2.2.2 requires).\n return value.toString();\n }\n\n if (typeof value === 'string') {\n return JSON.stringify(value);\n }\n\n if (Array.isArray(value)) {\n return '[' + value.map(jcsCanonicalise).join(',') + ']';\n }\n\n if (typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n // Filter undefined-valued fields, matching @vorim/verify and\n // @vorim/shared-types. Without this the SDK throws on { a: 1, b: undefined }\n // while the verifier silently drops b — a cross-module canonical-form\n // divergence that would break signature verification on such events.\n const keys = Object.keys(obj).filter(k => obj[k] !== undefined).sort();\n const parts = keys.map(k => JSON.stringify(k) + ':' + jcsCanonicalise(obj[k]));\n return '{' + parts.join(',') + '}';\n }\n\n // undefined, function, symbol, bigint — not JSON-representable\n throw new Error(`jcsCanonicalise: unsupported value type: ${typeof value}`);\n}\n\n// ─── SHA-256 ──────────────────────────────────────────────────────────────\n\nasync function sha256Hex(input: string | Uint8Array): Promise<string> {\n const bytes = typeof input === 'string' ? new TextEncoder().encode(input) : input;\n\n // Node.js Web Crypto (Node 18+) supports digest. Browser Web Crypto does too.\n // Fall back to node:crypto if Web Crypto is unavailable.\n const subtle = (globalThis as any).crypto?.subtle;\n if (subtle) {\n const buf = await subtle.digest('SHA-256', bytes);\n return Array.from(new Uint8Array(buf))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n }\n\n // Node fallback\n const nodeCrypto = await import('node:crypto');\n return nodeCrypto.createHash('sha256').update(bytes).digest('hex');\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * Hash a single tool definition. Returns `sha256:<hex>`.\n *\n * Canonical form (v1):\n * JCS-canonicalised JSON of `{name, description, schema}` where\n * absent fields substitute `description: \"\"` and `schema: {}`.\n */\nexport async function hashTool(tool: CatalogueTool): Promise<string> {\n const normalised = {\n name: tool.name,\n description: tool.description ?? '',\n schema: tool.schema ?? {},\n };\n const hex = await sha256Hex(jcsCanonicalise(normalised));\n return `sha256:${hex}`;\n}\n\n/**\n * Hash an entire tool catalogue. Returns `sha256:<hex>`.\n *\n * Reordering tools does NOT change the hash (tool hashes sorted\n * lexicographically before concatenation). Adding, removing, or\n * modifying a tool DOES change the hash.\n *\n * Per-tool hashing first means a verifier comparing two catalogue\n * hashes that differ can also be given the per-tool hashes to\n * identify which specific tool changed.\n */\nexport async function hashToolCatalogue(tools: CatalogueTool[]): Promise<string> {\n if (tools.length === 0) {\n // Empty catalogue has a deterministic, stable hash distinct from \"no field\"\n return `sha256:${await sha256Hex('[]')}`;\n }\n const perTool = await Promise.all(tools.map(hashTool));\n perTool.sort();\n const hex = await sha256Hex(perTool.join(''));\n return `sha256:${hex}`;\n}\n\n/**\n * Hash a system prompt. Returns `sha256:<hex>`.\n *\n * The prompt is UTF-8 encoded and hashed verbatim — no normalisation.\n * If a caller wants to ignore whitespace or comment differences, they\n * should normalise before calling. The intent here is deterministic\n * reproducibility, not semantic equivalence.\n */\nexport async function hashSystemPrompt(prompt: string): Promise<string> {\n const hex = await sha256Hex(prompt);\n return `sha256:${hex}`;\n}\n\n/**\n * Convenience: hash the previous event's canonical bytes for use in\n * the prev_event_hash field of hash-chained ingest. Caller provides\n * the canonical bytes (use canonicalPayloadV0 from the main module).\n */\nexport async function hashPreviousEvent(canonicalBytes: string): Promise<string> {\n const hex = await sha256Hex(canonicalBytes);\n return `sha256:${hex}`;\n}\n\n// ─── Replay context — framework integration helper ────────────────────────\n\n/**\n * Raw inputs the integration captures from the framework. Set by the\n * integration's config; turned into hashes by {@link prepareReplayContext}.\n */\nexport interface ReplayInputs {\n /** Stable identifier for the model. E.g. `\"anthropic:claude-opus-4-8\"`. */\n modelVersion?: string;\n /** Tools available to the agent at call time. Hashed via {@link hashToolCatalogue}. */\n tools?: CatalogueTool[];\n /** System prompt active at call time. Hashed via {@link hashSystemPrompt}. */\n systemPrompt?: string;\n}\n\n/**\n * Pre-computed hashes ready to attach to audit events. The three keys\n * match the audit_events column names.\n */\nexport interface ReplayContext {\n model_version?: string;\n tool_catalogue_hash?: string;\n system_prompt_hash?: string;\n}\n\n/**\n * Compute replay context once from raw inputs. Use at integration\n * setup time so each emit can attach the hashes without re-hashing.\n *\n * Returns an object suitable for spreading into an AuditEventInput:\n * `await vorim.emit({ ...event, ...replayContext })`\n *\n * If a field is absent in the inputs, it is absent in the result\n * (not the empty string). That keeps the event lean.\n */\nexport async function prepareReplayContext(\n inputs: ReplayInputs,\n): Promise<ReplayContext> {\n const ctx: ReplayContext = {};\n if (inputs.modelVersion) ctx.model_version = inputs.modelVersion;\n if (inputs.tools) ctx.tool_catalogue_hash = await hashToolCatalogue(inputs.tools);\n if (inputs.systemPrompt) ctx.system_prompt_hash = await hashSystemPrompt(inputs.systemPrompt);\n return ctx;\n}\n","// ============================================================================\n// VORIM SDK — LlamaIndex TS Integration\n// Wraps LlamaIndex tools with Vorim permission checks + audit trails.\n// Provides a tool wrapper and agent registration factory.\n//\n// Peer dependency: llamaindex >=0.4.0 (or @llamaindex/core)\n// ============================================================================\n\nimport type { VorimSDK } from '../index.js';\nimport type { PermissionScope, AuditEventInput } from '../types.js';\nimport {\n prepareReplayContext,\n type ReplayInputs,\n type ReplayContext,\n type CatalogueTool,\n} from '../replay.js';\n\n// ─── Re-declared LlamaIndex types (peer dependency — not bundled) ─────────\n// These mirror the actual interfaces from @llamaindex/core/llms and\n// @llamaindex/core/tools so consumers don't need to wrangle imports.\n\n/** Matches @llamaindex/core ToolMetadata */\ninterface ToolMetadata<P extends Record<string, unknown> = Record<string, unknown>> {\n name: string;\n description: string;\n parameters?: P;\n}\n\n/**\n * Matches @llamaindex/core BaseTool.\n * In LlamaIndex, `call` is optional on BaseTool but required on BaseToolWithCall.\n */\ninterface LlamaIndexTool<Input = any> {\n metadata: ToolMetadata;\n call: (input: Input) => any | Promise<any>;\n}\n\n// ─── Configuration ────────────────────────────────────────────────────────\n\nexport interface VorimLlamaIndexConfig {\n /** Vorim SDK instance. */\n vorim: VorimSDK;\n /** The Vorim agent_id to associate. */\n agentId: string;\n /** Map tool names → Vorim permission scopes. */\n permissionMap?: Record<string, PermissionScope>;\n /** Default permission scope for unmapped tools. @default 'agent:execute' */\n defaultPermission?: PermissionScope;\n /** Whether to emit audit events asynchronously (fire-and-forget). @default true */\n asyncAudit?: boolean;\n /**\n * Replayable agent decision evidence (VAIP -02). Hashes attached to\n * every audit event the wrapper emits. Not covered by v0 canonical\n * signature form.\n */\n replay?: ReplayInputs;\n}\n\n// ─── Tool Wrapper ─────────────────────────────────────────────────────────\n\n/**\n * Wraps a LlamaIndex tool (`BaseTool` / `BaseToolWithCall` / `FunctionTool`)\n * with Vorim permission checks before execution and audit event emission after.\n *\n * The wrapper implements the same `BaseTool` interface so it's a drop-in\n * replacement anywhere LlamaIndex expects a tool.\n *\n * @example\n * ```ts\n * import { FunctionTool } from \"llamaindex\";\n * import createVorim from \"@vorim/sdk\";\n * import { wrapTool } from \"@vorim/sdk/integrations/llamaindex\";\n *\n * const vorim = createVorim({ apiKey: \"agid_sk_live_...\" });\n *\n * const searchTool = FunctionTool.from(\n * async ({ query }: { query: string }) => `Results for: ${query}`,\n * { name: \"search\", description: \"Search documents\", parameters: { ... } }\n * );\n *\n * const guarded = wrapTool(searchTool, {\n * vorim,\n * agentId: \"agid_acme_a1b2c3d4\",\n * permissionMap: { search: \"agent:read\" },\n * });\n *\n * // Use with any LlamaIndex agent\n * const agent = new OpenAIAgent({ tools: [guarded] });\n * ```\n */\nexport function wrapTool<T extends LlamaIndexTool>(\n tool: T,\n config: VorimLlamaIndexConfig,\n): T {\n const {\n vorim, agentId,\n permissionMap = {},\n defaultPermission = 'agent:execute',\n asyncAudit = true,\n replay,\n } = config;\n\n const originalCall = tool.call.bind(tool);\n const toolName = tool.metadata.name;\n const scope = permissionMap[toolName] ?? defaultPermission;\n const getReplayCtx = makeReplayContextGetter(replay);\n\n // Create a new object that preserves the tool's prototype chain\n // and all properties, but overrides `call`\n const wrapped = Object.create(Object.getPrototypeOf(tool), {\n ...Object.getOwnPropertyDescriptors(tool),\n call: {\n value: async function vorimGuardedCall(input: any): Promise<any> {\n // 1. Permission check\n const { allowed, reason } = await vorim.check(agentId, scope);\n const replayCtx = await getReplayCtx();\n\n if (!allowed) {\n const event: AuditEventInput = {\n agent_id: agentId,\n event_type: 'tool_call',\n action: toolName,\n resource: truncate(JSON.stringify(input), 500),\n permission: scope,\n result: 'denied',\n metadata: { reason, framework: 'llamaindex' },\n ...replayCtx,\n };\n emitAudit(vorim, event, asyncAudit);\n throw new Error(\n `Vorim: permission denied for \"${toolName}\" — scope \"${scope}\"${reason ? `: ${reason}` : ''}`,\n );\n }\n\n // 2. Execute the original tool\n const start = Date.now();\n let result: any;\n try {\n result = await originalCall(input);\n } catch (err) {\n const event: AuditEventInput = {\n agent_id: agentId,\n event_type: 'tool_call',\n action: toolName,\n resource: truncate(JSON.stringify(input), 500),\n permission: scope,\n result: 'error',\n latency_ms: Date.now() - start,\n error_code: err instanceof Error ? err.name : 'UNKNOWN',\n metadata: {\n error: err instanceof Error ? err.message : String(err),\n framework: 'llamaindex',\n },\n ...replayCtx,\n };\n emitAudit(vorim, event, asyncAudit);\n throw err;\n }\n\n // 3. Audit success\n const event: AuditEventInput = {\n agent_id: agentId,\n event_type: 'tool_call',\n action: toolName,\n resource: truncate(JSON.stringify(input), 500),\n permission: scope,\n result: 'success',\n latency_ms: Date.now() - start,\n metadata: { framework: 'llamaindex' },\n ...replayCtx,\n };\n emitAudit(vorim, event, asyncAudit);\n\n return result;\n },\n writable: true,\n configurable: true,\n enumerable: true,\n },\n }) as T;\n\n return wrapped;\n}\n\n/**\n * Wraps an array of LlamaIndex tools with Vorim permission + audit.\n */\nexport function wrapTools<T extends LlamaIndexTool>(\n tools: T[],\n config: VorimLlamaIndexConfig,\n): T[] {\n return tools.map(t => wrapTool(t, config));\n}\n\n// ─── Agent Factory ────────────────────────────────────────────────────────\n\nexport interface CreateVorimLlamaIndexAgentConfig extends VorimLlamaIndexConfig {\n /** Display name for the agent. */\n name: string;\n /** Agent capabilities. */\n capabilities: string[];\n /** Initial permission scopes. */\n scopes: PermissionScope[];\n /** Optional description. */\n description?: string;\n}\n\n/**\n * Registers a new agent with Vorim and returns wrapped tools ready for\n * use with any LlamaIndex agent (OpenAIAgent, ReActAgent, etc.).\n *\n * @example\n * ```ts\n * import { OpenAIAgent, FunctionTool } from \"llamaindex\";\n * import createVorim from \"@vorim/sdk\";\n * import { createVorimAgent } from \"@vorim/sdk/integrations/llamaindex\";\n *\n * const vorim = createVorim({ apiKey: \"agid_sk_live_...\" });\n *\n * const searchTool = FunctionTool.from(...);\n * const writeTool = FunctionTool.from(...);\n *\n * const { agentId, tools } = await createVorimAgent({\n * vorim,\n * name: \"research-agent\",\n * capabilities: [\"search\", \"write\"],\n * scopes: [\"agent:read\", \"agent:write\", \"agent:execute\"],\n * tools: [searchTool, writeTool],\n * permissionMap: {\n * search: \"agent:read\",\n * write: \"agent:write\",\n * },\n * });\n *\n * // Create LlamaIndex agent with Vorim-wrapped tools\n * const agent = new OpenAIAgent({ tools });\n * const response = await agent.chat({ message: \"Research AI trends\" });\n * ```\n */\nexport async function createVorimAgent<T extends LlamaIndexTool>(\n config: CreateVorimLlamaIndexAgentConfig & { tools: T[] },\n) {\n const { vorim, name, capabilities, scopes, description, tools: rawTools, permissionMap, defaultPermission, asyncAudit } = config;\n\n // Register agent with Vorim\n const registration = await vorim.register({\n name,\n description,\n capabilities,\n scopes,\n });\n\n const agentId = registration.agent.agent_id;\n const toolConfig: VorimLlamaIndexConfig = { vorim, agentId, permissionMap, defaultPermission, asyncAudit };\n\n // Wrap tools with permission checks\n const tools = wrapTools(rawTools, toolConfig);\n\n return {\n /** The Vorim agent_id. */\n agentId,\n /** Full registration result. */\n registration,\n /** Tools wrapped with Vorim permission checks + audit. */\n tools,\n /** The private key (store securely — shown once). */\n privateKey: registration.private_key,\n };\n}\n\n// ─── Audit Helpers ────────────────────────────────────────────────────────\n\n/**\n * Emit a manual audit event for a LlamaIndex agent action that isn't\n * captured by the tool wrapper (e.g. RAG retrieval, chat responses).\n */\nexport async function emitLlamaIndexEvent(\n vorim: VorimSDK,\n agentId: string,\n event: {\n action: string;\n resource?: string;\n result: 'success' | 'denied' | 'error';\n latencyMs?: number;\n error?: string;\n metadata?: Record<string, unknown>;\n },\n): Promise<void> {\n await vorim.emit({\n agent_id: agentId,\n event_type: 'api_request',\n action: event.action,\n resource: event.resource,\n result: event.result,\n latency_ms: event.latencyMs,\n error_code: event.error ? 'LLAMAINDEX_ERROR' : undefined,\n metadata: {\n framework: 'llamaindex',\n ...(event.error ? { error: event.error } : {}),\n ...event.metadata,\n },\n });\n}\n\n// ─── Internal helpers ─────────────────────────────────────────────────────\n\nfunction truncate(str: string, max: number): string {\n return str.length > max ? str.slice(0, max) + '…' : str;\n}\n\nfunction emitAudit(vorim: VorimSDK, event: AuditEventInput, async: boolean): void {\n if (async) {\n vorim.emit(event).catch(() => {});\n } else {\n vorim.emit(event).catch(() => {});\n }\n}\n\n/**\n * Lazy-cached replay context getter. Hashes computed once on first\n * call, reused thereafter. Returns empty object when no replay config\n * was given, so the spread is a no-op.\n */\nfunction makeReplayContextGetter(replay: ReplayInputs | undefined): () => Promise<ReplayContext> {\n if (!replay) return async () => ({});\n let cached: Promise<ReplayContext> | null = null;\n return () => {\n if (!cached) cached = prepareReplayContext(replay);\n return cached;\n };\n}\n"],"mappings":"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","names":["event"]}
@@ -54,10 +54,9 @@ function jcsCanonicalise(value) {
54
54
  return "[" + value.map(jcsCanonicalise).join(",") + "]";
55
55
  }
56
56
  if (typeof value === "object") {
57
- const keys = Object.keys(value).sort();
58
- const parts = keys.map((k) => {
59
- return JSON.stringify(k) + ":" + jcsCanonicalise(value[k]);
60
- });
57
+ const obj = value;
58
+ const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
59
+ const parts = keys.map((k) => JSON.stringify(k) + ":" + jcsCanonicalise(obj[k]));
61
60
  return "{" + parts.join(",") + "}";
62
61
  }
63
62
  throw new Error(`jcsCanonicalise: unsupported value type: ${typeof value}`);
@@ -108,6 +107,7 @@ var VorimToolRegistry = class {
108
107
  agentId;
109
108
  defaultPermission;
110
109
  asyncAudit;
110
+ useRuntimeControl;
111
111
  tools = /* @__PURE__ */ new Map();
112
112
  replayInputs;
113
113
  replayCache = null;
@@ -116,8 +116,29 @@ var VorimToolRegistry = class {
116
116
  this.agentId = config.agentId;
117
117
  this.defaultPermission = config.defaultPermission ?? "agent:execute";
118
118
  this.asyncAudit = config.asyncAudit ?? true;
119
+ this.useRuntimeControl = config.useRuntimeControl ?? false;
119
120
  this.replayInputs = config.replay;
120
121
  }
122
+ /**
123
+ * Gate a tool call. When useRuntimeControl is on, run the full runtime
124
+ * decision (beforeAction) and surface its decisionId for audit linkage;
125
+ * otherwise use the lightweight permission check.
126
+ */
127
+ async gate(scope, actionTarget, payload) {
128
+ if (this.useRuntimeControl) {
129
+ const d = await this.vorim.beforeAction(
130
+ { agentId: this.agentId, actionType: "tool_call", actionTarget, requiredScope: scope, payload },
131
+ { throwOnDeny: false }
132
+ );
133
+ return {
134
+ allowed: d.decision === "allow" || d.decision === "modify" || d.decision === "fallback",
135
+ reason: d.reason,
136
+ decisionId: d.decisionId || void 0
137
+ };
138
+ }
139
+ const { allowed, reason } = await this.vorim.check(this.agentId, scope);
140
+ return { allowed, reason };
141
+ }
121
142
  async getReplayContext() {
122
143
  if (!this.replayInputs) return {};
123
144
  if (!this.replayCache) {
@@ -191,7 +212,7 @@ var VorimToolRegistry = class {
191
212
  content: JSON.stringify({ error: `Invalid JSON arguments for ${fn.name}` })
192
213
  };
193
214
  }
194
- const { allowed, reason } = await this.vorim.check(this.agentId, scope);
215
+ const { allowed, reason, decisionId } = await this.gate(scope, fn.name, args);
195
216
  const replayCtx = await this.getReplayContext();
196
217
  if (!allowed) {
197
218
  const event = {
@@ -201,10 +222,11 @@ var VorimToolRegistry = class {
201
222
  resource: truncate(fn.arguments, 500),
202
223
  permission: scope,
203
224
  result: "denied",
225
+ ...decisionId ? { decision_id: decisionId } : {},
204
226
  metadata: { reason, framework: "openai" },
205
227
  ...replayCtx
206
228
  };
207
- this.emitAudit(event);
229
+ await this.emitAudit(event);
208
230
  return {
209
231
  role: "tool",
210
232
  tool_call_id: id,
@@ -223,10 +245,11 @@ var VorimToolRegistry = class {
223
245
  permission: scope,
224
246
  result: "success",
225
247
  latency_ms: Date.now() - start,
248
+ ...decisionId ? { decision_id: decisionId } : {},
226
249
  metadata: { framework: "openai" },
227
250
  ...replayCtx
228
251
  };
229
- this.emitAudit(event);
252
+ await this.emitAudit(event);
230
253
  return { role: "tool", tool_call_id: id, content };
231
254
  } catch (err) {
232
255
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -239,10 +262,11 @@ var VorimToolRegistry = class {
239
262
  result: "error",
240
263
  latency_ms: Date.now() - start,
241
264
  error_code: err instanceof Error ? err.name : "UNKNOWN",
265
+ ...decisionId ? { decision_id: decisionId } : {},
242
266
  metadata: { error: errMsg, framework: "openai" },
243
267
  ...replayCtx
244
268
  };
245
- this.emitAudit(event);
269
+ await this.emitAudit(event);
246
270
  return {
247
271
  role: "tool",
248
272
  tool_call_id: id,
@@ -250,13 +274,15 @@ var VorimToolRegistry = class {
250
274
  };
251
275
  }
252
276
  }
253
- emitAudit(event) {
277
+ async emitAudit(event) {
254
278
  if (this.asyncAudit) {
255
279
  this.vorim.emit(event).catch(() => {
256
280
  });
257
281
  } else {
258
- this.vorim.emit(event).catch(() => {
259
- });
282
+ try {
283
+ await this.vorim.emit(event);
284
+ } catch {
285
+ }
260
286
  }
261
287
  }
262
288
  };