type Role = "user" | "assistant" | "tool" | "system";

/** "trace" rows are intermediate agent breadcrumbs (tool-call hints,
 * progress pings) that should not be rendered as conversational replies. */
type MessageKind = "message" | "trace";

export type UITurnPhase = "user" | "reasoning" | "activity" | "answer" | "complete";
export type MessageDeliveryStatus = "sending" | "accepted" | "failed";
export type MessageDeliveryErrorKind =
  | "message_too_big"
  | "workspace_scope_rejected"
  | "turn_rejected";

/** One image attached to a UIMessage.
 *
 * ``url`` can arrive in three different shapes, which the bubble renders
 * identically:
 * - A ``data:image/...;base64,...`` URL generated by the Composer for the
 *   optimistic preview of an in-flight user turn. Self-contained, no
 *   lifecycle.
 * - A signed ``/api/media/...`` URL attached to a historical user turn by
 *   the backend on session replay. Safe to drop into an ``<img src>``.
 * - Absent. The backend couldn't resolve a stored path (file moved,
 *   deleted, or pre-media-persistence session). The bubble shows a
 *   placeholder tile with ``name`` as the label.
 */
export interface UIImage {
  url?: string;
  name?: string;
}

export type UIMediaKind = "image" | "video" | "file";

export interface UIMediaAttachment {
  kind: UIMediaKind;
  url?: string;
  name?: string;
}

interface UIMessageSource { kind: "cron" | "local_trigger" | "trigger" | string; label?: string; }

export interface TurnUsage {
  prompt_tokens?: number;
  completion_tokens?: number;
  cached_tokens?: number;
  context_tokens?: number;
  request_count?: number;
  estimated_tokens?: number;
  [key: string]: number | undefined;
}

export type RecoveryStatus = "resuming" | "awaiting_user" | "recovered" | "failed";

export interface RecoveryState {
  status: RecoveryStatus;
  recovery_id: string;
  reason?: string;
  attempts?: number;
  can_continue?: boolean;
}

export interface UIMessage {
  id: string;
  role: Role;
  content: string;
  kind?: MessageKind;
  isStreaming?: boolean;
  createdAt: number;
  /** For trace rows: each individual hint line, so consecutive hints can
   * render as a single collapsible group. */
  traces?: string[];
  /** Structured tool events behind trace rows. Kept so activity cards can
   * distinguish running, completed, and failed tool phases. */
  toolEvents?: ToolProgressEvent[];
  /** Activity rows: explicit file edits emitted by edit tools. */
  fileEdits?: UIFileEdit[];
  /** Activity rows created during the same agent phase share one collapsible block. */
  activitySegmentId?: string;
  /** Internal projection marker for assistant text emitted before a later tool.
   * It is not a wire message and is rendered as a compact activity row. */
  activityKind?: "model";
  /** User turn: optimistic blob URLs for preview. Replay: placeholder chips. */
  images?: UIImage[];
  /** Signed or local UI-renderable media attachments. */
  media?: UIMediaAttachment[];
  /** App-specific CLI adapters explicitly attached to this user turn. */
  cliApps?: UICliAppAttachment[];
  /** Settings-managed MCP presets explicitly attached to this user turn. */
  mcpPresets?: UIMcpPresetAttachment[];
  /** Persisted sessions explicitly referenced by this user turn. */
  sessionMentions?: SessionMention[];
  /** Assistant turn: accumulated model reasoning / thinking text. Built up
   * incrementally from ``reasoning_delta`` frames; finalized when
   * ``reasoning_end`` arrives. */
  reasoning?: string;
  /** True while ``reasoning_delta`` frames are still arriving for this turn.
   * Drives the shimmer header on ``ReasoningBubble``. */
  reasoningStreaming?: boolean;
  /** End-to-end wall time for this assistant turn (persisted ``latency_ms`` / ``turn_end``). */
  latencyMs?: number;
  /** Client epoch milliseconds when the definitive ``turn_end`` was received. */
  completedAt?: number;
  /** Additive model usage for this turn; context_tokens is the final request only. */
  usage?: TurnUsage;
  /** Configured context-window capacity for the model used by this turn. */
  contextWindowTokens?: number;
  /** Lightweight provenance for proactive assistant messages. */
  source?: UIMessageSource;
  /** Structured provenance for a message delivered by another session. */
  sessionMessage?: UISessionMessage;
  /** Stable protocol metadata for grouping all activity emitted by one user turn. */
  turnId?: string;
  turnPhase?: UITurnPhase;
  turnSeq?: number;
  /** Ephemeral delivery lifecycle for optimistic user messages. */
  deliveryStatus?: MessageDeliveryStatus;
  /** Structured rejection reason shown with a failed optimistic message. */
  deliveryErrorKind?: MessageDeliveryErrorKind;
}

export interface UICliAppAttachment {
  name: string;
  display_name?: string;
  category?: string;
  entry_point?: string;
  logo_url?: string | null;
  brand_color?: string | null;
}

export interface UIMcpPresetAttachment {
  name: string;
  display_name?: string;
  category?: string;
  transport?: string;
  status?: string;
  configured?: boolean;
  logo_url?: string | null;
  brand_color?: string | null;
}

