{
  "version": 8,
  "name": "duplex-voice-agent",
  "runtime": "dsh-multi-model-provider/realtimeModelRuntime",
  "route": {
    "required": [
      "id",
      "model",
      "provider",
      "protocol",
      "adapter"
    ],
    "protocols": [
      "openai-webrtc",
      "doubao-realtime-duplex"
    ],
    "trustedOrigins": {
      "openai-webrtc": [
        "https://api.openai.com"
      ],
      "doubao-realtime-duplex": [
        "wss://openspeech.bytedance.com"
      ]
    },
    "browserAuthorization": {
      "httpMarkers": [
        "x-dsh-realtime-voice",
        "x-dsh-model-probe"
      ],
      "websocketSubprotocol": "dsh-realtime-voice-v1",
      "compatibilityAliases": {
        "basePath": "/dsh-voice-agent",
        "httpMarker": "x-dsh-voice-agent",
        "websocketSubprotocol": "dsh-voice-agent-v1"
      },
      "sameOrigin": true
    }
  },
  "clientService": {
    "name": "voiceAgent",
    "methods": [
      "capabilities",
      "startConversation",
      "registerActions"
    ],
    "startConversationOptions": {
      "initialUserText": "optional bounded complete transcript captured by a wake recognizer; emitted as a finalized input transcript after Provider readiness",
      "initialAudio": "optional bounded in-memory mono PCM16 capture; resampled to the selected Provider input rate and replayed as the first real user turn"
    },
    "recognitionCapture": {
      "option": "captureAudio",
      "handleMethods": ["takeAudio", "discardAudio"],
      "maxSeconds": 30,
      "persistence": false
    },
    "compatibilityMethods": [
      "models",
      "open",
      "registerTools",
      "recognize",
      "readAloud"
    ],
    "capabilities": [
      "secureContext",
      "realtime",
      "recognition",
      "audioInput",
      "readAloud",
      "voices"
    ],
    "audioInput": {
      "ownership": "exclusive-lease",
      "ownerOption": "ownerId",
      "conflictCode": "audio_input_busy",
      "standbyPreemption": "recognize-preemptible",
      "release": "conversation-end-or-startup-failure",
      "capture": "same-origin AudioWorkletProcessor batches Float32 microphone frames; the Client downsamples them to 16 kHz PCM for Doubao",
      "workletPath": "/dsh-realtime-voice/audio-input-worklet.js"
    },
    "standbyRecognition": {
      "providerSessionBeforeWake": false,
      "implementation": "browser SpeechRecognition; wake phrase matching remains in the product layer",
      "billingBoundary": "startConversation is called only by the product after a matching transcript",
      "privacyNote": "browser recognition is not claimed to be on-device unless the user agent explicitly guarantees it"
    },
    "standaloneBrowserClient": {
      "path": "/dsh-realtime-voice/client.js",
      "global": "DSHRealtimeVoice",
      "gateway": "a bounded same-origin absolute path may retain product-owned authorization, versioned session setup, and readiness event"
    },
    "actionRegistry": {
      "registration": "registerActions(ownerPrefix, actions)",
      "ownerMatch": "conversation ownerId startsWith ownerPrefix",
      "resolution": "registered name → execute(args, control) → resolveAction; unknown name under a matched owner → error result; unmatched owner → consumer resolves",
      "asyncResults": "execute may return a Promise; speech continues while the result is pending (dual output); executors that never settle are resolved with a timeout error (action.timeoutMs per action, default 300000ms)",
      "control": "execute may call control.resolve(result, options) to settle before follow-up work",
      "events": "action-result emitted after every automatic resolution"
    },
    "conversation": [
      "id",
      "subscribe",
      "updateContext",
      "resolveAction",
      "interrupt",
      "end"
    ],
    "compatibilityConversationMethods": ["resolveTool", "close"],
    "events": {
      "status": [
        "connected",
        "status"
      ],
      "phase": [
        "phase"
      ],
      "transcript": [
        "role",
        "text",
        "final"
      ],
      "action": [
        "callId",
        "name",
        "arguments"
      ],
      "action-result": [
        "callId",
        "name",
        "ok",
        "output",
        "error"
      ],
      "audio-level": [
        "source",
        "level"
      ],
      "error": [
        "code",
        "message",
        "recoverable"
      ],
      "closed": []
    }
  },
  "owns": [
    "provider-wire-protocol",
    "browser-transport",
    "browser-media-lifecycle",
    "audio-input-arbitration",
    "provider-event-normalization",
    "action-execution-loop",
    "dual-output-scheduling"
  ],
  "doesNotOwn": [
    "model-catalog",
    "credential-resolution",
    "role-profile",
    "drafts",
    "agent-submission",
    "knowledge",
    "delegation-policy"
  ]
}
