{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://github.com/andrewhuot/headless-chat-sdk/schemas/config.schema.json",
  "title": "ChatClientConfig",
  "description": "Configuration accepted by createChatClient. Generated to match the TypeScript surface in packages/gecx-chat/src/core/createChatClient.ts.",
  "type": "object",
  "additionalProperties": true,
  "properties": {
    "deployment": { "type": "string", "description": "Deployment ID for capability negotiation and metadata." },
    "environment": { "type": "string", "enum": ["production", "staging", "development", "test"] },
    "preserveRawResponses": { "type": "boolean" },
    "storage": {
      "oneOf": [
        {
          "type": "object",
          "description": "Inline StorageAdapter implementation (functions). Validated only structurally."
        },
        {
          "type": "object",
          "required": ["mode"],
          "properties": {
            "mode": { "type": "string", "enum": ["session", "local", "memory"] },
            "consent": { "type": "string", "enum": ["functional", "none"] }
          },
          "additionalProperties": false
        }
      ]
    },
    "telemetry": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "onTrace": { "description": "Function: (event: TraceEvent) => void" },
        "onError": { "description": "Function: (error: ChatSdkError | Error) => void" },
        "onToolEvent": { "description": "Function: (event: ToolEvent) => void" }
      }
    },
    "analytics": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "enabled": { "type": "boolean" },
        "sink": {
          "description": "Function or function array: (event: ProductAnalyticsEvent) => void | Promise<void>"
        },
        "includeContent": {
          "type": "boolean",
          "description": "Opt in to chip labels/values, CSAT comments, handoff target names, and resolution reasons."
        },
        "context": { "type": "object" },
        "maxEvents": { "type": "integer", "minimum": 1 },
        "dimensions": {
          "description": "Function: () => Record<string, unknown> — dynamic dimensions resolved per event."
        },
        "sampleRate": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "description": "Drop 1 - sampleRate of events deterministically per (sessionId, type, turnIndex ?? eventId). Critical events (CSAT, resolution, session lifecycle, errors) are never dropped."
        },
        "respectConsent": {
          "type": "boolean",
          "description": "Suppress emission when ChatGovernance consent posture does not permit analytics. Default true."
        }
      }
    },
    "retry": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "maxAttempts": { "type": "integer", "minimum": 1, "maximum": 10 },
        "backoffMs": { "type": "integer", "minimum": 0 }
      }
    },
    "recovery": {
      "type": "object",
      "additionalProperties": false,
      "description": "Recovery policy for mid-turn transport disconnects. See docs/internal/transport-and-recovery-contract.md.",
      "properties": {
        "maxAttempts": { "type": "integer", "minimum": 1, "maximum": 20 },
        "initialBackoffMs": { "type": "integer", "minimum": 0 },
        "maxBackoffMs": { "type": "integer", "minimum": 0 },
        "jitter": { "type": "string", "enum": ["none", "equal", "full"] },
        "resumeMode": { "type": "string", "enum": ["replay", "resume", "none"] }
      }
    },
    "network": {
      "type": "string",
      "enum": ["auto", "strict", "off"],
      "description": "Outbox / offline behavior. 'auto' queues offline sends; 'strict' errors on offline; 'off' disables the monitor."
    },
    "multiTab": {
      "type": "string",
      "enum": ["leader", "broadcast", "off"],
      "description": "Multi-tab coordination mode. 'leader' is the default in browsers with storage configured."
    },
    "capabilities": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "streaming": { "type": "boolean" },
        "richContent": { "type": "boolean" },
        "fileUpload": { "type": "boolean" },
        "handoff": { "type": "boolean" },
        "clientTools": { "type": "boolean" },
        "citations": { "type": "boolean" },
        "suggestionChips": { "type": "boolean" },
        "markdown": { "type": "boolean" },
        "parts": {
          "type": "object",
          "description": "Per-family rich-content capability map. See schemas/rich-content.schema.json#PartCapabilities.",
          "additionalProperties": {
            "type": "object",
            "required": ["versions", "fallback"],
            "properties": {
              "versions": { "type": "array", "items": { "type": "integer", "minimum": 1 } },
              "fallback": { "type": "string", "enum": ["text", "hide"] },
              "source": { "type": "string", "enum": ["core", "standard", "custom"] }
            }
          }
        }
      }
    },
    "governance": { "$ref": "governance.schema.json#/definitions/DataGovernancePolicy" },
    "richContent": {
      "description": "Pre-built RichContentRegistry instance OR an array of RichContentDefinition entries to register on top of the defaults.",
      "oneOf": [
        { "type": "object" },
        {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "family": { "type": "string" },
              "type": { "type": "string" },
              "versions": { "type": "array", "items": { "type": "integer", "minimum": 1 } },
              "fallback": { "type": "string", "enum": ["text", "hide"] }
            }
          }
        }
      ]
    },
    "signals": {
      "type": "object",
      "additionalProperties": false,
      "description": "Real-time sentiment + intent signal subsystem. See docs/guides/sentiment-and-intent.md.",
      "properties": {
        "adapters": {
          "type": "array",
          "description": "SignalAdapter instances produced by ruleAdapter, modelToolAdapter, geminiAdapter, claudeAdapter, openaiAdapter, or tfjsToxicityAdapter.",
          "items": {
            "type": "object",
            "required": ["id", "trigger", "source"],
            "properties": {
              "id": { "type": "string" },
              "trigger": { "type": "string", "enum": ["user-message-added", "assistant-message-completed", "turn-completed"] },
              "source": { "type": "string", "enum": ["model", "local", "server", "rule"] },
              "budgetMs": { "type": "integer", "minimum": 0 }
            }
          }
        },
        "escalation": {
          "type": "array",
          "description": "SignalEscalationRule list. When a rule's condition holds for `consecutiveTurns` turns above `minConfidence`, the SDK calls ChatSession.requestTransfer() with an idempotency key.",
          "items": {
            "type": "object",
            "required": ["id", "signal", "operator", "threshold"],
            "properties": {
              "id": { "type": "string" },
              "signal": { "type": "string", "enum": ["sentiment", "intent"] },
              "match": {
                "type": "object",
                "properties": {
                  "category": { "type": "string", "enum": ["frustration", "anger", "confusion", "urgency", "satisfaction", "delight", "neutral"] },
                  "intent": { "type": "string" }
                }
              },
              "operator": { "type": "string", "enum": [">", ">=", "<", "<=", "=="] },
              "threshold": { "type": "number", "minimum": 0, "maximum": 1 },
              "minConfidence": { "type": "number", "minimum": 0, "maximum": 1 },
              "consecutiveTurns": { "type": "integer", "minimum": 1 },
              "cooldownMs": { "type": "integer", "minimum": 0 }
            }
          }
        },
        "historyLimit": { "type": "integer", "minimum": 1, "description": "Max recent-message history handed to each adapter. Default 10." },
        "defaultBudgetMs": { "type": "integer", "minimum": 0, "description": "Default per-adapter latency budget in ms. Default 1500." }
      }
    },
    "voice": {
      "description": "Voice configuration. 'auto' picks a provider by environment (mock/web-audio-mock/Gemini Live). A VoiceConfig object enables explicit wiring. Omitted means voice is off. Configuring voice does NOT request microphone permission — only the user pressing the mic does.",
      "oneOf": [
        { "type": "string", "enum": ["auto"] },
        {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "provider": {
              "description": "Provider id or a custom VoiceProvider instance. String ids are bundled.",
              "oneOf": [
                { "type": "string", "enum": ["gemini-live", "mock", "web-audio-mock"] },
                { "type": "object" }
              ]
            },
            "tokenEndpoint": {
              "type": "string",
              "description": "URL the bundled Gemini Live provider POSTs to mint ephemeral tokens. Defaults to '/chat/voice-token'. Required in production when no explicit provider instance is supplied."
            },
            "model": { "type": "string", "description": "Gemini model id. Default: 'gemini-2.0-flash-live-001'." },
            "voice": { "type": "string", "description": "Gemini voice name. Default: 'Aoede'." },
            "mode": { "type": "string", "enum": ["push-to-talk", "open-mic"], "description": "Default voice mode. Default: 'push-to-talk'." },
            "bargeInTimeoutMs": { "type": "integer", "minimum": 0 },
            "systemInstruction": { "type": "string" },
            "vad": { "description": "Optional client-side VoiceActivityDetector instance. Validated only structurally." }
          }
        }
      ]
    }
  }
}