export interface SessionMention {
  /** Stable public identity. Older transcript rows may not include it. */
  id?: string;
  /** Text token inserted in the composer, without the leading @. */
  name: string;
  /** Stable persisted-session identifier used by read_session. */
  session_key: string;
  title: string;
}

/** Stable public handle returned by the session-list endpoint. */
export interface SessionHandle {
  id: string;
  name: string;
}

interface UISessionMessage {
  message_id: string;
  session: SessionHandle;
}

export interface SessionAutomationJob {
  id: string;
  name: string;
  enabled: boolean;
  protected?: boolean;
  delete_after_run?: boolean;
  created_at_ms?: number | null;
  updated_at_ms?: number | null;
  kind?: "local_trigger" | "cron" | string;
  schedule: {
    kind: "at" | "every" | "cron" | "local" | string;
    at_ms?: number | null;
    every_ms?: number | null;
    expr?: string | null;
    tz?: string | null;
  };
  payload: {
    message: string;
    kind?: "agent_turn" | "system_event" | "local_trigger" | string;
    command?: string;
  };
  state: {
    next_run_at_ms?: number | null;
    last_run_at_ms?: number | null;
    last_status?: "ok" | "error" | "skipped" | string | null;
    last_error?: string | null;
    pending?: boolean;
    run_history?: Array<{
      run_at_ms: number;
      status: "ok" | "error" | "skipped" | string;
      duration_ms?: number;
      error?: string | null;
    }>;
  };
  origin?: {
    session_key?: string;
    channel: string;
    chat_id?: string;
    title?: string;
    preview?: string;
  } | null;
  trigger?: {
    id: string;
    command: string;
  };
}

export interface SessionAutomationsPayload { jobs: SessionAutomationJob[]; }
export interface AutomationsPayload { jobs: SessionAutomationJob[]; }
export interface AutomationUpdatePayload {
  name?: string;
  message?: string;
  schedule?: {
    kind: "at" | "every" | "cron";
    at_ms?: number;
    every_ms?: number;
    expr?: string;
    tz?: string;
  };
}

export interface SessionDeleteResult {
  deleted: boolean;
  blocked_by_automations?: boolean;
  automations?: SessionAutomationJob[];
}

export interface SkillSummary {
  name: string;
  description: string;
  source: "workspace" | "builtin" | string;
  enabled?: boolean;
  deletable?: boolean;
  available: boolean;
  unavailable_reason?: string;
}

interface SkillRequirements {
  bins: string[];
  env: string[];
  missing_bins: string[];
  missing_env: string[];
}

interface SkillInstallOption {
  id: string;
  kind: string;
  label: string;
  command: string;
}

export interface SkillDetail extends SkillSummary {
  requirements: SkillRequirements;
  install_options?: SkillInstallOption[];
  raw_markdown: string;
}

export interface SkillsPayload { skills: SkillSummary[]; }

export interface SkillActionPayload extends SkillsPayload {
  last_action: {
    name: string;
    enabled: boolean;
    deleted: boolean;
  };
}

export interface MarketplaceSkillSummary {
  id: string;
  skill_id: string;
  name: string;
  source: string;
  provider: Exclude<MarketplaceProvider, "all">;
  installs: number;
  downloads?: number;
  url: string;
  installed: boolean;
  install_supported: boolean;
  metric: "installs_24h" | "installs_total";
  version?: string;
  verified?: boolean;
  requires_api_key?: boolean;
  rank?: number;
}

export type MarketplaceProvider = "all" | "skills_sh" | "skillhub";

export interface SkillsSearchPayload {
  query: string;
  skills: MarketplaceSkillSummary[];
  provider: MarketplaceProvider;
  install_supported: boolean;
}

export interface SkillsTrendingPayload {
  skills: MarketplaceSkillSummary[];
  period: "24h" | "trending" | "mixed";
  provider: MarketplaceProvider;
  install_supported: boolean;
}

export interface SkillsTrendsPayload {
  trends: Record<string, number[]>;
}

export interface SkillInstallPayload extends SkillsPayload {
  last_action: {
    installed: boolean;
    already_installed: boolean;
    name: string;
  };
}

/** Structured UI blob on ``progress`` WS frames; channels may add more ``kind`` values later. */
interface AgentUIBlob {
  kind: string;
  data?: unknown;
}

/** WebSocket snapshot for sustained goals (`goal_state` events; keyed by ``chat_id``). */
export interface GoalStateWsPayload {
  active: boolean;
  status?: "active" | "blocked";
  ui_summary?: string;
  objective?: string;
  recap?: string;
}

export interface ToolProgressEvent {
  version?: number;
  phase?: "start" | "end" | "error" | string;
  call_id?: string;
  name?: string;
  arguments?: unknown;
  result?: unknown;
  error?: unknown;
  files?: unknown[];
  embeds?: unknown[];
}

export interface UIFileDiff {
  format: "unified" | string;
  context?: number;
  truncated?: boolean;
  text?: string;
}

export interface UIFileEdit {
  version?: number;
  call_id: string;
  tool: string;
  path: string;
  absolute_path?: string;
  phase?: "start" | "end" | "error" | string;
  added: number;
  deleted: number;
  approximate?: boolean;
  status: "editing" | "done" | "error";
  operation?: "edit" | "delete" | string;
  binary?: boolean;
  error?: string;
  pending?: boolean;
  diff?: UIFileDiff;
}

