@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.
- package/CHANGELOG.md +35 -0
- package/README.md +66 -0
- package/dist/index.cjs +171 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +166 -1
- package/dist/index.d.ts +166 -1
- package/dist/index.js +170 -8
- package/dist/index.js.map +1 -1
- package/dist/integrations/anthropic.cjs +25 -5
- package/dist/integrations/anthropic.cjs.map +1 -1
- package/dist/integrations/anthropic.d.cts +13 -0
- package/dist/integrations/anthropic.d.ts +13 -0
- package/dist/integrations/anthropic.js +25 -5
- package/dist/integrations/anthropic.js.map +1 -1
- package/dist/integrations/langchain.cjs +3 -4
- package/dist/integrations/langchain.cjs.map +1 -1
- package/dist/integrations/langchain.js +3 -4
- package/dist/integrations/langchain.js.map +1 -1
- package/dist/integrations/llamaindex.cjs +3 -4
- package/dist/integrations/llamaindex.cjs.map +1 -1
- package/dist/integrations/llamaindex.js +3 -4
- package/dist/integrations/llamaindex.js.map +1 -1
- package/dist/integrations/openai.cjs +37 -11
- package/dist/integrations/openai.cjs.map +1 -1
- package/dist/integrations/openai.d.cts +20 -0
- package/dist/integrations/openai.d.ts +20 -0
- package/dist/integrations/openai.js +37 -11
- package/dist/integrations/openai.js.map +1 -1
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/integrations/openai.ts","../../src/replay.ts"],"sourcesContent":["// ============================================================================\n// VORIM SDK — OpenAI Integration\n// Wraps OpenAI function calling with Vorim permission checks, audit trails,\n// and agent identity. Works with the OpenAI Node.js SDK (chat completions\n// with tool use).\n//\n// Peer dependency: openai >=4.0.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 OpenAI types (peer dependency — not bundled) ─────────────\n\ninterface ChatCompletionTool {\n type: 'function';\n function: {\n name: string;\n description?: string;\n parameters?: Record<string, unknown>;\n strict?: boolean;\n };\n}\n\ninterface ToolCall {\n id: string;\n type: 'function';\n function: {\n name: string;\n arguments: string;\n };\n}\n\ninterface ToolMessage {\n role: 'tool';\n tool_call_id: string;\n content: string;\n}\n\n// ─── Configuration ────────────────────────────────────────────────────────\n\nexport interface VorimToolDefinition<TArgs = Record<string, unknown>, TResult = unknown> {\n /** Tool name (must match the function name sent to OpenAI). */\n name: string;\n /** Description shown to the LLM. */\n description: string;\n /** JSON Schema for the tool's parameters. */\n parameters: Record<string, unknown>;\n /** The function to execute when the tool is called. */\n execute: (args: TArgs) => Promise<TResult>;\n /** Vorim permission scope required. @default 'agent:execute' */\n permission?: PermissionScope;\n /** Whether to use strict mode for OpenAI structured outputs. */\n strict?: boolean;\n}\n\nexport interface VorimOpenAIConfig {\n /** Vorim SDK instance. */\n vorim: VorimSDK;\n /** The Vorim agent_id. */\n agentId: string;\n /** Default permission scope for tools without an explicit one. @default 'agent:execute' */\n defaultPermission?: PermissionScope;\n /** Whether to emit audit events asynchronously. @default true */\n asyncAudit?: boolean;\n /**\n * Replayable agent decision evidence (VAIP -02). When provided, the\n * hashes are attached to every audit event the registry emits.\n * If `replay.tools` is not given, the registry's own tool catalogue\n * (the tools you `add()`) is used automatically.\n *\n * Not covered by the v0 canonical signature form; advisory only.\n */\n replay?: ReplayInputs;\n}\n\n// ─── Tool Registry ────────────────────────────────────────────────────────\n\n/**\n * Manages a set of tools with Vorim permission checks and audit logging.\n * Converts tools to OpenAI's `ChatCompletionTool` format and handles\n * execution of tool calls from the model's response.\n *\n * @example\n * ```ts\n * import OpenAI from \"openai\";\n * import createVorim from \"@vorim/sdk\";\n * import { VorimToolRegistry } from \"@vorim/sdk/integrations/openai\";\n *\n * const vorim = createVorim({ apiKey: \"agid_sk_live_...\" });\n * const openai = new OpenAI();\n *\n * const registry = new VorimToolRegistry({\n * vorim,\n * agentId: \"agid_acme_a1b2c3d4\",\n * });\n *\n * registry.add({\n * name: \"search_docs\",\n * description: \"Search internal documents\",\n * parameters: {\n * type: \"object\",\n * properties: { query: { type: \"string\" } },\n * required: [\"query\"],\n * },\n * execute: async ({ query }) => searchDocs(query),\n * permission: \"agent:read\",\n * });\n *\n * // Use with OpenAI chat completions\n * const response = await openai.chat.completions.create({\n * model: \"gpt-4o\",\n * messages,\n * tools: registry.toOpenAITools(),\n * });\n *\n * // Execute tool calls from the response\n * const toolMessages = await registry.executeToolCalls(\n * response.choices[0].message.tool_calls ?? []\n * );\n * ```\n */\nexport class VorimToolRegistry {\n private vorim: VorimSDK;\n private agentId: string;\n private defaultPermission: PermissionScope;\n private asyncAudit: boolean;\n private tools = new Map<string, VorimToolDefinition>();\n private replayInputs: ReplayInputs | undefined;\n private replayCache: Promise<ReplayContext> | null = null;\n\n constructor(config: VorimOpenAIConfig) {\n this.vorim = config.vorim;\n this.agentId = config.agentId;\n this.defaultPermission = config.defaultPermission ?? 'agent:execute';\n this.asyncAudit = config.asyncAudit ?? true;\n this.replayInputs = config.replay;\n }\n\n private async getReplayContext(): Promise<ReplayContext> {\n if (!this.replayInputs) return {};\n if (!this.replayCache) {\n // If caller didn't supply tools, derive them from the registry.\n const inputs: ReplayInputs = {\n ...this.replayInputs,\n tools: this.replayInputs.tools ?? this.deriveCatalogue(),\n };\n this.replayCache = prepareReplayContext(inputs);\n }\n return this.replayCache;\n }\n\n private deriveCatalogue(): CatalogueTool[] {\n return [...this.tools.values()].map(t => ({\n name: t.name,\n description: t.description,\n schema: t.parameters,\n }));\n }\n\n /** Register a tool. Invalidates the cached tool-catalogue hash. */\n add<TArgs, TResult>(definition: VorimToolDefinition<TArgs, TResult>): this {\n this.tools.set(definition.name, definition as VorimToolDefinition);\n this.replayCache = null;\n return this;\n }\n\n /** Register multiple tools. */\n addAll(definitions: VorimToolDefinition[]): this {\n for (const def of definitions) this.add(def);\n return this;\n }\n\n /** Convert registered tools to OpenAI's ChatCompletionTool format. */\n toOpenAITools(): ChatCompletionTool[] {\n return [...this.tools.values()].map(t => ({\n type: 'function' as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n ...(t.strict != null ? { strict: t.strict } : {}),\n },\n }));\n }\n\n /**\n * Execute tool calls from an OpenAI chat completion response.\n * Each call is checked against Vorim permissions and audited.\n * Returns an array of tool messages ready to append to the conversation.\n */\n async executeToolCalls(toolCalls: ToolCall[]): Promise<ToolMessage[]> {\n const results = await Promise.all(\n toolCalls.map(tc => this.executeSingleCall(tc)),\n );\n return results;\n }\n\n private async executeSingleCall(toolCall: ToolCall): Promise<ToolMessage> {\n const { id, function: fn } = toolCall;\n const definition = this.tools.get(fn.name);\n\n if (!definition) {\n return {\n role: 'tool',\n tool_call_id: id,\n content: JSON.stringify({ error: `Unknown tool: ${fn.name}` }),\n };\n }\n\n const scope = definition.permission ?? this.defaultPermission;\n let args: unknown;\n try {\n args = JSON.parse(fn.arguments);\n } catch {\n return {\n role: 'tool',\n tool_call_id: id,\n content: JSON.stringify({ error: `Invalid JSON arguments for ${fn.name}` }),\n };\n }\n\n // 1. Permission check\n const { allowed, reason } = await this.vorim.check(this.agentId, scope);\n const replayCtx = await this.getReplayContext();\n\n if (!allowed) {\n const event: AuditEventInput = {\n agent_id: this.agentId,\n event_type: 'tool_call',\n action: fn.name,\n resource: truncate(fn.arguments, 500),\n permission: scope,\n result: 'denied',\n metadata: { reason, framework: 'openai' },\n ...replayCtx,\n };\n this.emitAudit(event);\n return {\n role: 'tool',\n tool_call_id: id,\n content: JSON.stringify({ error: `Permission denied: ${scope}${reason ? ` — ${reason}` : ''}` }),\n };\n }\n\n // 2. Execute\n const start = Date.now();\n try {\n const result = await definition.execute(args as any);\n const content = typeof result === 'string' ? result : JSON.stringify(result);\n\n const event: AuditEventInput = {\n agent_id: this.agentId,\n event_type: 'tool_call',\n action: fn.name,\n resource: truncate(fn.arguments, 500),\n permission: scope,\n result: 'success',\n latency_ms: Date.now() - start,\n metadata: { framework: 'openai' },\n ...replayCtx,\n };\n this.emitAudit(event);\n\n return { role: 'tool', tool_call_id: id, content };\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n\n const event: AuditEventInput = {\n agent_id: this.agentId,\n event_type: 'tool_call',\n action: fn.name,\n resource: truncate(fn.arguments, 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: errMsg, framework: 'openai' },\n ...replayCtx,\n };\n this.emitAudit(event);\n\n return {\n role: 'tool',\n tool_call_id: id,\n content: JSON.stringify({ error: errMsg }),\n };\n }\n }\n\n private emitAudit(event: AuditEventInput): void {\n if (this.asyncAudit) {\n this.vorim.emit(event).catch(() => {});\n } else {\n // Caller is already in an async context, but we don't await here\n // in async mode. For sync audit, the executeToolCalls caller\n // should await the full pipeline.\n this.vorim.emit(event).catch(() => {});\n }\n }\n}\n\n// ─── Agent Loop ───────────────────────────────────────────────────────────\n\nexport interface VorimAgentLoopConfig extends VorimOpenAIConfig {\n /** OpenAI client instance. */\n openai: OpenAIClient;\n /** Model to use. @default 'gpt-4o' */\n model?: string;\n /** System prompt for the agent. */\n systemPrompt?: string;\n /** Maximum iterations before stopping. @default 10 */\n maxIterations?: number;\n}\n\n/** Minimal OpenAI client interface (avoids importing the full SDK). */\ninterface OpenAIClient {\n chat: {\n completions: {\n create(params: any): Promise<any>;\n };\n };\n}\n\n/**\n * Runs a complete agent loop with OpenAI function calling, Vorim\n * permission enforcement, and audit logging.\n *\n * @example\n * ```ts\n * import OpenAI from \"openai\";\n * import createVorim from \"@vorim/sdk\";\n * import { runAgentLoop, VorimToolRegistry } from \"@vorim/sdk/integrations/openai\";\n *\n * const registry = new VorimToolRegistry({ vorim, agentId });\n * registry.add({ name: \"search\", ... });\n *\n * const response = await runAgentLoop({\n * vorim,\n * agentId,\n * openai: new OpenAI(),\n * model: \"gpt-4o\",\n * systemPrompt: \"You are a helpful assistant.\",\n * registry,\n * userMessage: \"Find docs about onboarding\",\n * });\n * ```\n */\nexport async function runAgentLoop(\n config: VorimAgentLoopConfig & {\n registry: VorimToolRegistry;\n userMessage: string;\n },\n): Promise<string> {\n const { openai, model = 'gpt-4o', systemPrompt, maxIterations = 10, registry, userMessage } = config;\n\n const messages: any[] = [];\n if (systemPrompt) messages.push({ role: 'system', content: systemPrompt });\n messages.push({ role: 'user', content: userMessage });\n\n const tools = registry.toOpenAITools();\n\n for (let i = 0; i < maxIterations; i++) {\n const response = await openai.chat.completions.create({\n model,\n messages,\n ...(tools.length > 0 ? { tools } : {}),\n });\n\n const choice = response.choices[0];\n messages.push(choice.message);\n\n // If the model is done (no tool calls), return the response\n if (choice.finish_reason === 'stop' || !choice.message.tool_calls?.length) {\n return choice.message.content ?? '';\n }\n\n // Execute tool calls and add results to messages\n const toolMessages = await registry.executeToolCalls(choice.message.tool_calls);\n messages.push(...toolMessages);\n }\n\n return messages[messages.length - 1]?.content ?? '';\n}\n\n// ─── Agent Registration Helper ───────────────────────────────────────────\n\n/**\n * Registers a new agent with Vorim and returns a ready-to-use tool registry.\n *\n * @example\n * ```ts\n * const { agentId, registry } = await createVorimOpenAIAgent({\n * vorim,\n * name: \"support-agent\",\n * capabilities: [\"search\", \"email\"],\n * scopes: [\"agent:read\", \"agent:execute\", \"agent:communicate\"],\n * tools: [searchTool, emailTool],\n * });\n * ```\n */\nexport async function createVorimOpenAIAgent(config: {\n vorim: VorimSDK;\n name: string;\n description?: string;\n capabilities: string[];\n scopes: PermissionScope[];\n tools: VorimToolDefinition[];\n}) {\n const { vorim, name, description, capabilities, scopes, tools } = config;\n\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 const registry = new VorimToolRegistry({ vorim, agentId });\n registry.addAll(tools);\n\n return {\n agentId,\n registration,\n registry,\n privateKey: registration.private_key,\n };\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────\n\nfunction truncate(str: string, max: number): string {\n return str.length > max ? str.slice(0, max) + '…' : str;\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":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/integrations/openai.ts","../../src/replay.ts"],"sourcesContent":["// ============================================================================\n// VORIM SDK — OpenAI Integration\n// Wraps OpenAI function calling with Vorim permission checks, audit trails,\n// and agent identity. Works with the OpenAI Node.js SDK (chat completions\n// with tool use).\n//\n// Peer dependency: openai >=4.0.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 OpenAI types (peer dependency — not bundled) ─────────────\n\ninterface ChatCompletionTool {\n type: 'function';\n function: {\n name: string;\n description?: string;\n parameters?: Record<string, unknown>;\n strict?: boolean;\n };\n}\n\ninterface ToolCall {\n id: string;\n type: 'function';\n function: {\n name: string;\n arguments: string;\n };\n}\n\ninterface ToolMessage {\n role: 'tool';\n tool_call_id: string;\n content: string;\n}\n\n// ─── Configuration ────────────────────────────────────────────────────────\n\nexport interface VorimToolDefinition<TArgs = Record<string, unknown>, TResult = unknown> {\n /** Tool name (must match the function name sent to OpenAI). */\n name: string;\n /** Description shown to the LLM. */\n description: string;\n /** JSON Schema for the tool's parameters. */\n parameters: Record<string, unknown>;\n /** The function to execute when the tool is called. */\n execute: (args: TArgs) => Promise<TResult>;\n /** Vorim permission scope required. @default 'agent:execute' */\n permission?: PermissionScope;\n /** Whether to use strict mode for OpenAI structured outputs. */\n strict?: boolean;\n}\n\nexport interface VorimOpenAIConfig {\n /** Vorim SDK instance. */\n vorim: VorimSDK;\n /** The Vorim agent_id. */\n agentId: string;\n /** Default permission scope for tools without an explicit one. @default 'agent:execute' */\n defaultPermission?: PermissionScope;\n /** Whether to emit audit events asynchronously. @default true */\n asyncAudit?: boolean;\n /**\n * Gate each tool call through the runtime-control decision API\n * (`beforeAction`) instead of the lightweight permission check.\n * @default false\n *\n * When true, every tool call is evaluated against your live policy rules\n * (allow/deny/escalate) and the returned `decisionId` is linked onto each\n * audit event's `decision_id`, so runtime decisions tie back to the audit\n * trail. Requires the runtime_control plan feature (Growth+) and consumes\n * decision quota. When false (default) the integration uses the\n * fast permission check and emits audit events with no decision linkage.\n */\n useRuntimeControl?: boolean;\n /**\n * Replayable agent decision evidence (VAIP -02). When provided, the\n * hashes are attached to every audit event the registry emits.\n * If `replay.tools` is not given, the registry's own tool catalogue\n * (the tools you `add()`) is used automatically.\n *\n * Not covered by the v0 canonical signature form; advisory only.\n */\n replay?: ReplayInputs;\n}\n\n// ─── Tool Registry ────────────────────────────────────────────────────────\n\n/**\n * Manages a set of tools with Vorim permission checks and audit logging.\n * Converts tools to OpenAI's `ChatCompletionTool` format and handles\n * execution of tool calls from the model's response.\n *\n * @example\n * ```ts\n * import OpenAI from \"openai\";\n * import createVorim from \"@vorim/sdk\";\n * import { VorimToolRegistry } from \"@vorim/sdk/integrations/openai\";\n *\n * const vorim = createVorim({ apiKey: \"agid_sk_live_...\" });\n * const openai = new OpenAI();\n *\n * const registry = new VorimToolRegistry({\n * vorim,\n * agentId: \"agid_acme_a1b2c3d4\",\n * });\n *\n * registry.add({\n * name: \"search_docs\",\n * description: \"Search internal documents\",\n * parameters: {\n * type: \"object\",\n * properties: { query: { type: \"string\" } },\n * required: [\"query\"],\n * },\n * execute: async ({ query }) => searchDocs(query),\n * permission: \"agent:read\",\n * });\n *\n * // Use with OpenAI chat completions\n * const response = await openai.chat.completions.create({\n * model: \"gpt-4o\",\n * messages,\n * tools: registry.toOpenAITools(),\n * });\n *\n * // Execute tool calls from the response\n * const toolMessages = await registry.executeToolCalls(\n * response.choices[0].message.tool_calls ?? []\n * );\n * ```\n */\nexport class VorimToolRegistry {\n private vorim: VorimSDK;\n private agentId: string;\n private defaultPermission: PermissionScope;\n private asyncAudit: boolean;\n private useRuntimeControl: boolean;\n private tools = new Map<string, VorimToolDefinition>();\n private replayInputs: ReplayInputs | undefined;\n private replayCache: Promise<ReplayContext> | null = null;\n\n constructor(config: VorimOpenAIConfig) {\n this.vorim = config.vorim;\n this.agentId = config.agentId;\n this.defaultPermission = config.defaultPermission ?? 'agent:execute';\n this.asyncAudit = config.asyncAudit ?? true;\n this.useRuntimeControl = config.useRuntimeControl ?? false;\n this.replayInputs = config.replay;\n }\n\n /**\n * Gate a tool call. When useRuntimeControl is on, run the full runtime\n * decision (beforeAction) and surface its decisionId for audit linkage;\n * otherwise use the lightweight permission check.\n */\n private async gate(\n scope: PermissionScope,\n actionTarget: string,\n payload?: Record<string, unknown>,\n ): Promise<{ allowed: boolean; reason?: string; decisionId?: string }> {\n if (this.useRuntimeControl) {\n const d = await this.vorim.beforeAction(\n { agentId: this.agentId, actionType: 'tool_call', actionTarget, requiredScope: scope, payload },\n { throwOnDeny: false },\n );\n return {\n allowed: d.decision === 'allow' || d.decision === 'modify' || d.decision === 'fallback',\n reason: d.reason,\n decisionId: d.decisionId || undefined,\n };\n }\n const { allowed, reason } = await this.vorim.check(this.agentId, scope);\n return { allowed, reason };\n }\n\n private async getReplayContext(): Promise<ReplayContext> {\n if (!this.replayInputs) return {};\n if (!this.replayCache) {\n // If caller didn't supply tools, derive them from the registry.\n const inputs: ReplayInputs = {\n ...this.replayInputs,\n tools: this.replayInputs.tools ?? this.deriveCatalogue(),\n };\n this.replayCache = prepareReplayContext(inputs);\n }\n return this.replayCache;\n }\n\n private deriveCatalogue(): CatalogueTool[] {\n return [...this.tools.values()].map(t => ({\n name: t.name,\n description: t.description,\n schema: t.parameters,\n }));\n }\n\n /** Register a tool. Invalidates the cached tool-catalogue hash. */\n add<TArgs, TResult>(definition: VorimToolDefinition<TArgs, TResult>): this {\n this.tools.set(definition.name, definition as VorimToolDefinition);\n this.replayCache = null;\n return this;\n }\n\n /** Register multiple tools. */\n addAll(definitions: VorimToolDefinition[]): this {\n for (const def of definitions) this.add(def);\n return this;\n }\n\n /** Convert registered tools to OpenAI's ChatCompletionTool format. */\n toOpenAITools(): ChatCompletionTool[] {\n return [...this.tools.values()].map(t => ({\n type: 'function' as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n ...(t.strict != null ? { strict: t.strict } : {}),\n },\n }));\n }\n\n /**\n * Execute tool calls from an OpenAI chat completion response.\n * Each call is checked against Vorim permissions and audited.\n * Returns an array of tool messages ready to append to the conversation.\n */\n async executeToolCalls(toolCalls: ToolCall[]): Promise<ToolMessage[]> {\n const results = await Promise.all(\n toolCalls.map(tc => this.executeSingleCall(tc)),\n );\n return results;\n }\n\n private async executeSingleCall(toolCall: ToolCall): Promise<ToolMessage> {\n const { id, function: fn } = toolCall;\n const definition = this.tools.get(fn.name);\n\n if (!definition) {\n return {\n role: 'tool',\n tool_call_id: id,\n content: JSON.stringify({ error: `Unknown tool: ${fn.name}` }),\n };\n }\n\n const scope = definition.permission ?? this.defaultPermission;\n let args: unknown;\n try {\n args = JSON.parse(fn.arguments);\n } catch {\n return {\n role: 'tool',\n tool_call_id: id,\n content: JSON.stringify({ error: `Invalid JSON arguments for ${fn.name}` }),\n };\n }\n\n // 1. Gate: runtime decision (with decisionId linkage) or permission check.\n const { allowed, reason, decisionId } = await this.gate(scope, fn.name, args as Record<string, unknown>);\n const replayCtx = await this.getReplayContext();\n\n if (!allowed) {\n const event: AuditEventInput = {\n agent_id: this.agentId,\n event_type: 'tool_call',\n action: fn.name,\n resource: truncate(fn.arguments, 500),\n permission: scope,\n result: 'denied',\n ...(decisionId ? { decision_id: decisionId } : {}),\n metadata: { reason, framework: 'openai' },\n ...replayCtx,\n };\n await this.emitAudit(event);\n return {\n role: 'tool',\n tool_call_id: id,\n content: JSON.stringify({ error: `Permission denied: ${scope}${reason ? ` — ${reason}` : ''}` }),\n };\n }\n\n // 2. Execute\n const start = Date.now();\n try {\n const result = await definition.execute(args as any);\n const content = typeof result === 'string' ? result : JSON.stringify(result);\n\n const event: AuditEventInput = {\n agent_id: this.agentId,\n event_type: 'tool_call',\n action: fn.name,\n resource: truncate(fn.arguments, 500),\n permission: scope,\n result: 'success',\n latency_ms: Date.now() - start,\n ...(decisionId ? { decision_id: decisionId } : {}),\n metadata: { framework: 'openai' },\n ...replayCtx,\n };\n await this.emitAudit(event);\n\n return { role: 'tool', tool_call_id: id, content };\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n\n const event: AuditEventInput = {\n agent_id: this.agentId,\n event_type: 'tool_call',\n action: fn.name,\n resource: truncate(fn.arguments, 500),\n permission: scope,\n result: 'error',\n latency_ms: Date.now() - start,\n error_code: err instanceof Error ? err.name : 'UNKNOWN',\n ...(decisionId ? { decision_id: decisionId } : {}),\n metadata: { error: errMsg, framework: 'openai' },\n ...replayCtx,\n };\n await this.emitAudit(event);\n\n return {\n role: 'tool',\n tool_call_id: id,\n content: JSON.stringify({ error: errMsg }),\n };\n }\n }\n\n private async emitAudit(event: AuditEventInput): Promise<void> {\n if (this.asyncAudit) {\n // Fire-and-forget: don't block the tool pipeline on the audit write.\n this.vorim.emit(event).catch(() => {});\n } else {\n // Synchronous mode: actually await so the audit is flushed before the\n // caller continues. (Previously both branches were identical and the\n // promise was never awaited — asyncAudit=false did nothing.)\n try { await this.vorim.emit(event); } catch { /* best-effort */ }\n }\n }\n}\n\n// ─── Agent Loop ───────────────────────────────────────────────────────────\n\nexport interface VorimAgentLoopConfig extends VorimOpenAIConfig {\n /** OpenAI client instance. */\n openai: OpenAIClient;\n /** Model to use. @default 'gpt-4o' */\n model?: string;\n /** System prompt for the agent. */\n systemPrompt?: string;\n /** Maximum iterations before stopping. @default 10 */\n maxIterations?: number;\n}\n\n/** Minimal OpenAI client interface (avoids importing the full SDK). */\ninterface OpenAIClient {\n chat: {\n completions: {\n create(params: any): Promise<any>;\n };\n };\n}\n\n/**\n * Runs a complete agent loop with OpenAI function calling, Vorim\n * permission enforcement, and audit logging.\n *\n * @example\n * ```ts\n * import OpenAI from \"openai\";\n * import createVorim from \"@vorim/sdk\";\n * import { runAgentLoop, VorimToolRegistry } from \"@vorim/sdk/integrations/openai\";\n *\n * const registry = new VorimToolRegistry({ vorim, agentId });\n * registry.add({ name: \"search\", ... });\n *\n * const response = await runAgentLoop({\n * vorim,\n * agentId,\n * openai: new OpenAI(),\n * model: \"gpt-4o\",\n * systemPrompt: \"You are a helpful assistant.\",\n * registry,\n * userMessage: \"Find docs about onboarding\",\n * });\n * ```\n */\nexport async function runAgentLoop(\n config: VorimAgentLoopConfig & {\n registry: VorimToolRegistry;\n userMessage: string;\n },\n): Promise<string> {\n const { openai, model = 'gpt-4o', systemPrompt, maxIterations = 10, registry, userMessage } = config;\n\n const messages: any[] = [];\n if (systemPrompt) messages.push({ role: 'system', content: systemPrompt });\n messages.push({ role: 'user', content: userMessage });\n\n const tools = registry.toOpenAITools();\n\n for (let i = 0; i < maxIterations; i++) {\n const response = await openai.chat.completions.create({\n model,\n messages,\n ...(tools.length > 0 ? { tools } : {}),\n });\n\n const choice = response.choices[0];\n messages.push(choice.message);\n\n // If the model is done (no tool calls), return the response\n if (choice.finish_reason === 'stop' || !choice.message.tool_calls?.length) {\n return choice.message.content ?? '';\n }\n\n // Execute tool calls and add results to messages\n const toolMessages = await registry.executeToolCalls(choice.message.tool_calls);\n messages.push(...toolMessages);\n }\n\n return messages[messages.length - 1]?.content ?? '';\n}\n\n// ─── Agent Registration Helper ───────────────────────────────────────────\n\n/**\n * Registers a new agent with Vorim and returns a ready-to-use tool registry.\n *\n * @example\n * ```ts\n * const { agentId, registry } = await createVorimOpenAIAgent({\n * vorim,\n * name: \"support-agent\",\n * capabilities: [\"search\", \"email\"],\n * scopes: [\"agent:read\", \"agent:execute\", \"agent:communicate\"],\n * tools: [searchTool, emailTool],\n * });\n * ```\n */\nexport async function createVorimOpenAIAgent(config: {\n vorim: VorimSDK;\n name: string;\n description?: string;\n capabilities: string[];\n scopes: PermissionScope[];\n tools: VorimToolDefinition[];\n}) {\n const { vorim, name, description, capabilities, scopes, tools } = config;\n\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 const registry = new VorimToolRegistry({ vorim, agentId });\n registry.addAll(tools);\n\n return {\n agentId,\n registration,\n registry,\n privateKey: registration.private_key,\n };\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────\n\nfunction truncate(str: string, max: number): string {\n return str.length > max ? str.slice(0, max) + '…' : str;\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":[]}
|
|
@@ -45,6 +45,19 @@ interface VorimOpenAIConfig {
|
|
|
45
45
|
defaultPermission?: PermissionScope;
|
|
46
46
|
/** Whether to emit audit events asynchronously. @default true */
|
|
47
47
|
asyncAudit?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Gate each tool call through the runtime-control decision API
|
|
50
|
+
* (`beforeAction`) instead of the lightweight permission check.
|
|
51
|
+
* @default false
|
|
52
|
+
*
|
|
53
|
+
* When true, every tool call is evaluated against your live policy rules
|
|
54
|
+
* (allow/deny/escalate) and the returned `decisionId` is linked onto each
|
|
55
|
+
* audit event's `decision_id`, so runtime decisions tie back to the audit
|
|
56
|
+
* trail. Requires the runtime_control plan feature (Growth+) and consumes
|
|
57
|
+
* decision quota. When false (default) the integration uses the
|
|
58
|
+
* fast permission check and emits audit events with no decision linkage.
|
|
59
|
+
*/
|
|
60
|
+
useRuntimeControl?: boolean;
|
|
48
61
|
/**
|
|
49
62
|
* Replayable agent decision evidence (VAIP -02). When provided, the
|
|
50
63
|
* hashes are attached to every audit event the registry emits.
|
|
@@ -104,10 +117,17 @@ declare class VorimToolRegistry {
|
|
|
104
117
|
private agentId;
|
|
105
118
|
private defaultPermission;
|
|
106
119
|
private asyncAudit;
|
|
120
|
+
private useRuntimeControl;
|
|
107
121
|
private tools;
|
|
108
122
|
private replayInputs;
|
|
109
123
|
private replayCache;
|
|
110
124
|
constructor(config: VorimOpenAIConfig);
|
|
125
|
+
/**
|
|
126
|
+
* Gate a tool call. When useRuntimeControl is on, run the full runtime
|
|
127
|
+
* decision (beforeAction) and surface its decisionId for audit linkage;
|
|
128
|
+
* otherwise use the lightweight permission check.
|
|
129
|
+
*/
|
|
130
|
+
private gate;
|
|
111
131
|
private getReplayContext;
|
|
112
132
|
private deriveCatalogue;
|
|
113
133
|
/** Register a tool. Invalidates the cached tool-catalogue hash. */
|
|
@@ -45,6 +45,19 @@ interface VorimOpenAIConfig {
|
|
|
45
45
|
defaultPermission?: PermissionScope;
|
|
46
46
|
/** Whether to emit audit events asynchronously. @default true */
|
|
47
47
|
asyncAudit?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Gate each tool call through the runtime-control decision API
|
|
50
|
+
* (`beforeAction`) instead of the lightweight permission check.
|
|
51
|
+
* @default false
|
|
52
|
+
*
|
|
53
|
+
* When true, every tool call is evaluated against your live policy rules
|
|
54
|
+
* (allow/deny/escalate) and the returned `decisionId` is linked onto each
|
|
55
|
+
* audit event's `decision_id`, so runtime decisions tie back to the audit
|
|
56
|
+
* trail. Requires the runtime_control plan feature (Growth+) and consumes
|
|
57
|
+
* decision quota. When false (default) the integration uses the
|
|
58
|
+
* fast permission check and emits audit events with no decision linkage.
|
|
59
|
+
*/
|
|
60
|
+
useRuntimeControl?: boolean;
|
|
48
61
|
/**
|
|
49
62
|
* Replayable agent decision evidence (VAIP -02). When provided, the
|
|
50
63
|
* hashes are attached to every audit event the registry emits.
|
|
@@ -104,10 +117,17 @@ declare class VorimToolRegistry {
|
|
|
104
117
|
private agentId;
|
|
105
118
|
private defaultPermission;
|
|
106
119
|
private asyncAudit;
|
|
120
|
+
private useRuntimeControl;
|
|
107
121
|
private tools;
|
|
108
122
|
private replayInputs;
|
|
109
123
|
private replayCache;
|
|
110
124
|
constructor(config: VorimOpenAIConfig);
|
|
125
|
+
/**
|
|
126
|
+
* Gate a tool call. When useRuntimeControl is on, run the full runtime
|
|
127
|
+
* decision (beforeAction) and surface its decisionId for audit linkage;
|
|
128
|
+
* otherwise use the lightweight permission check.
|
|
129
|
+
*/
|
|
130
|
+
private gate;
|
|
111
131
|
private getReplayContext;
|
|
112
132
|
private deriveCatalogue;
|
|
113
133
|
/** Register a tool. Invalidates the cached tool-catalogue hash. */
|
|
@@ -16,10 +16,9 @@ function jcsCanonicalise(value) {
|
|
|
16
16
|
return "[" + value.map(jcsCanonicalise).join(",") + "]";
|
|
17
17
|
}
|
|
18
18
|
if (typeof value === "object") {
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
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}`);
|
|
@@ -70,6 +69,7 @@ var VorimToolRegistry = class {
|
|
|
70
69
|
agentId;
|
|
71
70
|
defaultPermission;
|
|
72
71
|
asyncAudit;
|
|
72
|
+
useRuntimeControl;
|
|
73
73
|
tools = /* @__PURE__ */ new Map();
|
|
74
74
|
replayInputs;
|
|
75
75
|
replayCache = null;
|
|
@@ -78,8 +78,29 @@ var VorimToolRegistry = class {
|
|
|
78
78
|
this.agentId = config.agentId;
|
|
79
79
|
this.defaultPermission = config.defaultPermission ?? "agent:execute";
|
|
80
80
|
this.asyncAudit = config.asyncAudit ?? true;
|
|
81
|
+
this.useRuntimeControl = config.useRuntimeControl ?? false;
|
|
81
82
|
this.replayInputs = config.replay;
|
|
82
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Gate a tool call. When useRuntimeControl is on, run the full runtime
|
|
86
|
+
* decision (beforeAction) and surface its decisionId for audit linkage;
|
|
87
|
+
* otherwise use the lightweight permission check.
|
|
88
|
+
*/
|
|
89
|
+
async gate(scope, actionTarget, payload) {
|
|
90
|
+
if (this.useRuntimeControl) {
|
|
91
|
+
const d = await this.vorim.beforeAction(
|
|
92
|
+
{ agentId: this.agentId, actionType: "tool_call", actionTarget, requiredScope: scope, payload },
|
|
93
|
+
{ throwOnDeny: false }
|
|
94
|
+
);
|
|
95
|
+
return {
|
|
96
|
+
allowed: d.decision === "allow" || d.decision === "modify" || d.decision === "fallback",
|
|
97
|
+
reason: d.reason,
|
|
98
|
+
decisionId: d.decisionId || void 0
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const { allowed, reason } = await this.vorim.check(this.agentId, scope);
|
|
102
|
+
return { allowed, reason };
|
|
103
|
+
}
|
|
83
104
|
async getReplayContext() {
|
|
84
105
|
if (!this.replayInputs) return {};
|
|
85
106
|
if (!this.replayCache) {
|
|
@@ -153,7 +174,7 @@ var VorimToolRegistry = class {
|
|
|
153
174
|
content: JSON.stringify({ error: `Invalid JSON arguments for ${fn.name}` })
|
|
154
175
|
};
|
|
155
176
|
}
|
|
156
|
-
const { allowed, reason } = await this.
|
|
177
|
+
const { allowed, reason, decisionId } = await this.gate(scope, fn.name, args);
|
|
157
178
|
const replayCtx = await this.getReplayContext();
|
|
158
179
|
if (!allowed) {
|
|
159
180
|
const event = {
|
|
@@ -163,10 +184,11 @@ var VorimToolRegistry = class {
|
|
|
163
184
|
resource: truncate(fn.arguments, 500),
|
|
164
185
|
permission: scope,
|
|
165
186
|
result: "denied",
|
|
187
|
+
...decisionId ? { decision_id: decisionId } : {},
|
|
166
188
|
metadata: { reason, framework: "openai" },
|
|
167
189
|
...replayCtx
|
|
168
190
|
};
|
|
169
|
-
this.emitAudit(event);
|
|
191
|
+
await this.emitAudit(event);
|
|
170
192
|
return {
|
|
171
193
|
role: "tool",
|
|
172
194
|
tool_call_id: id,
|
|
@@ -185,10 +207,11 @@ var VorimToolRegistry = class {
|
|
|
185
207
|
permission: scope,
|
|
186
208
|
result: "success",
|
|
187
209
|
latency_ms: Date.now() - start,
|
|
210
|
+
...decisionId ? { decision_id: decisionId } : {},
|
|
188
211
|
metadata: { framework: "openai" },
|
|
189
212
|
...replayCtx
|
|
190
213
|
};
|
|
191
|
-
this.emitAudit(event);
|
|
214
|
+
await this.emitAudit(event);
|
|
192
215
|
return { role: "tool", tool_call_id: id, content };
|
|
193
216
|
} catch (err) {
|
|
194
217
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -201,10 +224,11 @@ var VorimToolRegistry = class {
|
|
|
201
224
|
result: "error",
|
|
202
225
|
latency_ms: Date.now() - start,
|
|
203
226
|
error_code: err instanceof Error ? err.name : "UNKNOWN",
|
|
227
|
+
...decisionId ? { decision_id: decisionId } : {},
|
|
204
228
|
metadata: { error: errMsg, framework: "openai" },
|
|
205
229
|
...replayCtx
|
|
206
230
|
};
|
|
207
|
-
this.emitAudit(event);
|
|
231
|
+
await this.emitAudit(event);
|
|
208
232
|
return {
|
|
209
233
|
role: "tool",
|
|
210
234
|
tool_call_id: id,
|
|
@@ -212,13 +236,15 @@ var VorimToolRegistry = class {
|
|
|
212
236
|
};
|
|
213
237
|
}
|
|
214
238
|
}
|
|
215
|
-
emitAudit(event) {
|
|
239
|
+
async emitAudit(event) {
|
|
216
240
|
if (this.asyncAudit) {
|
|
217
241
|
this.vorim.emit(event).catch(() => {
|
|
218
242
|
});
|
|
219
243
|
} else {
|
|
220
|
-
|
|
221
|
-
|
|
244
|
+
try {
|
|
245
|
+
await this.vorim.emit(event);
|
|
246
|
+
} catch {
|
|
247
|
+
}
|
|
222
248
|
}
|
|
223
249
|
}
|
|
224
250
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/replay.ts","../../src/integrations/openai.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 — OpenAI Integration\n// Wraps OpenAI function calling with Vorim permission checks, audit trails,\n// and agent identity. Works with the OpenAI Node.js SDK (chat completions\n// with tool use).\n//\n// Peer dependency: openai >=4.0.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 OpenAI types (peer dependency — not bundled) ─────────────\n\ninterface ChatCompletionTool {\n type: 'function';\n function: {\n name: string;\n description?: string;\n parameters?: Record<string, unknown>;\n strict?: boolean;\n };\n}\n\ninterface ToolCall {\n id: string;\n type: 'function';\n function: {\n name: string;\n arguments: string;\n };\n}\n\ninterface ToolMessage {\n role: 'tool';\n tool_call_id: string;\n content: string;\n}\n\n// ─── Configuration ────────────────────────────────────────────────────────\n\nexport interface VorimToolDefinition<TArgs = Record<string, unknown>, TResult = unknown> {\n /** Tool name (must match the function name sent to OpenAI). */\n name: string;\n /** Description shown to the LLM. */\n description: string;\n /** JSON Schema for the tool's parameters. */\n parameters: Record<string, unknown>;\n /** The function to execute when the tool is called. */\n execute: (args: TArgs) => Promise<TResult>;\n /** Vorim permission scope required. @default 'agent:execute' */\n permission?: PermissionScope;\n /** Whether to use strict mode for OpenAI structured outputs. */\n strict?: boolean;\n}\n\nexport interface VorimOpenAIConfig {\n /** Vorim SDK instance. */\n vorim: VorimSDK;\n /** The Vorim agent_id. */\n agentId: string;\n /** Default permission scope for tools without an explicit one. @default 'agent:execute' */\n defaultPermission?: PermissionScope;\n /** Whether to emit audit events asynchronously. @default true */\n asyncAudit?: boolean;\n /**\n * Replayable agent decision evidence (VAIP -02). When provided, the\n * hashes are attached to every audit event the registry emits.\n * If `replay.tools` is not given, the registry's own tool catalogue\n * (the tools you `add()`) is used automatically.\n *\n * Not covered by the v0 canonical signature form; advisory only.\n */\n replay?: ReplayInputs;\n}\n\n// ─── Tool Registry ────────────────────────────────────────────────────────\n\n/**\n * Manages a set of tools with Vorim permission checks and audit logging.\n * Converts tools to OpenAI's `ChatCompletionTool` format and handles\n * execution of tool calls from the model's response.\n *\n * @example\n * ```ts\n * import OpenAI from \"openai\";\n * import createVorim from \"@vorim/sdk\";\n * import { VorimToolRegistry } from \"@vorim/sdk/integrations/openai\";\n *\n * const vorim = createVorim({ apiKey: \"agid_sk_live_...\" });\n * const openai = new OpenAI();\n *\n * const registry = new VorimToolRegistry({\n * vorim,\n * agentId: \"agid_acme_a1b2c3d4\",\n * });\n *\n * registry.add({\n * name: \"search_docs\",\n * description: \"Search internal documents\",\n * parameters: {\n * type: \"object\",\n * properties: { query: { type: \"string\" } },\n * required: [\"query\"],\n * },\n * execute: async ({ query }) => searchDocs(query),\n * permission: \"agent:read\",\n * });\n *\n * // Use with OpenAI chat completions\n * const response = await openai.chat.completions.create({\n * model: \"gpt-4o\",\n * messages,\n * tools: registry.toOpenAITools(),\n * });\n *\n * // Execute tool calls from the response\n * const toolMessages = await registry.executeToolCalls(\n * response.choices[0].message.tool_calls ?? []\n * );\n * ```\n */\nexport class VorimToolRegistry {\n private vorim: VorimSDK;\n private agentId: string;\n private defaultPermission: PermissionScope;\n private asyncAudit: boolean;\n private tools = new Map<string, VorimToolDefinition>();\n private replayInputs: ReplayInputs | undefined;\n private replayCache: Promise<ReplayContext> | null = null;\n\n constructor(config: VorimOpenAIConfig) {\n this.vorim = config.vorim;\n this.agentId = config.agentId;\n this.defaultPermission = config.defaultPermission ?? 'agent:execute';\n this.asyncAudit = config.asyncAudit ?? true;\n this.replayInputs = config.replay;\n }\n\n private async getReplayContext(): Promise<ReplayContext> {\n if (!this.replayInputs) return {};\n if (!this.replayCache) {\n // If caller didn't supply tools, derive them from the registry.\n const inputs: ReplayInputs = {\n ...this.replayInputs,\n tools: this.replayInputs.tools ?? this.deriveCatalogue(),\n };\n this.replayCache = prepareReplayContext(inputs);\n }\n return this.replayCache;\n }\n\n private deriveCatalogue(): CatalogueTool[] {\n return [...this.tools.values()].map(t => ({\n name: t.name,\n description: t.description,\n schema: t.parameters,\n }));\n }\n\n /** Register a tool. Invalidates the cached tool-catalogue hash. */\n add<TArgs, TResult>(definition: VorimToolDefinition<TArgs, TResult>): this {\n this.tools.set(definition.name, definition as VorimToolDefinition);\n this.replayCache = null;\n return this;\n }\n\n /** Register multiple tools. */\n addAll(definitions: VorimToolDefinition[]): this {\n for (const def of definitions) this.add(def);\n return this;\n }\n\n /** Convert registered tools to OpenAI's ChatCompletionTool format. */\n toOpenAITools(): ChatCompletionTool[] {\n return [...this.tools.values()].map(t => ({\n type: 'function' as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n ...(t.strict != null ? { strict: t.strict } : {}),\n },\n }));\n }\n\n /**\n * Execute tool calls from an OpenAI chat completion response.\n * Each call is checked against Vorim permissions and audited.\n * Returns an array of tool messages ready to append to the conversation.\n */\n async executeToolCalls(toolCalls: ToolCall[]): Promise<ToolMessage[]> {\n const results = await Promise.all(\n toolCalls.map(tc => this.executeSingleCall(tc)),\n );\n return results;\n }\n\n private async executeSingleCall(toolCall: ToolCall): Promise<ToolMessage> {\n const { id, function: fn } = toolCall;\n const definition = this.tools.get(fn.name);\n\n if (!definition) {\n return {\n role: 'tool',\n tool_call_id: id,\n content: JSON.stringify({ error: `Unknown tool: ${fn.name}` }),\n };\n }\n\n const scope = definition.permission ?? this.defaultPermission;\n let args: unknown;\n try {\n args = JSON.parse(fn.arguments);\n } catch {\n return {\n role: 'tool',\n tool_call_id: id,\n content: JSON.stringify({ error: `Invalid JSON arguments for ${fn.name}` }),\n };\n }\n\n // 1. Permission check\n const { allowed, reason } = await this.vorim.check(this.agentId, scope);\n const replayCtx = await this.getReplayContext();\n\n if (!allowed) {\n const event: AuditEventInput = {\n agent_id: this.agentId,\n event_type: 'tool_call',\n action: fn.name,\n resource: truncate(fn.arguments, 500),\n permission: scope,\n result: 'denied',\n metadata: { reason, framework: 'openai' },\n ...replayCtx,\n };\n this.emitAudit(event);\n return {\n role: 'tool',\n tool_call_id: id,\n content: JSON.stringify({ error: `Permission denied: ${scope}${reason ? ` — ${reason}` : ''}` }),\n };\n }\n\n // 2. Execute\n const start = Date.now();\n try {\n const result = await definition.execute(args as any);\n const content = typeof result === 'string' ? result : JSON.stringify(result);\n\n const event: AuditEventInput = {\n agent_id: this.agentId,\n event_type: 'tool_call',\n action: fn.name,\n resource: truncate(fn.arguments, 500),\n permission: scope,\n result: 'success',\n latency_ms: Date.now() - start,\n metadata: { framework: 'openai' },\n ...replayCtx,\n };\n this.emitAudit(event);\n\n return { role: 'tool', tool_call_id: id, content };\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n\n const event: AuditEventInput = {\n agent_id: this.agentId,\n event_type: 'tool_call',\n action: fn.name,\n resource: truncate(fn.arguments, 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: errMsg, framework: 'openai' },\n ...replayCtx,\n };\n this.emitAudit(event);\n\n return {\n role: 'tool',\n tool_call_id: id,\n content: JSON.stringify({ error: errMsg }),\n };\n }\n }\n\n private emitAudit(event: AuditEventInput): void {\n if (this.asyncAudit) {\n this.vorim.emit(event).catch(() => {});\n } else {\n // Caller is already in an async context, but we don't await here\n // in async mode. For sync audit, the executeToolCalls caller\n // should await the full pipeline.\n this.vorim.emit(event).catch(() => {});\n }\n }\n}\n\n// ─── Agent Loop ───────────────────────────────────────────────────────────\n\nexport interface VorimAgentLoopConfig extends VorimOpenAIConfig {\n /** OpenAI client instance. */\n openai: OpenAIClient;\n /** Model to use. @default 'gpt-4o' */\n model?: string;\n /** System prompt for the agent. */\n systemPrompt?: string;\n /** Maximum iterations before stopping. @default 10 */\n maxIterations?: number;\n}\n\n/** Minimal OpenAI client interface (avoids importing the full SDK). */\ninterface OpenAIClient {\n chat: {\n completions: {\n create(params: any): Promise<any>;\n };\n };\n}\n\n/**\n * Runs a complete agent loop with OpenAI function calling, Vorim\n * permission enforcement, and audit logging.\n *\n * @example\n * ```ts\n * import OpenAI from \"openai\";\n * import createVorim from \"@vorim/sdk\";\n * import { runAgentLoop, VorimToolRegistry } from \"@vorim/sdk/integrations/openai\";\n *\n * const registry = new VorimToolRegistry({ vorim, agentId });\n * registry.add({ name: \"search\", ... });\n *\n * const response = await runAgentLoop({\n * vorim,\n * agentId,\n * openai: new OpenAI(),\n * model: \"gpt-4o\",\n * systemPrompt: \"You are a helpful assistant.\",\n * registry,\n * userMessage: \"Find docs about onboarding\",\n * });\n * ```\n */\nexport async function runAgentLoop(\n config: VorimAgentLoopConfig & {\n registry: VorimToolRegistry;\n userMessage: string;\n },\n): Promise<string> {\n const { openai, model = 'gpt-4o', systemPrompt, maxIterations = 10, registry, userMessage } = config;\n\n const messages: any[] = [];\n if (systemPrompt) messages.push({ role: 'system', content: systemPrompt });\n messages.push({ role: 'user', content: userMessage });\n\n const tools = registry.toOpenAITools();\n\n for (let i = 0; i < maxIterations; i++) {\n const response = await openai.chat.completions.create({\n model,\n messages,\n ...(tools.length > 0 ? { tools } : {}),\n });\n\n const choice = response.choices[0];\n messages.push(choice.message);\n\n // If the model is done (no tool calls), return the response\n if (choice.finish_reason === 'stop' || !choice.message.tool_calls?.length) {\n return choice.message.content ?? '';\n }\n\n // Execute tool calls and add results to messages\n const toolMessages = await registry.executeToolCalls(choice.message.tool_calls);\n messages.push(...toolMessages);\n }\n\n return messages[messages.length - 1]?.content ?? '';\n}\n\n// ─── Agent Registration Helper ───────────────────────────────────────────\n\n/**\n * Registers a new agent with Vorim and returns a ready-to-use tool registry.\n *\n * @example\n * ```ts\n * const { agentId, registry } = await createVorimOpenAIAgent({\n * vorim,\n * name: \"support-agent\",\n * capabilities: [\"search\", \"email\"],\n * scopes: [\"agent:read\", \"agent:execute\", \"agent:communicate\"],\n * tools: [searchTool, emailTool],\n * });\n * ```\n */\nexport async function createVorimOpenAIAgent(config: {\n vorim: VorimSDK;\n name: string;\n description?: string;\n capabilities: string[];\n scopes: PermissionScope[];\n tools: VorimToolDefinition[];\n}) {\n const { vorim, name, description, capabilities, scopes, tools } = config;\n\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 const registry = new VorimToolRegistry({ vorim, agentId });\n registry.addAll(tools);\n\n return {\n agentId,\n registration,\n registry,\n privateKey: registration.private_key,\n };\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────\n\nfunction truncate(str: string, max: number): string {\n return str.length > max ? str.slice(0, max) + '…' : str;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/replay.ts","../../src/integrations/openai.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 — OpenAI Integration\n// Wraps OpenAI function calling with Vorim permission checks, audit trails,\n// and agent identity. Works with the OpenAI Node.js SDK (chat completions\n// with tool use).\n//\n// Peer dependency: openai >=4.0.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 OpenAI types (peer dependency — not bundled) ─────────────\n\ninterface ChatCompletionTool {\n type: 'function';\n function: {\n name: string;\n description?: string;\n parameters?: Record<string, unknown>;\n strict?: boolean;\n };\n}\n\ninterface ToolCall {\n id: string;\n type: 'function';\n function: {\n name: string;\n arguments: string;\n };\n}\n\ninterface ToolMessage {\n role: 'tool';\n tool_call_id: string;\n content: string;\n}\n\n// ─── Configuration ────────────────────────────────────────────────────────\n\nexport interface VorimToolDefinition<TArgs = Record<string, unknown>, TResult = unknown> {\n /** Tool name (must match the function name sent to OpenAI). */\n name: string;\n /** Description shown to the LLM. */\n description: string;\n /** JSON Schema for the tool's parameters. */\n parameters: Record<string, unknown>;\n /** The function to execute when the tool is called. */\n execute: (args: TArgs) => Promise<TResult>;\n /** Vorim permission scope required. @default 'agent:execute' */\n permission?: PermissionScope;\n /** Whether to use strict mode for OpenAI structured outputs. */\n strict?: boolean;\n}\n\nexport interface VorimOpenAIConfig {\n /** Vorim SDK instance. */\n vorim: VorimSDK;\n /** The Vorim agent_id. */\n agentId: string;\n /** Default permission scope for tools without an explicit one. @default 'agent:execute' */\n defaultPermission?: PermissionScope;\n /** Whether to emit audit events asynchronously. @default true */\n asyncAudit?: boolean;\n /**\n * Gate each tool call through the runtime-control decision API\n * (`beforeAction`) instead of the lightweight permission check.\n * @default false\n *\n * When true, every tool call is evaluated against your live policy rules\n * (allow/deny/escalate) and the returned `decisionId` is linked onto each\n * audit event's `decision_id`, so runtime decisions tie back to the audit\n * trail. Requires the runtime_control plan feature (Growth+) and consumes\n * decision quota. When false (default) the integration uses the\n * fast permission check and emits audit events with no decision linkage.\n */\n useRuntimeControl?: boolean;\n /**\n * Replayable agent decision evidence (VAIP -02). When provided, the\n * hashes are attached to every audit event the registry emits.\n * If `replay.tools` is not given, the registry's own tool catalogue\n * (the tools you `add()`) is used automatically.\n *\n * Not covered by the v0 canonical signature form; advisory only.\n */\n replay?: ReplayInputs;\n}\n\n// ─── Tool Registry ────────────────────────────────────────────────────────\n\n/**\n * Manages a set of tools with Vorim permission checks and audit logging.\n * Converts tools to OpenAI's `ChatCompletionTool` format and handles\n * execution of tool calls from the model's response.\n *\n * @example\n * ```ts\n * import OpenAI from \"openai\";\n * import createVorim from \"@vorim/sdk\";\n * import { VorimToolRegistry } from \"@vorim/sdk/integrations/openai\";\n *\n * const vorim = createVorim({ apiKey: \"agid_sk_live_...\" });\n * const openai = new OpenAI();\n *\n * const registry = new VorimToolRegistry({\n * vorim,\n * agentId: \"agid_acme_a1b2c3d4\",\n * });\n *\n * registry.add({\n * name: \"search_docs\",\n * description: \"Search internal documents\",\n * parameters: {\n * type: \"object\",\n * properties: { query: { type: \"string\" } },\n * required: [\"query\"],\n * },\n * execute: async ({ query }) => searchDocs(query),\n * permission: \"agent:read\",\n * });\n *\n * // Use with OpenAI chat completions\n * const response = await openai.chat.completions.create({\n * model: \"gpt-4o\",\n * messages,\n * tools: registry.toOpenAITools(),\n * });\n *\n * // Execute tool calls from the response\n * const toolMessages = await registry.executeToolCalls(\n * response.choices[0].message.tool_calls ?? []\n * );\n * ```\n */\nexport class VorimToolRegistry {\n private vorim: VorimSDK;\n private agentId: string;\n private defaultPermission: PermissionScope;\n private asyncAudit: boolean;\n private useRuntimeControl: boolean;\n private tools = new Map<string, VorimToolDefinition>();\n private replayInputs: ReplayInputs | undefined;\n private replayCache: Promise<ReplayContext> | null = null;\n\n constructor(config: VorimOpenAIConfig) {\n this.vorim = config.vorim;\n this.agentId = config.agentId;\n this.defaultPermission = config.defaultPermission ?? 'agent:execute';\n this.asyncAudit = config.asyncAudit ?? true;\n this.useRuntimeControl = config.useRuntimeControl ?? false;\n this.replayInputs = config.replay;\n }\n\n /**\n * Gate a tool call. When useRuntimeControl is on, run the full runtime\n * decision (beforeAction) and surface its decisionId for audit linkage;\n * otherwise use the lightweight permission check.\n */\n private async gate(\n scope: PermissionScope,\n actionTarget: string,\n payload?: Record<string, unknown>,\n ): Promise<{ allowed: boolean; reason?: string; decisionId?: string }> {\n if (this.useRuntimeControl) {\n const d = await this.vorim.beforeAction(\n { agentId: this.agentId, actionType: 'tool_call', actionTarget, requiredScope: scope, payload },\n { throwOnDeny: false },\n );\n return {\n allowed: d.decision === 'allow' || d.decision === 'modify' || d.decision === 'fallback',\n reason: d.reason,\n decisionId: d.decisionId || undefined,\n };\n }\n const { allowed, reason } = await this.vorim.check(this.agentId, scope);\n return { allowed, reason };\n }\n\n private async getReplayContext(): Promise<ReplayContext> {\n if (!this.replayInputs) return {};\n if (!this.replayCache) {\n // If caller didn't supply tools, derive them from the registry.\n const inputs: ReplayInputs = {\n ...this.replayInputs,\n tools: this.replayInputs.tools ?? this.deriveCatalogue(),\n };\n this.replayCache = prepareReplayContext(inputs);\n }\n return this.replayCache;\n }\n\n private deriveCatalogue(): CatalogueTool[] {\n return [...this.tools.values()].map(t => ({\n name: t.name,\n description: t.description,\n schema: t.parameters,\n }));\n }\n\n /** Register a tool. Invalidates the cached tool-catalogue hash. */\n add<TArgs, TResult>(definition: VorimToolDefinition<TArgs, TResult>): this {\n this.tools.set(definition.name, definition as VorimToolDefinition);\n this.replayCache = null;\n return this;\n }\n\n /** Register multiple tools. */\n addAll(definitions: VorimToolDefinition[]): this {\n for (const def of definitions) this.add(def);\n return this;\n }\n\n /** Convert registered tools to OpenAI's ChatCompletionTool format. */\n toOpenAITools(): ChatCompletionTool[] {\n return [...this.tools.values()].map(t => ({\n type: 'function' as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n ...(t.strict != null ? { strict: t.strict } : {}),\n },\n }));\n }\n\n /**\n * Execute tool calls from an OpenAI chat completion response.\n * Each call is checked against Vorim permissions and audited.\n * Returns an array of tool messages ready to append to the conversation.\n */\n async executeToolCalls(toolCalls: ToolCall[]): Promise<ToolMessage[]> {\n const results = await Promise.all(\n toolCalls.map(tc => this.executeSingleCall(tc)),\n );\n return results;\n }\n\n private async executeSingleCall(toolCall: ToolCall): Promise<ToolMessage> {\n const { id, function: fn } = toolCall;\n const definition = this.tools.get(fn.name);\n\n if (!definition) {\n return {\n role: 'tool',\n tool_call_id: id,\n content: JSON.stringify({ error: `Unknown tool: ${fn.name}` }),\n };\n }\n\n const scope = definition.permission ?? this.defaultPermission;\n let args: unknown;\n try {\n args = JSON.parse(fn.arguments);\n } catch {\n return {\n role: 'tool',\n tool_call_id: id,\n content: JSON.stringify({ error: `Invalid JSON arguments for ${fn.name}` }),\n };\n }\n\n // 1. Gate: runtime decision (with decisionId linkage) or permission check.\n const { allowed, reason, decisionId } = await this.gate(scope, fn.name, args as Record<string, unknown>);\n const replayCtx = await this.getReplayContext();\n\n if (!allowed) {\n const event: AuditEventInput = {\n agent_id: this.agentId,\n event_type: 'tool_call',\n action: fn.name,\n resource: truncate(fn.arguments, 500),\n permission: scope,\n result: 'denied',\n ...(decisionId ? { decision_id: decisionId } : {}),\n metadata: { reason, framework: 'openai' },\n ...replayCtx,\n };\n await this.emitAudit(event);\n return {\n role: 'tool',\n tool_call_id: id,\n content: JSON.stringify({ error: `Permission denied: ${scope}${reason ? ` — ${reason}` : ''}` }),\n };\n }\n\n // 2. Execute\n const start = Date.now();\n try {\n const result = await definition.execute(args as any);\n const content = typeof result === 'string' ? result : JSON.stringify(result);\n\n const event: AuditEventInput = {\n agent_id: this.agentId,\n event_type: 'tool_call',\n action: fn.name,\n resource: truncate(fn.arguments, 500),\n permission: scope,\n result: 'success',\n latency_ms: Date.now() - start,\n ...(decisionId ? { decision_id: decisionId } : {}),\n metadata: { framework: 'openai' },\n ...replayCtx,\n };\n await this.emitAudit(event);\n\n return { role: 'tool', tool_call_id: id, content };\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n\n const event: AuditEventInput = {\n agent_id: this.agentId,\n event_type: 'tool_call',\n action: fn.name,\n resource: truncate(fn.arguments, 500),\n permission: scope,\n result: 'error',\n latency_ms: Date.now() - start,\n error_code: err instanceof Error ? err.name : 'UNKNOWN',\n ...(decisionId ? { decision_id: decisionId } : {}),\n metadata: { error: errMsg, framework: 'openai' },\n ...replayCtx,\n };\n await this.emitAudit(event);\n\n return {\n role: 'tool',\n tool_call_id: id,\n content: JSON.stringify({ error: errMsg }),\n };\n }\n }\n\n private async emitAudit(event: AuditEventInput): Promise<void> {\n if (this.asyncAudit) {\n // Fire-and-forget: don't block the tool pipeline on the audit write.\n this.vorim.emit(event).catch(() => {});\n } else {\n // Synchronous mode: actually await so the audit is flushed before the\n // caller continues. (Previously both branches were identical and the\n // promise was never awaited — asyncAudit=false did nothing.)\n try { await this.vorim.emit(event); } catch { /* best-effort */ }\n }\n }\n}\n\n// ─── Agent Loop ───────────────────────────────────────────────────────────\n\nexport interface VorimAgentLoopConfig extends VorimOpenAIConfig {\n /** OpenAI client instance. */\n openai: OpenAIClient;\n /** Model to use. @default 'gpt-4o' */\n model?: string;\n /** System prompt for the agent. */\n systemPrompt?: string;\n /** Maximum iterations before stopping. @default 10 */\n maxIterations?: number;\n}\n\n/** Minimal OpenAI client interface (avoids importing the full SDK). */\ninterface OpenAIClient {\n chat: {\n completions: {\n create(params: any): Promise<any>;\n };\n };\n}\n\n/**\n * Runs a complete agent loop with OpenAI function calling, Vorim\n * permission enforcement, and audit logging.\n *\n * @example\n * ```ts\n * import OpenAI from \"openai\";\n * import createVorim from \"@vorim/sdk\";\n * import { runAgentLoop, VorimToolRegistry } from \"@vorim/sdk/integrations/openai\";\n *\n * const registry = new VorimToolRegistry({ vorim, agentId });\n * registry.add({ name: \"search\", ... });\n *\n * const response = await runAgentLoop({\n * vorim,\n * agentId,\n * openai: new OpenAI(),\n * model: \"gpt-4o\",\n * systemPrompt: \"You are a helpful assistant.\",\n * registry,\n * userMessage: \"Find docs about onboarding\",\n * });\n * ```\n */\nexport async function runAgentLoop(\n config: VorimAgentLoopConfig & {\n registry: VorimToolRegistry;\n userMessage: string;\n },\n): Promise<string> {\n const { openai, model = 'gpt-4o', systemPrompt, maxIterations = 10, registry, userMessage } = config;\n\n const messages: any[] = [];\n if (systemPrompt) messages.push({ role: 'system', content: systemPrompt });\n messages.push({ role: 'user', content: userMessage });\n\n const tools = registry.toOpenAITools();\n\n for (let i = 0; i < maxIterations; i++) {\n const response = await openai.chat.completions.create({\n model,\n messages,\n ...(tools.length > 0 ? { tools } : {}),\n });\n\n const choice = response.choices[0];\n messages.push(choice.message);\n\n // If the model is done (no tool calls), return the response\n if (choice.finish_reason === 'stop' || !choice.message.tool_calls?.length) {\n return choice.message.content ?? '';\n }\n\n // Execute tool calls and add results to messages\n const toolMessages = await registry.executeToolCalls(choice.message.tool_calls);\n messages.push(...toolMessages);\n }\n\n return messages[messages.length - 1]?.content ?? '';\n}\n\n// ─── Agent Registration Helper ───────────────────────────────────────────\n\n/**\n * Registers a new agent with Vorim and returns a ready-to-use tool registry.\n *\n * @example\n * ```ts\n * const { agentId, registry } = await createVorimOpenAIAgent({\n * vorim,\n * name: \"support-agent\",\n * capabilities: [\"search\", \"email\"],\n * scopes: [\"agent:read\", \"agent:execute\", \"agent:communicate\"],\n * tools: [searchTool, emailTool],\n * });\n * ```\n */\nexport async function createVorimOpenAIAgent(config: {\n vorim: VorimSDK;\n name: string;\n description?: string;\n capabilities: string[];\n scopes: PermissionScope[];\n tools: VorimToolDefinition[];\n}) {\n const { vorim, name, description, capabilities, scopes, tools } = config;\n\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 const registry = new VorimToolRegistry({ vorim, agentId });\n registry.addAll(tools);\n\n return {\n agentId,\n registration,\n registry,\n privateKey: registration.private_key,\n };\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────\n\nfunction truncate(str: string, max: number): string {\n return str.length > max ? str.slice(0, max) + '…' : str;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vorim/sdk",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "Official TypeScript SDK for Vorim AI — AI Agent Identity, Permissions & Audit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"files": [
|
|
72
72
|
"dist",
|
|
73
73
|
"README.md",
|
|
74
|
+
"CHANGELOG.md",
|
|
74
75
|
"LICENSE"
|
|
75
76
|
],
|
|
76
77
|
"scripts": {
|