{"version":3,"sources":["../src/realtime.ts"],"names":[],"mappings":"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","file":"realtime.cjs","sourcesContent":["import type { AxioRank } from \"./client\";\nimport type { Decision, ToolCallResult, ToolCallSignal } from \"./types\";\n\n/**\n * Govern an OpenAI Realtime (voice-agent) session with AxioRank.\n *\n * A realtime session streams JSON events over a WebSocket (or WebRTC data\n * channel). Two of those events are security-relevant, and this guard maps each\n * to the SAME policy/risk/audit engine that governs ordinary tool calls:\n *\n * - `response.function_call_arguments.done` — the model asked to run a function\n *   tool. This is governed BEFORE your app executes it (preventive): on a deny,\n *   the guard hands you the client events to send back so the model gets a\n *   blocked result instead of the action running.\n * - `response.output_audio_transcript.done` / `response.output_text.done` — what\n *   the model just said. This is inspected for leaked secrets/PII (detective:\n *   in a live voice session the audio has already streamed, so a hit is audited\n *   and alerted, not un-said).\n *\n * The whole session is correlated into one AxioRank run, so multi-step \"kill\n * chains\" across a conversation are still caught. Transport-agnostic: you own\n * the socket and simply feed each server event to {@link RealtimeGuard.onServerEvent}.\n *\n * @example\n * ```ts\n * const axio = new AxioRank({ apiKey: process.env.AXIORANK_KEY! });\n * const guard = createRealtimeGuard(axio, { sessionId });\n * ws.on(\"message\", async (raw) => {\n *   const event = JSON.parse(raw.toString());\n *   const verdict = await guard.onServerEvent(event);\n *   for (const ev of verdict.send) ws.send(JSON.stringify(ev)); // deny injection\n *   if (verdict.kind === \"function_call\" && verdict.decision === \"allow\") {\n *     // ...run the tool and return your own function_call_output...\n *   }\n * });\n * ```\n */\nexport interface RealtimeGuardOptions {\n  /** Group this voice session with its siblings on the Agent Runs page. */\n  sessionId?: string;\n  /** Join an existing AxioRank run instead of starting a fresh one. */\n  traceId?: string;\n  /** Metadata tags merged onto every governed call (e.g. your end-user id). */\n  metadata?: Record<string, string | number | boolean>;\n  /**\n   * Inspect the model's output transcript for secret/PII leakage. Detective\n   * only (the audio has already streamed), so a hit is audited/alerted.\n   * @default true\n   */\n  inspectOutput?: boolean;\n  /** Tool name recorded for output-DLP inspection. @default \"realtime.output\" */\n  outputTool?: string;\n  /** Called when output DLP flags a leak in what the agent said. Best-effort. */\n  onOutputLeak?: (info: {\n    reason: string;\n    risk: number;\n    signals?: ToolCallSignal[];\n  }) => void;\n}\n\n/** Which kind of event the guard just handled. */\nexport type RealtimeGuardKind = \"function_call\" | \"output\" | \"ignored\";\n\n/** The guard's decision about one Realtime server event. */\nexport interface RealtimeGuardResult {\n  kind: RealtimeGuardKind;\n  /** `allow` = proceed; `deny` = the call was blocked (or a leak was found). */\n  decision: Decision;\n  reason?: string;\n  /** Risk score AxioRank assigned, when a verdict was fetched. */\n  risk?: number;\n  /** The governed function call, when `kind === \"function_call\"`. */\n  call?: { callId: string; name: string; arguments: Record<string, unknown> };\n  /**\n   * Client events to send back over the Realtime socket. On a denied function\n   * call this is a `function_call_output` carrying the block plus a\n   * `response.create` so the model continues gracefully. Empty otherwise.\n   */\n  send: Array<Record<string, unknown>>;\n  /** The raw governance result, when a verdict was fetched. */\n  result?: ToolCallResult;\n}\n\nexport interface RealtimeGuard {\n  /** Feed each server event you receive from the Realtime socket. */\n  onServerEvent(event: unknown): Promise<RealtimeGuardResult>;\n  /** The AxioRank run id correlating this voice session. */\n  readonly traceId: string;\n}\n\n/** A fresh \"not a governed event\" result (fresh so its `send` array is never shared). */\nconst ignored = (): RealtimeGuardResult => ({\n  kind: \"ignored\",\n  decision: \"allow\",\n  send: [],\n});\n\n/** Parse the function-call arguments (a JSON string on the wire) into an object. */\nfunction parseArgs(raw: unknown): Record<string, unknown> {\n  if (raw && typeof raw === \"object\") return raw as Record<string, unknown>;\n  if (typeof raw !== \"string\" || raw.length === 0) return {};\n  try {\n    const v = JSON.parse(raw) as unknown;\n    return v && typeof v === \"object\" ? (v as Record<string, unknown>) : { value: v };\n  } catch {\n    return { _raw: raw };\n  }\n}\n\n/** The events to send back so a denied function call returns a block, not a run. */\nfunction denialEvents(callId: string, reason: string): Array<Record<string, unknown>> {\n  return [\n    {\n      type: \"conversation.item.create\",\n      item: {\n        type: \"function_call_output\",\n        call_id: callId,\n        output: JSON.stringify({ error: \"blocked_by_axiorank\", reason }),\n      },\n    },\n    { type: \"response.create\" },\n  ];\n}\n\n/**\n * Create a guard for one Realtime session. Pass an {@link AxioRank} client; the\n * guard opens a correlated run and governs each relevant server event you feed\n * it. Reuses your client's outage posture (`fail_open`/`fail_closed`), so a\n * gateway outage never silently halts a live call unless you configured it to.\n */\nexport function createRealtimeGuard(\n  client: AxioRank,\n  options: RealtimeGuardOptions = {},\n): RealtimeGuard {\n  const trace = client.trace({\n    id: options.traceId,\n    sessionId: options.sessionId,\n    metadata: { surface: \"realtime\", ...options.metadata },\n  });\n  const inspectOutput = options.inspectOutput ?? true;\n  const outputTool = options.outputTool ?? \"realtime.output\";\n  // The function name isn't always on the arguments.done event, so remember it\n  // from the function_call item when it's created, keyed by call_id.\n  const names = new Map<string, string>();\n\n  async function onServerEvent(event: unknown): Promise<RealtimeGuardResult> {\n    const ev = event as { type?: unknown };\n    const type = typeof ev?.type === \"string\" ? ev.type : \"\";\n\n    // Learn function-call names from the item as it's added to the conversation.\n    if (type === \"response.output_item.added\" || type === \"conversation.item.created\") {\n      const item = (event as { item?: { type?: string; call_id?: string; name?: string } })\n        .item;\n      if (item?.type === \"function_call\" && item.call_id && item.name) {\n        names.set(item.call_id, item.name);\n      }\n      return ignored();\n    }\n\n    // Preventive: govern a function/tool call before your app runs it.\n    if (type === \"response.function_call_arguments.done\") {\n      const e = event as { call_id?: string; name?: string; arguments?: unknown };\n      const callId = e.call_id ?? \"\";\n      const name = e.name ?? names.get(callId) ?? \"unknown\";\n      const args = parseArgs(e.arguments);\n      const call = { callId, name, arguments: args };\n      const result = await trace.toolCall({ tool: name, arguments: args });\n      if (result.decision === \"deny\") {\n        return {\n          kind: \"function_call\",\n          decision: \"deny\",\n          reason: result.reason,\n          risk: result.risk,\n          call,\n          result,\n          send: denialEvents(callId, result.reason),\n        };\n      }\n      return {\n        kind: \"function_call\",\n        decision: \"allow\",\n        reason: result.reason,\n        risk: result.risk,\n        call,\n        result,\n        send: [],\n      };\n    }\n\n    // Detective: inspect what the model SAID for secret/PII leakage.\n    if (\n      inspectOutput &&\n      (type === \"response.output_audio_transcript.done\" ||\n        type === \"response.output_text.done\")\n    ) {\n      const e = event as { transcript?: unknown; text?: unknown };\n      const text =\n        typeof e.transcript === \"string\"\n          ? e.transcript\n          : typeof e.text === \"string\"\n            ? e.text\n            : \"\";\n      if (!text) return ignored();\n      const result = await trace.inspectResult({ tool: outputTool, resultText: text });\n      if (result.decision === \"deny\") {\n        try {\n          options.onOutputLeak?.({\n            reason: result.reason,\n            risk: result.risk,\n            signals: result.signals,\n          });\n        } catch {\n          // A telemetry callback must never break the session.\n        }\n      }\n      return {\n        kind: \"output\",\n        decision: result.decision,\n        reason: result.reason,\n        risk: result.risk,\n        result,\n        send: [],\n      };\n    }\n\n    return ignored();\n  }\n\n  return { onServerEvent, traceId: trace.id };\n}\n"]}