export interface ChatSummary {
  /** Server-side session key, e.g. ``websocket:abcd-...``. */
  key: string;
  /** Local channel + chat_id parts derived from ``key`` for convenience. */
  channel: string;
  chatId: string;
  createdAt: string | null;
  updatedAt: string | null;
  title?: string;
  preview: string;
  /** Model preset persisted for this session; null means it still follows the global default. */
  modelPreset?: string | null;
  /** Unix epoch seconds when this session currently has a turn in flight. */
  runStartedAt?: number | null;
  /** Durable recovery state that needs attention after an interrupted turn. */
  recoveryState?: RecoveryState | null;
  workspaceScope?: WorkspaceScopePayload | null;
  /** Stable, server-owned @handle for this session. */
  handle?: SessionHandle | null;
}

export type WorkspaceAccessMode = "restricted" | "full";
export type WebuiDefaultAccessMode = "default" | "full";

export interface WorkspaceScopePayload {
  project_path: string;
  project_name?: string;
  access_mode: WorkspaceAccessMode;
  restrict_to_workspace?: boolean;
  sandbox_status?: {
    restrict_to_workspace: boolean;
    workspace_root: string;
    level: string;
    enforced: boolean;
    provider: string;
    provider_label: string;
    summary: string;
  };
}

export interface WorkspacesPayload {
  schema_version: number;
  default_access_mode: WebuiDefaultAccessMode;
  default_scope: WorkspaceScopePayload;
  controls: {
    can_change_project: boolean;
    can_use_full_access: boolean;
    can_pick_folder?: boolean;
  };
}

export type SidebarDensity = "comfortable" | "compact";
export type SidebarSortMode = "updated_desc" | "created_desc" | "title_asc" | "manual";
export type WorkbenchLayout = "columns" | "rows" | "grid" | "bsp" | "main-stack";

export interface WorkbenchTabState {
  explicit: boolean;
  title: string | null;
  paneKeys: string[];
  layoutPaneKeys: string[];
  layout: WorkbenchLayout;
  splitRatios: number[];
}

export interface WorkbenchState {
  version: 1;
  tabs: Record<string, WorkbenchTabState>;
}

export interface SidebarViewState {
  density: SidebarDensity;
  show_previews: boolean;
  show_timestamps: boolean;
  show_archived: boolean;
  sort: SidebarSortMode;
}

export interface SidebarStatePayload {
  schema_version: number;
  pinned_keys: string[];
  archived_keys: string[];
  session_order: string[];
  title_overrides: Record<string, string>;
  project_name_overrides: Record<string, string>;
  tags_by_key: Record<string, string[]>;
  collapsed_groups: Record<string, boolean>;
  workbench: WorkbenchState;
  view: SidebarViewState;
  updated_at?: string | null;
}

export interface BootstrapResponse {
  token?: string;
  api_token?: string;
  ws_path: string;
  ws_url?: string | null;
  expires_in?: number;
  limits?: WebUIIngressLimits;
  model_name?: string | null;
  runtime_surface?: RuntimeSurface;
  runtime_capabilities?: RuntimeCapabilities;
}

interface WebUITransportLimits {
  max_frame_bytes: number;
  envelope_reserve_bytes: number;
}

interface WebUIMessageLimits {
  max_text_bytes: number;
}

interface WebUIAttachmentLimits {
  max_count: number;
  max_file_bytes: number;
  max_total_bytes: number;
}

export interface WebUIIngressLimits {
  transport: WebUITransportLimits;
  message: WebUIMessageLimits;
  attachments: WebUIAttachmentLimits;
}

export type RuntimeSurface = "browser" | "native";
type RestartBehavior = "none" | "nextTurn" | "engineRestart" | "appRestart";
type SettingsApplyStatus =
  | "idle"
  | "pending"
  | "applying"
  | "restarting_engine"
  | "requires_app_restart";

export interface RuntimeCapabilities {
  can_restart_engine: boolean;
  can_pick_folder: boolean;
  can_open_logs: boolean;
  can_export_diagnostics: boolean;
}

interface ProviderModelInfo {
  id: string;
  label?: string | null;
  description?: string | null;
  owned_by?: string | null;
  context_window?: number | null;
  reasoning_efforts?: string[];
  supports_backend_search?: boolean;
}

export interface ProviderModelsPayload {
  provider: string;
  label: string;
  status:
    | "available"
    | "unsupported"
    | "not_configured"
    | "missing_api_base"
    | "error";
  catalog_kind:
    | "builtin"
    | "hybrid"
    | "official"
    | "catalog"
    | "local"
    | "custom"
    | "unsupported";
  source?: "remote" | "cache" | "stale" | "fallback";
  models: ProviderModelInfo[];
  model_count: number;
  message?: string | null;
  fetched_at?: number;
}

export interface ProviderOAuthAuthorizationRequired {
  status: "authorization_required";
  provider: string;
  flow_id: string;
  authorization_url: string;
  expires_in: number;
  completion_input?: "authorization_code" | "callback_url";
}

export interface ProviderOAuthPending {
  status: "pending";
  provider: string;
  flow_id: string;
}

export type ProviderOAuthLoginResult = SettingsPayload | ProviderOAuthAuthorizationRequired;
export type ProviderOAuthCompletionResult = SettingsPayload | ProviderOAuthPending;

export interface SettingsPayload {
  surface?: RuntimeSurface;
  runtime_surface?: RuntimeSurface;
  runtime_capabilities?: RuntimeCapabilities;
  apply_state?: {
    status: SettingsApplyStatus;
    sections: string[];
  };
  restart_behavior_by_section?: Record<string, RestartBehavior>;
  agent: {
    model: string;
    provider: string;
    resolved_provider: string | null;
    has_api_key: boolean;
    model_preset: string | null;
    max_tokens: number;
    context_window_tokens: number;
    temperature: number;
    reasoning_effort: string | null;
    timezone: string;
    tool_hint_max_length: number;
  };
  model_presets: Array<{
    name: string;
    /** @deprecated Compatibility alias. New clients must use `name`. */
    label?: string;
    active: boolean;
    is_default: boolean;
    model: string;
    provider: string;
    resolved_provider?: string | null;
    max_tokens: number;
    context_window_tokens: number;
    temperature: number;
    reasoning_effort: string | null;
    reasoning_effort_values?: string[];
  }>;
  model_call_order: string[];
  model_call_order_editable: boolean;
  /** Whether an actual legacy model configuration is available to convert. */
  model_configuration_migratable?: boolean;
  created_model_preset?: string;
  created_provider?: string;
  providers: Array<{
    name: string;
    label: string;
    is_custom?: boolean;
    configured: boolean;
    auth_type?: "api_key" | "oauth";
    api_key_required?: boolean;
    api_key_hint?: string | null;
    api_base?: string | null;
    default_api_base?: string | null;
    model_selectable?: boolean;
    model_catalog?: ProviderModelsPayload["catalog_kind"];
    api_type?: "auto" | "chat_completions" | "responses";
    oauth_account?: string | null;
    oauth_expires_at?: number | null;
    oauth_login_supported?: boolean;
    proxy?: string | null;
    advanced_fields?: Array<
      | "api_type"
      | "extra_headers"
      | "extra_body"
      | "extra_query"
      | "proxy"
      | "thinking_style"
      | "region"
      | "profile"
    >;
    extra_headers?: Record<string, string> | null;
    extra_body?: Record<string, unknown> | null;
    extra_query?: Record<string, string> | null;
    thinking_style?: string | null;
    region?: string | null;
    profile?: string | null;
  }>;
  web_search: {
    provider: string;
    api_key_hint?: string | null;
    base_url?: string | null;
    max_results: number;
    timeout: number;
    providers: Array<{
      name: string;
      label: string;
      credential: "none" | "api_key" | "optional_api_key" | "base_url";
    }>;
  };
  web: {
    enable: boolean;
    proxy?: string | null;
    user_agent?: string | null;
    search: {
      max_results: number;
      timeout: number;
    };
    fetch: {
      use_jina_reader: boolean;
    };
  };
  api?: {
    host: string;
    port: number;
    timeout: number;
    api_key_hint?: string | null;
  };
  observability?: {
    provider: "langfuse" | string;
    configured: boolean;
    base_url: string;
  };
  image_generation: {
    enabled: boolean;
    provider: string;
    provider_configured: boolean;
    model: string;
    default_aspect_ratio: string;
    default_image_size: string;
    max_images_per_turn: number;
    save_dir: string;
    providers: Array<{
      name: string;
      label: string;
      configured: boolean;
      auth_type?: "api_key" | "oauth";
      api_key_hint?: string | null;
      api_base?: string | null;
      default_api_base?: string | null;
      models?: string[];
      default_model?: string | null;
    }>;
  };
  transcription?: {
    enabled: boolean;
    provider: string;
    provider_configured: boolean;
    model: string;
    language: string | null;
    max_duration_sec: number;
    max_upload_mb: number;
    providers: Array<{
      name: string;
      label: string;
      configured: boolean;
      api_key_hint?: string | null;
      api_base?: string | null;
      default_api_base?: string | null;
    }>;
  };
  runtime: {
    config_path: string;
    workspace_path: string;
    gateway_host: string;
    gateway_port: number;
    heartbeat: {
      enabled: boolean;
      interval_s: number;
      keep_recent_messages: number;
    };
    dream: {
      schedule: string;
    };
    unified_session: boolean;
  };
  usage?: {
    days: Array<{
      date: string;
      input_tokens: number;
      output_tokens: number;
      cache_read_tokens: number;
      cache_write_tokens: number;
      cache_read_observed_input_tokens: number;
      cache_write_observed_input_tokens: number;
      total_tokens: number;
      reported_tokens?: number;
      estimated_tokens?: number;
      requests: number;
      reported_requests?: number;
      estimated_requests?: number;
      successful_requests?: number;
      failed_requests?: number;
      generation_ms?: number;
      measured_output_tokens?: number;
      ttft_ms?: number;
      timed_requests?: number;
      duration_ms?: number;
      sources?: Record<
        "user" | "api" | "cron" | "dream" | "system" | string,
        {
          input_tokens: number;
          output_tokens: number;
          cache_read_tokens: number;
          cache_write_tokens: number;
          cache_read_observed_input_tokens: number;
          cache_write_observed_input_tokens: number;
          total_tokens: number;
          reported_tokens?: number;
          estimated_tokens?: number;
          requests: number;
          reported_requests?: number;
          estimated_requests?: number;
          successful_requests?: number;
          failed_requests?: number;
          generation_ms?: number;
          measured_output_tokens?: number;
          ttft_ms?: number;
          timed_requests?: number;
          duration_ms?: number;
        }
      >;
    }>;
    total_tokens: number;
    total_tokens_30d: number;
    total_tokens_365d: number;
    peak_day_tokens: number;
    current_streak_days: number;
    longest_streak_days: number;
    active_days_30d: number;
    requests_30d: number;
    failed_requests_30d?: number;
    reported_tokens_30d?: number;
    estimated_tokens_30d?: number;
    cache_read_tokens_30d?: number;
    cache_read_observed_input_tokens_30d?: number;
    cache_read_rate_30d?: number | null;
    providers_30d?: Array<{
      provider: string;
      model: string;
      input_tokens: number;
      output_tokens: number;
      cache_read_tokens: number;
      cache_write_tokens: number;
      cache_read_observed_input_tokens: number;
      cache_write_observed_input_tokens: number;
      total_tokens: number;
      reported_tokens: number;
      estimated_tokens: number;
      requests: number;
      successful_requests: number;
      failed_requests: number;
      reported_requests: number;
      estimated_requests: number;
      generation_ms: number;
      measured_output_tokens: number;
      ttft_ms: number;
      timed_requests: number;
      duration_ms: number;
    }>;
    updated_at?: string | null;
  };
  advanced: {
    restrict_to_workspace: boolean;
    workspace_sandbox?: {
      restrict_to_workspace: boolean;
      workspace_root: string;
      level: "off" | "application" | "system" | string;
      enforced: boolean;
      provider: string;
      provider_label: string;
      summary: string;
    };
    ssrf_whitelist_count: number;
    webui_allow_local_service_access: boolean;
    allow_local_preview_access?: boolean;
    webui_default_access_mode: WebuiDefaultAccessMode;
    private_service_protection_enabled: boolean;
    mcp_server_count: number;
    exec_enabled: boolean;
    exec_sandbox?: string | null;
    exec_path_prepend_set: boolean;
    exec_path_append_set: boolean;
  };
  requires_restart: boolean;
  restart_required_sections?: Array<"runtime" | "browser" | "image">;
  version?: {
    current: string;
  };
  docs?: {
    version: string;
    base_url: string;
    chat_apps_url: string;
    latest_url?: string;
  };
}

export interface ApiServicePayload {
  installed: boolean;
  running: boolean;
  managed: boolean;
  host: string;
  port: number;
  timeout: number;
  api_key_hint?: string | null;
  endpoint: string;
  command: string;
  log_path?: string | null;
  last_action?: "started" | "stopped" | string;
}

interface AppPackageRef {
  manager: string;
  name?: string;
}

interface AppCapability {
  type: "cli" | "mcp" | "skill" | string;
  entry_point?: string;
  package?: AppPackageRef;
  path?: string;
  transport?: string;
  command?: string;
  args?: string[];
  url?: string;
  fields?: Array<{
    name: string;
    target?: string;
    required?: boolean;
    secret?: boolean;
    env_var?: string | null;
  }>;
}

interface AppPlan {
  supported: boolean;
  strategy?: string;
  managed_paths?: string[];
  verification?: string[];
}

interface AppTrust {
  registry: string;
  level: string;
  review_status: string;
}

interface AppManifest {
  schema: "agent-app.v1" | string;
  id: string;
  display_name: string;
  version?: string;
  description: string;
  category: string;
  source: string;
  logo_url?: string | null;
  brand_color?: string | null;
  docs_url?: string | null;
  capabilities: AppCapability[];
  install: AppPlan;
  remove: AppPlan;
  trust: AppTrust;
}

export interface CliAppInfo {
  name: string;
  display_name: string;
  category: string;
  description: string;
  requires: string;
  source: string;
  entry_point: string;
  install_supported: boolean;
  installed: boolean;
  available: boolean;
  status: "installed" | "missing" | "available" | "unsupported" | "not_installed" | string;
  logo_url?: string | null;
  brand_color?: string | null;
  skill_installed: boolean;
  manifest?: AppManifest;
}

export interface CliAppsPayload {
  apps: CliAppInfo[];
  installed_count: number;
  catalog_updated_at?: string | null;
  catalog_refresh_pending?: boolean;
  last_action?: {
    ok: boolean;
    message: string;
    installed?: boolean;
    removed?: boolean;
    output?: string | null;
    still_available?: boolean;
    verification?: string[];
    verification_failed?: string[];
  };
}

export interface NanobotFeatureInfo {
  name: string;
  display_name: string;
  capabilities?: string[];
  settings_visible?: boolean;
  webui?: string;
  type: "channel" | "feature" | string;
  enabled: boolean;
  running?: boolean;
  runtime_status?: ChannelRuntimeStatus;
  runtime_error?: string;
  configured?: boolean;
  config_values?: Record<string, string>;
  configured_fields?: string[];
  setup?: ChannelSetupContract;
  instances?: NanobotChannelInstanceInfo[];
  installed: boolean;
  ready: boolean;
  status: "enabled" | "missing_dependency" | "not_enabled" | string;
  install_supported: boolean;
  requires_restart: boolean;
}

export interface ChannelSetupContractField {
  key: string;
  field: string;
  kind: "string" | "secret" | "int" | "bool" | "list" | "enum" | string;
  choices: string[];
  required: boolean;
  default_value?: string;
}

export interface ChannelSetupContract {
  fields: ChannelSetupContractField[];
  official_url?: string;
}

export interface NanobotChannelInstanceInfo {
  id: string;
  name: string;
  display_name?: string;
  avatar_url?: string;
  enabled: boolean;
  running?: boolean;
  runtime_status?: ChannelRuntimeStatus;
  runtime_error?: string;
  configured: boolean;
  config_values: Record<string, string>;
  configured_fields: string[];
}

export type ChannelRuntimeStatus = "running" | "starting" | "failed" | "stopped" | string;

export interface NanobotFeaturesPayload {
  features: NanobotFeatureInfo[];
  enabled_count: number;
  requires_restart?: boolean;
  last_action?: {
    ok: boolean;
    message: string;
    enabled?: boolean;
  };
}

type ChannelSetupStatus =
  | "connected"
  | "configured"
  | "needs_setup"
  | "invalid"
  | "unsupported"
  | string;

type ChannelValidationCheckStatus = "pass" | "warn" | "fail" | "skipped" | string;

interface ChannelValidationCheck {
  id: string;
  label: string;
  status: ChannelValidationCheckStatus;
  message?: string;
  action_url?: string;
}

interface ChannelIdentity {
  name?: string;
  workspace?: string;
  account?: string;
  avatar_url?: string;
}

export interface ChannelValidationPayload {
  name: string;
  status: ChannelSetupStatus;
  checks: ChannelValidationCheck[];
  identity?: ChannelIdentity;
  missing_fields: string[];
  can_enable: boolean;
  requires_restart: boolean;
  checked_at?: string;
  message?: string;
}

export interface PairingRequestInfo {
  code: string;
  channel: string;
  sender_id: string;
  created_at_ms?: number | null;
  expires_at_ms?: number | null;
  expires_in_seconds?: number | null;
}

export interface PairingPayload {
  requests: PairingRequestInfo[];
  last_action?: {
    ok: boolean;
    action: "approve" | "deny" | string;
    message: string;
    code?: string;
    channel?: string;
    sender_id?: string;
  };
}

interface McpPresetField {
  name: string;
  label: string;
  secret: boolean;
  required: boolean;
  configured: boolean;
  placeholder?: string;
  env_var?: string | null;
}

export interface McpPresetInfo {
  name: string;
  display_name: string;
  category: string;
  description: string;
  docs_url: string;
  transport: "stdio" | "streamableHttp" | "sse" | "oauth" | string;
  auth?: "oauth" | null;
  requires: string;
  note: string;
  install_supported: boolean;
  installed: boolean;
  configured: boolean;
  enabled?: boolean;
  available: boolean;
  status: "not_installed" | "configured" | "missing_credentials" | "missing_dependency" | "coming_soon" | string;
  runtime_status?: "connecting" | "connected" | "failed" | string;
  logo_url?: string | null;
  brand_color?: string | null;
  required_fields: McpPresetField[];
  connection_summary: string;
  tool_count?: number;
  tool_names?: string[];
  checked_at?: string | null;
  error?: string | null;
  enabled_tools?: string[];
  source?: "preset" | "custom" | string;
  manifest?: AppManifest;
}

type McpOAuthFlowStatus =
  | "starting"
  | "authorization_required"
  | "connecting"
  | "authorized"
  | "connected"
  | "failed"
  | "cancelled";

export interface McpOAuthFlowPayload {
  flow_id: string;
  name: string;
  status: McpOAuthFlowStatus;
  expires_in: number;
  authorization_url?: string;
  completion_input?: "callback_url";
  error?: string;
  hot_reload?: {
    ok: boolean;
    message?: string;
    requires_restart?: boolean;
  };
}

export interface McpPresetsPayload {
  presets: McpPresetInfo[];
  installed_count: number;
  requires_restart?: boolean;
  hot_reload?: {
    ok: boolean;
    message: string;
    added?: string[];
    changed?: string[];
    removed?: string[];
    retried?: string[];
    connected?: string[];
    configured?: string[];
    failed?: string[];
    tools_removed?: number;
    requires_restart?: boolean;
  };
  last_action?: {
    ok: boolean;
    message: string;
    installed?: boolean;
    removed?: boolean;
    managed_paths_removed?: string[];
    verification?: string[];
    verification_failed?: string[];
    tool_count?: number;
    tool_names?: string[];
    checked_at?: string | null;
    error?: string | null;
  };
}

type ChannelConnectStatus = "pending" | "succeeded" | "expired" | "cancelled" | "failed";

export interface ChannelConnectPayload {
  session_id: string;
  instance_id?: string;
  status: ChannelConnectStatus;
  message?: string;
  qr_url?: string;
  domain?: string;
  interval_ms?: number;
  expires_at_ms?: number;
  app_id?: string;
  account?: string;
  nanobot_features?: NanobotFeaturesPayload;
}

export interface ChannelConfigurePayload {
  name: string;
  saved: boolean;
  saved_keys?: string[];
  nanobot_features?: NanobotFeaturesPayload;
}

export interface SettingsUpdate {
  model?: string;
  provider?: string;
  modelPreset?: string | null;
  contextWindowTokens?: number;
  timezone?: string;
  toolHintMaxLength?: number;
}

export interface ModelConfigurationCreate {
  name: string;
  provider: string;
  model: string;
  maxTokens?: number;
  contextWindowTokens?: number;
  temperature?: number;
  reasoningEffort?: string | null;
}

export interface ModelConfigurationUpdate {
  name: string;
  newName?: string;
  provider?: string;
  model?: string;
  maxTokens?: number;
  contextWindowTokens?: number;
  temperature?: number;
  reasoningEffort?: string | null;
}

export interface ProviderSettingsUpdate {
  provider: string;
  displayName?: string;
  apiKey?: string;
  apiBase?: string;
  apiType?: "auto" | "chat_completions" | "responses";
  proxy?: string;
  extraHeaders?: string;
  extraBody?: string;
  extraQuery?: string;
  thinkingStyle?: string;
  region?: string;
  profile?: string;
}

export interface ProviderCreationUpdate {
  name: string;
  apiKey?: string;
  apiBase: string;
  proxy?: string;
  extraHeaders?: string;
  extraBody?: string;
  extraQuery?: string;
  thinkingStyle?: string;
}

export interface WebSearchSettingsUpdate {
  provider: string;
  apiKey?: string;
  baseUrl?: string;
  maxResults?: number;
  timeout?: number;
  useJinaReader?: boolean;
}

export interface NetworkSafetySettingsUpdate {
  webuiAllowLocalServiceAccess: boolean;
  webuiDefaultAccessMode: WebuiDefaultAccessMode;
}

export interface ImageGenerationSettingsUpdate {
  enabled: boolean;
  provider: string;
  model: string;
  defaultAspectRatio: string;
  defaultImageSize: string;
  maxImagesPerTurn: number;
}

export interface TranscriptionSettingsUpdate {
  enabled: boolean;
  provider: string;
  model: string;
  language: string;
  maxDurationSec: number;
  maxUploadMb: number;
}

/**
 * Backend-owned contract for how a slash command affects WebUI turn state.
 *
 * - side_channel: returns control text without starting or ending an agent turn.
 * - finalize_active_turn: side-channel command that also closes the active UI turn.
 * - stop_active_turn: cancels the active turn; exact submits may be intercepted locally.
 * - agent_turn: always enters the normal agent path.
 * - agent_turn_with_args: no args is side-channel usage; args enter the agent path.
 */
export type SlashCommandLifecycle =
  | "side_channel"
  | "finalize_active_turn"
  | "stop_active_turn"
  | "agent_turn"
  | "agent_turn_with_args";

export interface SlashCommand {
  command: string;
  title: string;
  description: string;
  icon: string;
  argHint?: string;
  lifecycle: SlashCommandLifecycle;
  acceptsArgs: boolean;
}

export type ConnectionStatus =
  | "idle"
  | "connecting"
  | "open"
  | "reconnecting"
  | "closed"
  | "error";

interface InboundTurnMetadata {
  turn_id?: string;
  turn_phase?: UITurnPhase;
  turn_seq?: number;
}

export type InboundEvent =
  | { event: "ready"; chat_id: string; client_id: string }
  | {
      event: "attached";
      chat_id: string;
      temporary?: boolean;
      recovery_state?: RecoveryState;
      usage?: TurnUsage;
    }
  | {
      event: "message_accepted";
      chat_id: string;
      turn_id: string;
      starts_turn?: boolean;
      active_turn_id?: string;
      started_at?: number;
    }
  | {
      event: "user_message";
      chat_id: string;
      text: string;
      turn_id?: string;
      active_turn_id?: string;
      starts_turn: boolean;
      started_at?: number;
      created_at_ms?: number;
      media_urls?: UIMediaAttachment[];
      cli_apps?: UICliAppAttachment[];
      mcp_presets?: UIMcpPresetAttachment[];
      session_mentions?: SessionMention[];
      provenance?: { session_message?: UISessionMessage };
    }
  | ({
      event: "message";
      chat_id: string;
      text: string;
      reply_to?: string;
      media?: string[];
      media_urls?: Array<{ url: string; name?: string }>;
      tool_events?: ToolProgressEvent[];
      /** Present when the frame is an agent breadcrumb (e.g. tool hint,
       * generic progress line) rather than a conversational reply. */
      kind?: "tool_hint" | "progress" | "reasoning";
      /** Server-measured turn wall time when this frame finishes an assistant reply. */
      latency_ms?: number;
      /** Lightweight provenance for proactive assistant messages. */
      source?: UIMessageSource;
      /** Optional structured payload on progress frames (channel-specific). */
      agent_ui?: AgentUIBlob;
    } & InboundTurnMetadata)
  | ({
      event: "recovery_state";
      chat_id: string;
    } & RecoveryState)
  | ({
      event: "file_edit";
      chat_id: string;
      edits: UIFileEdit[];
    } & InboundTurnMetadata)
  | ({
      event: "delta";
      chat_id: string;
      text: string;
      stream_id?: string;
      /** Lightweight provenance for proactive streamed assistant messages. */
      source?: UIMessageSource;
    } & InboundTurnMetadata)
  | ({
      event: "stream_end";
      chat_id: string;
      stream_id?: string;
      text?: string;
      /** Lightweight provenance for proactive streamed assistant messages. */
      source?: UIMessageSource;
      /** This answer segment ended, but the active agent turn will continue. */
      resuming?: boolean;
      /** The next answer segment continues this same assistant message. */
      merge_next?: boolean;
    } & InboundTurnMetadata)
  | ({
      event: "reasoning_delta";
      chat_id: string;
      text: string;
      stream_id?: string;
    } & InboundTurnMetadata)
  | ({
      event: "reasoning_end";
      chat_id: string;
      stream_id?: string;
    } & InboundTurnMetadata)
  | {
      event: "runtime_model_updated";
      model_name: string;
      model_preset?: string | null;
    }
  | {
      event: "turn_model_updated";
      chat_id: string;
      model_name: string;
      model_preset?: string | null;
      fallback?: boolean;
    }
  | ({
      event: "turn_end";
      chat_id: string;
      latency_ms?: number;
      usage?: TurnUsage;
      context_window_tokens?: number;
      /** Authoritative sustained-goal snapshot for this chat (same shape as ``goal_state`` events). */
      goal_state?: GoalStateWsPayload;
    } & InboundTurnMetadata)
  | ({
      event: "goal_status";
      chat_id: string;
      /** Turn executing (user message through agent loop). */
      status: "running" | "idle";
      /** Server ``time.time()`` when ``status`` is ``running``. */
      started_at?: number;
    } & InboundTurnMetadata)
  | {
      event: "goal_state";
      chat_id: string;
      goal_state: GoalStateWsPayload;
    }
  | {
      event: "session_updated";
      chat_id: string;
      scope?: "metadata" | "thread" | string;
      workspace_scope?: WorkspaceScopePayload;
    }
  | {
      event: "sidebar_state_updated";
      state: SidebarStatePayload;
    }
  | { event: "transcription_result"; request_id: string; text: string }
  | {
      event: "transcription_error";
      request_id?: string;
      detail?: string;
      provider?: string;
    }
  | {
      event: "webui_response";
      request_id: string;
      ok: true;
      result: unknown;
    }
  | {
      event: "webui_response";
      request_id: string;
      ok: false;
      error: { status: number; message: string };
    }
  | {
      event: "error";
      chat_id?: string;
      detail?: string;
      reason?: string;
      /** Present when this error rejects a specific outbound WebUI turn. */
      turn_id?: string;
    };

/** Base64-encoded file attached to an outbound ``message`` envelope.
 *
 * ``data_url`` must use a server-whitelisted image, video, or document MIME
 * type. SVG remains rejected on ingress to avoid an embedded-script XSS
 * surface. ``name`` is advisory and is surfaced as the placeholder label when
 * the session is replayed.
 */
export interface OutboundMedia {
  data_url: string;
  name?: string;
}

export interface OutboundCliAppMention {
  name: string;
  display_name?: string;
  category?: string;
  entry_point?: string;
  logo_url?: string | null;
  brand_color?: string | null;
}

export interface OutboundMcpPresetMention {
  name: string;
  display_name?: string;
  category?: string;
  transport?: string;
  status?: string;
  configured?: boolean;
  logo_url?: string | null;
  brand_color?: string | null;
}

/** Response shape for ``GET .../webui-thread`` (server-built transcript replay). */
interface WebuiThreadPagePayload {
  before_cursor?: string | null;
  has_more_before?: boolean;
  loaded_message_count?: number;
  total_known_message_count?: number;
  user_message_offset?: number;
}

export interface WebuiThreadPersistedPayload {
  schemaVersion: number;
  sessionKey?: string;
  savedAt?: string;
  messages: UIMessage[];
  fork_boundary_message_count?: number;
  /** Turn ids backed by an explicit persisted ``turn_end`` event. */
  completed_turn_ids?: string[];
  has_pending_tool_calls?: boolean;
  /** Exact active turn when supplied by a current gateway. */
  active_turn_id?: string | null;
  page?: WebuiThreadPagePayload;
  workspace_scope?: WorkspaceScopePayload;
}

export interface FilePreviewPayload {
  path: string;
  display_path: string;
  project_path: string;
  language: string;
  content: string;
  size: number;
  truncated: boolean;
}

export type Outbound =
  | { type: "new_chat"; workspace_scope?: WorkspaceScopePayload }
  | { type: "new_temporary_chat" }
  | {
      type: "webui_request";
      request_id: string;
      action: string;
      payload: Record<string, unknown>;
    }
  | { type: "fork_chat"; source_chat_id: string; before_user_index: number; title?: string }
  | { type: "attach"; chat_id: string }
  | { type: "set_sidebar_state"; state: SidebarStatePayload }
  | { type: "discard_temporary_chat"; chat_id: string }
  | { type: "set_workspace_scope"; chat_id: string; workspace_scope: WorkspaceScopePayload }
  | { type: "transcribe_audio"; request_id: string; data_url: string; duration_ms?: number }
  | {
      type: "message";
      chat_id: string;
      content: string;
      media?: OutboundMedia[];
      cli_apps?: OutboundCliAppMention[];
      mcp_presets?: OutboundMcpPresetMention[];
      session_mentions?: SessionMention[];
      quoted_context?: string;
      workspace_scope?: WorkspaceScopePayload;
      turn_id?: string;
      /** Marks messages sent by the embedded WebUI, without changing the
       * generic websocket protocol for other clients. */
      webui?: true;
    };
