// ╔═══════════════════════════════════════════════════════════════════════════╗
// ║  AUTO-GENERATED by ts-rs — do not edit manually                        ║
// ║  Run `make typegen` to regenerate from Rust models                     ║
// ╚═══════════════════════════════════════════════════════════════════════════╝

// ─── Config ─────────────────────────────────────────────────────────────────

export interface AppConfig {
  server: ServerConfig;
  tokens: TokensConfig;
  scan: ScanConfig;
  agents: AgentsConfig;
  language: string;
}

export interface ServerConfig {
  host: string;
  port: number;
}

export interface TokensConfig {
  keys: ApiKey[];
  disabled_overrides: string[];
}

export interface ApiKey {
  id: string;
  name: string;
  provider: string;
  active: boolean;
}

export interface ApiKeyDisplay {
  id: string;
  name: string;
  provider: string;
  masked_value: string;
  active: boolean;
}

export interface SaveApiKeyRequest {
  id: string | null;
  name: string;
  provider: string;
  value: string;
}

export interface ApiKeysResponse {
  keys: ApiKeyDisplay[];
  disabled_overrides: string[];
}

export interface SaveTokensRequest {
  anthropic: string | null;
  openai: string | null;
}

export interface ScanConfig {
  paths: string[];
  ignore: string[];
  scan_depth: number;
}

export interface AgentsConfig {
  claude_code: AgentConfig;
  codex: AgentConfig;
  gemini_cli: AgentConfig;
  kiro: AgentConfig;
  vibe: AgentConfig;
  copilot_cli: AgentConfig;
  ollama: AgentConfig;
  model_tiers: ModelTiersConfig;
}

export type ModelTier = "economy" | "default" | "reasoning";

export interface ModelTierConfig {
  economy: string | null;
  /** Default-tier override (0.7.1) — when set, wins over the built-in
   *  fallback in `backend/agents/runner.rs:resolve_model_flag`. Surfaced
   *  by the OllamaCard picker (others use the existing tier rows).
   *  Optional in TS so legacy fixtures `{economy,reasoning}` still
   *  type-check; the serde attribute `skip_serializing_if = none`
   *  ensures the wire payload stays clean. */
  default?: string | null;
  reasoning: string | null;
}

export interface ModelTiersConfig {
  claude_code: ModelTierConfig;
  codex: ModelTierConfig;
  gemini_cli: ModelTierConfig;
  kiro: ModelTierConfig;
  vibe: ModelTierConfig;
  copilot_cli: ModelTierConfig;
  ollama: ModelTierConfig;
}

// ─── Ollama (0.4.0) ──────────────────────────────────────────────────

export interface OllamaModel {
  name: string;
  size: string;
  modified: string;
}

export interface OllamaHealthResponse {
  status: string;
  version: string | null;
  endpoint: string;
  models_count: number;
  hint: string | null;
}

export interface OllamaModelsResponse {
  models: OllamaModel[];
}

export interface SetAgentAccessRequest {
  agent: AgentType;
  full_access: boolean;
}

export interface AgentConfig {
  path: string | null;
  installed: boolean;
  version: string | null;
  full_access: boolean;
}

// ─── Setup ──────────────────────────────────────────────────────────────────

export interface SetupStatus {
  is_first_run: boolean;
  current_step: SetupStep;
  agents_detected: AgentDetection[];
  scan_paths_set: boolean;
  repos_detected: DetectedRepo[];
  default_scan_path: string | null;
}

export type SetupStep = "Agents" | "ScanPaths" | "Detection" | "Complete";

export interface AgentDetection {
  name: string;
  agent_type: AgentType;
  installed: boolean;
  enabled: boolean;
  path: string | null;
  version: string | null;
  latest_version: string | null;
  origin: string;
  install_command: string | null;
  host_managed: boolean;
  host_label: string | null;
  /** Agent is runnable via npx/uvx fallback even when no local binary is found */
  runtime_available: boolean;
  /** `rtk` binary found on the host (PATH). Same value for every agent detection in a given sweep. */
  rtk_available: boolean;
  /** Agent's config file declares an RTK hook. Always false for API-only (Vibe) or hookless (Ollama) agents. */
  rtk_hook_configured: boolean;
  /** Optional i18n key for a runtime-degradation warning the frontend should
   *  surface inline (e.g. "vibe.sdk_fallback" when Vibe runs through the
   *  direct Mistral API instead of the SDK with local tools). */
  runtime_warning?: string;
}

/** Result of `POST /api/db/backup` — feeds the Settings UI toast. */
export interface DbBackupResponse {
  /** Absolute path of the backup file written. */
  backup_path: string;
  /** Size of the backup in bytes. */
  size_bytes: number;
  /** ISO-8601 UTC timestamp when the backup was taken. */
  taken_at: string;
}

/** Result of `GET /api/version/check` — feeds the auto-update banner. */
export interface VersionCheck {
  /** Currently-running Kronn version (from CARGO_PKG_VERSION). */
  current: string;
  /** Latest GitHub release tag (without `v` prefix). `null` when offline
   *  or GitHub is unreachable; the banner stays hidden in that case. */
  latest: string | null;
  /** Direct link to the GitHub release page for the user to download. */
  release_url: string | null;
  /** True when `current >= latest` (or `latest === null`). When false,
   *  the banner shows a "new version available" pill. */
  up_to_date: boolean;
}

export type AgentType = "ClaudeCode" | "Codex" | "Vibe" | "GeminiCli" | "Kiro" | "CopilotCli" | "Ollama" | "Custom";

// ─── AI Audit ────────────────────────────────────────────────────────────────

export type AiAuditStatus = "NoTemplate" | "TemplateInstalled" | "Bootstrapped" | "Audited" | "Validated";

export interface LaunchAuditRequest {
  agent: AgentType;
  kind?: string | null;
  custom_prompt?: string | null;
  // 0.8.3 (#311) — resume an interrupted run from step `resume_from + 1`.
  resume_from?: number | null;
}

/**
 * Live audit progress — snapshot of `AppState.audit_tracker.progress`.
 * Polled via `GET /api/projects/:id/audit-status` by ProjectCard to
 * resume the progress bar after tab/page navigation.
 * `phase` is one of "installing" | "auditing" | "validating" | "done".
 * `kind` is one of "full" | "partial" | "full_audit".
 */
export interface AuditProgress {
  project_id: string;
  phase: string;
  step_index: number;
  total_steps: number;
  current_file?: string | null;
  started_at: string;
  kind: string;
  // 0.8.3 — live chips state surfaced via the poll endpoint so the
  // frontend can re-seed when SSE buffers / stalls.
  step_tokens?: number | null;
  total_tokens_so_far?: number | null;
  current_tool?: string | null;
  // 0.8.4 (#319 / B3) — running count of tool calls in the current
  // step. Bumped on every tool_call event regardless of token activity
  // so the user sees a "still alive" signal during long tool-only
  // phases (Step 9 of Full audit writes 25+ TD files without
  // intermediate Usage events).
  current_tool_call_count?: number | null;
}

export interface BootstrapProjectRequest {
  name: string;
  description: string;
  agent: AgentType;
  mcp_config_ids?: string[];
  skill_ids?: string[];
}

export interface BootstrapProjectResponse {
  project_id: string;
  discussion_id: string;
}

export interface CloneProjectRequest { url: string; name: string | null; agent: AgentType; }
export interface CloneProjectResponse { project_id: string; discussion_id: string | null; }

export interface RemoteRepo {
  name: string;
  full_name: string;
  clone_url: string;
  ssh_url: string;
  description: string | null;
  language: string | null;
  stargazers_count: number;
  updated_at: string;
  source: string;
  already_cloned: boolean;
}

export interface RepoSource {
  id: string;
  label: string;
  provider: string;
}

export interface DiscoverReposRequest {
  source_ids: string[];
}

export interface DiscoverReposResponse {
  repos: RemoteRepo[];
  sources: string[];
  available_sources: RepoSource[];
}

// ─── Drift Detection ─────────────────────────────────────────────────────────

export interface DriftCheckResponse {
  audit_date: string | null;
  stale_sections: DriftSection[];
  fresh_sections: string[];
  total_sections: number;
}

export interface DriftSection {
  ai_file: string;
  audit_step: number;
  changed_sources: string[];
}

export interface PartialAuditRequest {
  agent: AgentType;
  steps: number[];
}

// ─── Projects ───────────────────────────────────────────────────────────────

export interface Project {
  id: string;
  name: string;
  path: string;
  repo_url: string | null;
  token_override: TokenOverride | null;
  ai_config: AiConfigStatus;
  audit_status: AiAuditStatus;
  ai_todo_count: number;
  /** Total tech-debt entries detected in the project's docs tree:
   *  one per `.md` file under `docs/tech-debt/` plus one per row in
   *  `docs/inconsistencies-tech-debt.md`. Surfaced as a badge on the
   *  ProjectCard. Computed by the API layer from the filesystem; not
   *  persisted in DB. */
  tech_debt_count?: number;
  /** True when the project still uses the legacy `ai/index.md` layout
   *  and no migrated `docs/AGENTS.md` exists. Drives the migration
   *  banner on `ProjectCard`. Computed by the API layer from the
   *  filesystem; not persisted in DB. */
  needs_docs_migration?: boolean;
  default_skill_ids?: string[];
  briefing_notes?: string | null;
  /** 0.8.3 — companion repos the agent on this project should know
   *  about. Surfaced in every audit / discussion / QP / workflow's
   *  system prompt prelude so cross-repo context flows naturally. */
  linked_repos?: LinkedRepo[];
  created_at: string; // ISO 8601
  updated_at: string;
}

/** A companion repository linked to a project (0.8.3). The
 *  `location` is either a filesystem path or a URL. `kind` is a UI
 *  hint for the icon + grouping; recognized values: api / iac /
 *  design / shared-lib / docs / other. Backend validates that. */
export interface LinkedRepo {
  id: string;
  name: string;
  kind: string;
  location: string;
  description?: string;
}

export interface TokenOverride {
  provider: string;
  token: string;
}

export interface AiConfigStatus {
  detected: boolean;
  configs: AiConfigType[];
}

export type AiConfigType = "ClaudeMd" | "ClauseDir" | "AiDir" | "CursorRules" | "ContinueDev" | "McpJson" | "Custom";

export interface DetectedRepo {
  path: string;
  name: string;
  remote_url: string | null;
  branch: string;
  ai_configs: AiConfigType[];
  has_project: boolean;
  hidden: boolean;
}

// ─── MCPs (new 3-tier model) ────────────────────────────────────────────────

export interface McpServer {
  id: string;
  name: string;
  description: string;
  transport: McpTransport;
  source: McpSource;
  /** REST API capability (present for API-only plugins like Chartbeat,
   *  or hybrid plugins that have both an MCP transport AND a REST API). */
  api_spec?: ApiSpec | null;
}

export type McpTransport =
  | { Stdio: { command: string; args: string[] } }
  | { Sse: { url: string } }
  | { Streamable: { url: string } }
  | "ApiOnly";

/** REST API capability for a plugin. */
export interface ApiSpec {
  base_url: string;
  auth: ApiAuthKind;
  endpoints: ApiEndpoint[];
  docs_url?: string | null;
  config_keys?: ApiConfigKey[];
}

export type ApiAuthKind =
  | { ApiKeyQuery: { param_name: string; env_key: string } }
  | { ApiKeyHeader: { header_name: string; env_key: string } }
  | { Bearer: { env_key: string } }
  | { Basic: { user_env: string; password_env: string } }
  /** HTTP Basic with the API key as username and an empty password —
   *  `Authorization: Basic <base64(API_KEY:)>`. SpeedCurve, Stripe, etc. */
  | { BasicApiKey: { env_key: string } }
  | { OAuth2ClientCredentials: {
      token_url: string;
      client_id_env: string;
      client_secret_env: string;
      scope: string;
      extra_headers?: OAuth2ExtraHeader[];
    } }
  /** 0.8.6 — Generic 2-step token exchange. Generalises OAuth2 to
   *  arbitrary body shapes (JSON / form-encoded), field names, and
   *  token JSONPaths. Unblocks Didomi (POST /sessions with
   *  `{type, key, secret}` JSON body → `access_token`) and similar
   *  enterprise APIs. Token cached + refreshed transparently. */
  | { TokenExchange: {
      endpoint: string;
      method: string;
      body_template: unknown;
      body_format: TokenExchangeBodyFormat;
      token_jsonpath: string;
      ttl_seconds: number;
      inject: TokenInjection;
      creds_env_keys?: string[];
    } }
  | "None";

export type TokenExchangeBodyFormat = "Json" | "FormUrlEncoded";

export type TokenInjection =
  | "BearerHeader"
  | { CustomHeader: { name: string } }
  | { QueryParam: { name: string } };

export interface OAuth2ExtraHeader {
  name: string;
  value_template: string;
}

export interface ApiEndpoint {
  path: string;
  method: string;
  description: string;
}

export interface ApiConfigKey {
  env_key: string;
  label: string;
  placeholder: string;
  description: string;
}

export type McpSource = "Registry" | "Detected" | "Manual" | "HostImported";

/** How a config is surfaced to local CLIs running outside a Kronn project. */
export type HostSyncMode = "None" | "GlobalOnly" | "MirrorAll";

// ─── Host MCP discovery (Phase 1 inbound) ───────────────────────────────────

export type HostScope =
  | { kind: "ClaudeUser" }
  | { kind: "ClaudeLocal"; value: { project_path: string } }
  | { kind: "Gemini" }
  | { kind: "Codex" }
  | { kind: "Copilot" };

export type KronnOwnership =
  | { type: "NotManaged" }
  | { type: "ManagedByMarker"; config_id: string }
  | { type: "ManagedByHash"; config_id: string };

export interface DiscoveredHostMcp {
  source_file: string;
  scope: HostScope;
  name: string;
  transport: McpTransport;
  /** Env variable names declared by the entry. Values are never surfaced. */
  env_keys: string[];
  managed_by_kronn: KronnOwnership;
}

export interface McpConfig {
  id: string;
  server_id: string;
  label: string;
  env_keys: string[];
  env_encrypted: string;
  args_override: string[] | null;
  is_global: boolean;
  include_general: boolean;
  config_hash: string;
  project_ids: string[];
  host_sync: HostSyncMode;
}

export interface McpConfigDisplay {
  id: string;
  server_id: string;
  server_name: string;
  label: string;
  env_keys: string[];
  env_masked: McpEnvEntry[];
  args_override: string[] | null;
  is_global: boolean;
  include_general: boolean;
  config_hash: string;
  project_ids: string[];
  project_names: string[];
  /** True when env_keys exist but decryption fails (secrets need re-entry). */
  secrets_broken: boolean;
  host_sync: HostSyncMode;
}

export interface McpEnvEntry {
  key: string;
  masked_value: string;
}

export interface McpOverview {
  servers: McpServer[];
  configs: McpConfigDisplay[];
  customized_contexts: string[];
  incompatibilities: McpIncompatibility[];
  // Optional in TS to keep existing test fixtures source-compatible.
  // Backend always emits the field (#[serde(default)]).
  incomplete_configs?: McpIncompleteConfig[];
}

export interface McpIncompatibility {
  server_id: string;
  agent: AgentType;
  reason: string;
}

export interface McpIncompleteConfig {
  config_id: string;
  label: string;
  server_name: string;
  missing_keys: string[];
  reason: string;
}

// Legacy types (kept for backward compatibility)
export interface McpDefinition {
  id: string;
  name: string;
  description: string;
  transport: McpTransport;
  env_keys: string[];
  tags: string[];
  token_url: string | null;
  token_help: string | null;
  publisher: string;
  official: boolean;
  /** API capability (present for API-only plugins like Chartbeat, or
   *  hybrid plugins). Mirrored onto `McpServer.api_spec` at seed time. */
  api_spec?: ApiSpec | null;
}

// ─── Workflows ─────────────────────────────────────────────────────────────

export interface Workflow {
  id: string;
  name: string;
  project_id: string | null;
  trigger: WorkflowTrigger;
  steps: WorkflowStep[];
  actions: WorkflowAction[];
  safety: WorkflowSafety;
  workspace_config: WorkspaceConfig | null;
  concurrency_limit: number | null;
  /** 0.7.0 — execution limits (timeout, max LLM calls, loop detection).
   *  `null` = use backend defaults (120 min, 100 calls, 10 revisits/step). */
  guards?: WorkflowGuards | null;
  /** 0.7.0 Phase 3 — declared artifacts (file outputs the steps may
   *  emit). Map key = artifact name; referenced in steps as
   *  `{{artifacts.<name>}}`. Absent / empty map = no artifact persistence. */
  artifacts?: Record<string, ArtifactSpec>;
  /** 0.7.0 Phase 7 — compensating steps run when the main pipeline ends in
   *  RunStatus::Failed. Empty by default. NOT fired on Cancelled / StoppedByGuard
   *  / Gate-Reject. Each rollback step sees `{{failed_step.name}}` and
   *  `{{failed_step.output}}` in addition to the regular template context.
   *  Gate steps are rejected here (would deadlock the run). */
  on_failure?: WorkflowStep[];
  /** 0.7.0 Phase 5 — allowlist of binaries that StepType::Exec is permitted
   *  to invoke. Empty list = Exec disabled (the safe default). Match is exact
   *  on the bare binary name (no path, no glob). */
  exec_allowlist?: string[];
  /** 0.6.0 UX pass — variables prompted at manual launch time (mirrors
   *  QuickPrompt.variables). Empty = no launch form, lance direct. */
  variables?: PromptVariable[];
  enabled: boolean;
  created_at: string;
  updated_at: string;
}

/** Declared artifact in a workflow. Path is workspace-relative. */
export interface ArtifactSpec {
  path: string;
  /** Hint for the UI: "markdown", "yaml", "json", "text". Informational only. */
  format?: string | null;
}

/** Per-workflow execution limits enforced by the runner. */
export interface WorkflowGuards {
  /** Wall-clock max duration in seconds. */
  timeout_seconds?: number | null;
  /** Cap on LLM-spending steps (Agent=1, BatchQuickPrompt=1, ApiCall/Notify=0). */
  max_llm_calls?: number | null;
  /** Max revisits of the same step via Goto. Default 10. */
  loop_detection_max_revisits?: number | null;
}

/** Which guard tripped (sent in the `guard_triggered` SSE event). */
export type GuardKind =
  | { type: "Timeout" }
  | { type: "MaxLlmCalls" }
  | { type: "LoopDetection"; step_name: string };

export type WorkflowTrigger =
  | { type: "Cron"; schedule: string }
  | { type: "Tracker"; source: TrackerSourceConfig; query: string; labels: string[]; interval: string }
  | { type: "Manual" };

export type TrackerSourceConfig =
  | { type: "GitHub"; owner: string; repo: string };

/** 0.8.3 — what happens after `TypedSchema` validation still fails
 *  post-repair. `Continue` keeps the pre-0.8.3 behavior (warn + raw
 *  output). `Fail` fails the step (and the run, unless guarded). */
export type OnInvalid = "Continue" | "Fail";

export type StepOutputFormat =
  | { type: "FreeText" }
  | { type: "Structured" }
  /** 0.7.0 — like Structured, but `data` is constrained by a JSON-Schema
   *  subset. The schema is sent to the LLM in the prompt and validated
   *  post-extract by the runner.
   *  0.8.3 — `on_invalid` controls strict mode for contract steps. */
  | { type: "TypedSchema"; schema: unknown; on_invalid?: OnInvalid };

export interface WorkflowStep {
  name: string;
  step_type?: StepType;
  description?: string | null;
  agent: AgentType;
  prompt_template: string;
  mode: StepMode;
  output_format?: StepOutputFormat;
  mcp_config_ids?: string[];
  agent_settings?: AgentSettings | null;
  on_result?: StepConditionRule[];
  stall_timeout_secs?: number | null;
  retry?: RetryConfig | null;
  delay_after_secs?: number | null;
  skill_ids?: string[];
  profile_ids?: string[];
  directive_ids?: string[];
  // ─── BatchQuickPrompt fields (Phase 2 batch workflows) ─────────────
  // Only meaningful when step_type.type === 'BatchQuickPrompt'.
  batch_quick_prompt_id?: string | null;
  batch_items_from?: string | null;
  batch_wait_for_completion?: boolean | null;
  batch_max_items?: number | null;
  /// "Direct" (default) or "Isolated" — per-child git worktree for parallel code writes.
  batch_workspace_mode?: string | null;
  /** Quick Prompt IDs to fire sequentially after the initial one inside each
   * child discussion. Progress only increments when the full chain finishes. */
  batch_chain_prompt_ids?: string[];
  /** 0.6.0 — concurrent fan-out cap for `BatchApiCall` (HTTP path only).
   * Default 5; capped at 20. Distinct from agent_semaphore which gates
   * `BatchQuickPrompt`. */
  batch_concurrent_limit?: number | null;
  /** 0.6.0 — when set on a `BatchApiCall` step, the executor loads the
   * referenced QuickApi from DB at run-time and uses its API config.
   * The step's own api_* fields take precedence per-field. 0.7+ — étendu
   * à `StepType::ApiCall` (single, non-batch). */
  quick_api_id?: string | null;
  /** 0.7+ — when set on an `Agent` step, the runner loads the referenced
   *  QuickPrompt and pulls `prompt_template`, `tier`, and `skill_ids` from
   *  it. The step's values override per-field (non-empty wins). */
  quick_prompt_id?: string | null;
  // ─── Notify fields (0.3.5) ─────────────────────────────────────────
  // Only meaningful when step_type.type === 'Notify'.
  notify_config?: NotifyConfig | null;
  // ─── ApiCall fields (désagentification — 0.5.2) ────────────────────
  // Only meaningful when step_type.type === 'ApiCall'. See
  // ai/operations/deagent-apicall.md for the full contract.
  api_plugin_slug?: string | null;
  api_config_id?: string | null;
  api_endpoint_path?: string | null;
  api_method?: string | null;
  api_path_params?: Record<string, string> | null;
  api_query?: Record<string, string> | null;
  api_headers?: Record<string, string> | null;
  api_body?: unknown | null;
  api_extract?: ExtractSpec | null;
  api_pagination?: PaginationSpec | null;
  api_timeout_ms?: number | null;
  api_max_retries?: number | null;
  api_output_var?: string | null;

  // ─── Gate fields (0.7.0 Phase 4 — human-in-the-loop) ─────────────
  /** Markdown message shown to the operator. Templates supported. */
  gate_message?: string | null;
  /** Step name to jump to on "Request Changes". Default: previous step. */
  gate_request_changes_target?: string | null;
  /** 0.7.0 P1-1 — optional webhook URL fired when the run enters
   *  WaitingApproval. Best-effort, fire-and-forget. Templated. */
  gate_notify_url?: string | null;

  // ─── Exec fields (0.7.0 Phase 5 — direct shell, allowlist-gated) ─────
  /** Binary to execute. Must match an entry in Workflow.exec_allowlist. NOT templated. */
  exec_command?: string | null;
  /** Argv elements. Templates {{steps.X}} are rendered, but the result becomes a literal arg. */
  exec_args?: string[];
  /** Per-step timeout in seconds. Default 300, capped at 1800. */
  exec_timeout_secs?: number | null;
  /** 0.8.2 — Optional setup command that runs IMMEDIATELY BEFORE the main
   *  `exec_command`. Same allowlist + path-separator + multi-word-shell
   *  validation as `exec_command`. Designed for the worktree-dep-install
   *  pattern (composer install / npm ci / pnpm install). If setup fails,
   *  the step fails immediately without running the main command. */
  exec_setup_command?: string | null;
  /** Argv for `exec_setup_command`. Same literal semantics as `exec_args`. */
  exec_setup_args?: string[];

  // ─── JsonData fields (0.7+ — déterministe data source) ───────────────
  /** Payload JSON émis par le step. Validé au save (parse JSON valide,
   *  taille raisonnable). Aucun templating au runtime — la valeur est
   *  retournée telle quelle. Voir `StepType.JsonData`. */
  json_data_payload?: unknown | null;
}

/** Configuration for a Notify webhook step (zero agent tokens). */
export interface NotifyConfig {
  url: string;
  method: string;
  headers: Record<string, string>;
  body_template: string;
}

/** JSONPath extraction spec for an ApiCall step's JSON response. */
export interface ExtractSpec {
  /** JSONPath expression, e.g. `$.issues[*].key`. */
  path: string;
  /** Default value when path resolves to nothing. */
  fallback?: unknown | null;
  /** If true, empty extraction → `status: NO_RESULTS` (for on_result routing). */
  fail_on_empty?: boolean;
}

/** Pagination strategy for an ApiCall step. */
export type PaginationSpec =
  | { type: "None" }
  | { type: "Auto"; max_pages?: number | null }
  | { type: "Offset"; start_param: string; limit_param: string; limit: number; total_path: string; max_pages?: number | null }
  | { type: "Cursor"; cursor_param: string; next_path: string; max_pages?: number | null }
  | { type: "Page"; page_param: string; page_size_param: string; page_size: number; has_more_path: string; max_pages?: number | null };

export type StepType =
  | { type: "Agent" }
  | { type: "ApiCall" }
  | { type: "BatchQuickPrompt" }
  | { type: "Notify" }
  | { type: "Gate" }
  | { type: "Exec" }
  | { type: "BatchApiCall" }
  | { type: "JsonData" };

export type StepMode =
  | { type: "Normal" };

export interface AgentSettings {
  model?: string | null;
  tier?: ModelTier | null;
  reasoning_effort?: string | null;
  max_tokens?: number | null;
}

export interface StepConditionRule {
  contains: string;
  action: ConditionAction;
}

export type ConditionAction =
  | { type: "Stop" }
  | { type: "Skip" }
  /** 0.7.0 Phase 6 — `max_iterations` caps how many times THIS Goto fires
   *  before the runner falls through. `null` = no per-edge limit (only the
   *  workflow-level loop_detection guard applies). */
  | { type: "Goto"; step_name: string; max_iterations?: number | null };

export interface RetryConfig {
  max_retries: number;
  backoff: string;
}

export type WorkflowAction =
  | { type: "CreatePr"; title_template: string; body_template: string; branch_template: string }
  | { type: "CommentIssue"; body_template: string }
  | { type: "UpdateTrackerStatus"; status: string }
  | { type: "CreateIssue"; title_template: string; body_template: string };

export interface WorkflowSafety {
  sandbox: boolean;
  max_files?: number | null;
  max_lines?: number | null;
  require_approval: boolean;
}

export interface WorkspaceConfig {
  hooks: WorkspaceHooks;
}

export interface WorkspaceHooks {
  after_create?: string | null;
  before_run?: string | null;
  after_run?: string | null;
  before_remove?: string | null;
}

export interface WorkflowRun {
  id: string;
  workflow_id: string;
  status: RunStatus;
  trigger_context: WorkflowTrigger | null;
  step_results: StepResult[];
  tokens_used: number;
  workspace_path: string | null;
  started_at: string;
  finished_at: string | null;
  /** "linear" (classic workflow runs) or "batch" (Phase 1b fan-out) */
  run_type?: string;
  /** Batch runs only: target number of child discussions */
  batch_total?: number;
  /** Batch runs only: number of child discussions that finished OK */
  batch_completed?: number;
  /** Batch runs only: number of child discussions that failed */
  batch_failed?: number;
  /** Batch runs only: display name in the sidebar group header */
  batch_name?: string | null;
  /** 0.7.0 — branches preserved by the runner during worktree cleanup
   *  because their HEAD held commits not on any known base. Empty when
   *  nothing was preserved (most cases). */
  produced_branches?: ProducedBranch[];
}

export interface ProducedBranch {
  branch_name: string;
  head_sha: string;
  ahead: number;
  pushed_upstream: boolean;
}

export type RunStatus = "Pending" | "Running" | "Success" | "Failed" | "Cancelled" | "WaitingApproval" | "StoppedByGuard";

/** 0.7.0 Phase 4 — payload for POST /api/workflows/:id/runs/:run_id/decide. */
export interface DecideRunRequest {
  /** "approve" | "request_changes" | "reject" */
  decision: string;
  /** Operator's free-text comment. Required for request_changes. */
  comment?: string | null;
}

export interface DecideRunResponse {
  run_id: string;
  new_status: RunStatus;
}

export interface StepResult {
  step_name: string;
  status: RunStatus;
  output: string;
  tokens_used: number;
  duration_ms: number;
  /** 0.8.2 — Wall-clock timestamp at which the step started executing.
   *  Optional for backward compatibility with runs predating the field.
   *  Drives the WorkflowDetail page's "live progress" view after navigation
   *  (synthesized liveRun) and the gate's actual pause duration. */
  started_at?: string | null;
  condition_result?: string | null;
  /**
   * For output_format=Structured only: did the agent produce the
   * ---STEP_OUTPUT--- envelope (possibly after repair).
   * true = envelope found, .data/.summary/.status populated downstream.
   * false = Structured requested but extraction failed even after repair.
   * null/undefined = FreeText step, the concept does not apply.
   */
  envelope_detected?: boolean | null;
  /** Snapshot of `step.step_type.type` at execution time. `null` for
   *  rows written before this field existed. */
  step_kind?: string | null;
  /** Snapshot of `step.agent` for Agent steps. */
  step_agent?: AgentType | null;
  /** Snapshot of `step.api_plugin_slug` for ApiCall steps. */
  step_api_plugin_slug?: string | null;
  /** Snapshot of `step.api_endpoint_path` for ApiCall steps. */
  step_api_endpoint_path?: string | null;
}

export interface WorkflowSummary {
  id: string;
  name: string;
  project_id: string | null;
  project_name: string | null;
  trigger_type: string;
  step_count: number;
  enabled: boolean;
  last_run: WorkflowRunSummary | null;
  created_at: string;
}

export interface WorkflowRunSummary {
  id: string;
  status: RunStatus;
  started_at: string;
  finished_at: string | null;
  tokens_used: number;
}

/** Compact summary of a batch workflow run, with its parent linear run
 *  resolved to (workflow name + run sequence). Feeds the sidebar pastille. */
export interface BatchRunSummary {
  run_id: string;
  batch_name: string | null;
  batch_total: number;
  status: RunStatus;
  /** Quick Prompt that this batch fans out — used as the sidebar folder label. */
  quick_prompt_id: string | null;
  quick_prompt_name: string | null;
  quick_prompt_icon: string | null;
  parent_run_id: string | null;
  parent_workflow_id: string | null;
  parent_workflow_name: string | null;
  parent_run_sequence: number | null;
}

export interface WorkflowSuggestion {
  id: string;
  title: string;
  description: string;
  reason: string;
  required_mcps: string[];
  audience: string;
  complexity: string;
  trigger: WorkflowTrigger;
  steps: WorkflowStep[];
}

export interface CreateWorkflowRequest {
  name: string;
  project_id?: string | null;
  trigger: WorkflowTrigger;
  steps: WorkflowStep[];
  actions?: WorkflowAction[];
  safety?: WorkflowSafety | null;
  workspace_config?: WorkspaceConfig | null;
  concurrency_limit?: number | null;
  guards?: WorkflowGuards | null;
  artifacts?: Record<string, ArtifactSpec>;
  /** 0.7.0 Phase 7 — rollback chain. Empty = no compensation. Gate steps rejected. */
  on_failure?: WorkflowStep[];
  /** 0.7.0 Phase 5 — Exec binary allowlist. Empty = Exec disabled. */
  exec_allowlist?: string[];
  /** 0.6.0 UX pass — manual launch variables. */
  variables?: PromptVariable[];
}

// 0.8.3 — Bundle endpoint types (atomic Workflow + QP + QA + CustomAPI).
// See backend/src/models/bundle.rs for the canonical source.

export interface BundleCreated {
  bundle_id: string;
  id: string;
  name: string;
}

export interface BundleWorkflowCreated {
  id: string;
  name: string;
}

export interface BundleResponse {
  quick_prompts: BundleCreated[];
  quick_apis: BundleCreated[];
  custom_apis: BundleCreated[];
  workflow: BundleWorkflowCreated;
}

export interface UpdateWorkflowRequest {
  name?: string;
  project_id?: string | null;
  trigger?: WorkflowTrigger;
  steps?: WorkflowStep[];
  actions?: WorkflowAction[];
  safety?: WorkflowSafety;
  workspace_config?: WorkspaceConfig;
  concurrency_limit?: number | null;
  guards?: WorkflowGuards | null;
  artifacts?: Record<string, ArtifactSpec> | null;
  /** Replace the rollback chain entirely when present. Send `[]` to clear. */
  on_failure?: WorkflowStep[] | null;
  /** Replace the Exec allowlist entirely when present. Send `[]` to disable. */
  exec_allowlist?: string[] | null;
  /** Replace launch-time variables entirely. */
  variables?: PromptVariable[] | null;
  enabled?: boolean;
}

/** 0.7.0 UX pass — payload for POST /api/workflows/import. */
export interface ImportWorkflowRequest {
  content: string;
  project_id?: string | null;
}

/** 0.6.0 UX pass — optional payload for POST /api/workflows/:id/trigger.
 *  Carries values matching the workflow's declared `variables`. */
export interface TriggerWorkflowRequest {
  variables: Record<string, string>;
}

/** 0.7.0 UX pass — payload for POST /api/quick-prompts/import. */
export interface ImportQuickPromptRequest {
  content: string;
  project_id?: string | null;
}

/** Self-contained envelope returned by GET /api/workflows/:id/export. */
export interface WorkflowExportEnvelope {
  kind: string;
  version: number;
  exported_at: string;
  workflow: Workflow;
  referenced_quick_prompts?: QuickPrompt[];
}

/** Self-contained envelope returned by GET /api/quick-prompts/:id/export. */
export interface QuickPromptExportEnvelope {
  kind: string;
  version: number;
  exported_at: string;
  quick_prompt: QuickPrompt;
}

// ─── AI Documentation Files ─────────────────────────────────────────────────

export interface AiFileNode {
  path: string;
  name: string;
  is_dir: boolean;
  children?: AiFileNode[];
}

export interface AiFileContent {
  path: string;
  content: string;
}

export interface AiSearchResult {
  path: string;
  match_count: number;
}

// ─── Stats ──────────────────────────────────────────────────────────────────

export interface TokenUsageSummary {
  total_tokens: number;
  total_cost_usd: number;
  discussion_tokens: number;
  workflow_tokens: number;
  by_provider: ProviderUsage[];
  by_project: ProjectUsage[];
  top_discussions: UsageEntry[];
  top_workflows: UsageEntry[];
  daily_history: DailyUsage[];
}

export interface UsageEntry {
  id: string;
  name: string;
  tokens_used: number;
  cost_usd: number;
}

export interface ProviderUsage {
  provider: string;
  tokens_used: number;
  tokens_limit: number | null;
  cost_usd: number | null;
}

export interface ProjectUsage {
  project_id: string;
  project_name: string;
  tokens_used: number;
  cost_usd: number;
}

export interface DailyUsage {
  date: string;
  tokens: number;
  cost_usd: number;
  anthropic: number;
  openai: number;
  google: number;
  mistral: number;
  amazon: number;
  github: number;
}

export interface AgentUsageSummary {
  agent_type: string;
  total_tokens: number;
  message_count: number;
  by_project: AgentProjectUsage[];
}

// ─── Context Files ──────────────────────────────────────────────────────────

export interface ContextFile {
  id: string;
  discussion_id: string;
  filename: string;
  mime_type: string;
  original_size: number;
  extracted_size: number;
  disk_path?: string | null;
  created_at: string;
}

export interface UploadContextFileResponse {
  file: ContextFile;
  suggested_skills: string[];
}

export interface AgentProjectUsage {
  project_id: string;
  project_name: string;
  tokens_used: number;
  message_count: number;
}

// ─── Skills (WHAT — domain expertise, multi-select) ────────────────────────

export type SkillCategory = "Language" | "Domain" | "Business";

export interface Skill {
  id: string;
  name: string;
  description: string;
  icon: string;
  category: SkillCategory;
  content: string;
  is_builtin: boolean;
  token_estimate: number;
  /** agentskills.io: SPDX license identifier */
  license: string | null;
  /** agentskills.io: space-delimited list of pre-approved tools */
  allowed_tools: string | null;
  /** Auto-activation regexes by locale, declared in the skill's YAML
   *  frontmatter. When a message in the relevant locale matches any
   *  pattern, the frontend auto-adds this skill to the discussion. */
  auto_triggers?: AutoTriggers | null;
  /** 0.7+ — true when the skill content was vendored from a third-party
   *  open-source project. UI renders a "🔗 External" badge. */
  external?: boolean;
  /** 0.7+ — when `external` is true, upstream project URL (for attribution). */
  source_url?: string | null;
}

export interface AutoTriggers {
  /** Patterns that apply regardless of the discussion language.
   *  Usually language-independent tokens (format names, brand names). */
  common?: string[];
  /** Per-locale pattern buckets keyed by IETF language tag
   *  (`fr`, `en`, `es`, …). */
  locales?: Record<string, string[]>;
}

export interface CreateSkillRequest {
  name: string;
  description: string;
  icon: string;
  category: SkillCategory;
  content: string;
  license?: string | null;
  allowed_tools?: string | null;
}

// ─── Profiles (WHO — persona, multi-select) ────────────────────────────────

export type ProfileCategory = "Technical" | "Business" | "Meta";

export interface AgentProfile {
  id: string;
  name: string;
  persona_name: string;
  role: string;
  avatar: string;
  color: string;
  category: ProfileCategory;
  persona_prompt: string;
  default_engine?: string;
  is_builtin: boolean;
  token_estimate: number;
}

export interface CreateProfileRequest {
  name: string;
  persona_name: string;
  role: string;
  avatar: string;
  color: string;
  category: ProfileCategory;
  persona_prompt: string;
  default_engine?: string;
}

// ─── Directives (HOW — output behavior, multi-select) ──────────────────────

export type DirectiveCategory = "Output" | "Language";

export interface Directive {
  id: string;
  name: string;
  description: string;
  icon: string;
  category: DirectiveCategory;
  content: string;
  is_builtin: boolean;
  conflicts?: string[];
  token_estimate: number;
  /** Optional URL to the upstream project for third-party adaptations (e.g. Caveman). */
  source_url?: string | null;
}

export interface CreateDirectiveRequest {
  name: string;
  description: string;
  icon: string;
  category: DirectiveCategory;
  content: string;
  conflicts?: string[];
}

// ─── Discussions ────────────────────────────────────────────────────────────

export interface Discussion {
  id: string;
  project_id: string | null;
  title: string;
  agent: AgentType;
  language: string;
  participants: AgentType[];
  messages: DiscussionMessage[];
  message_count: number;
  skill_ids?: string[];
  profile_ids?: string[];
  directive_ids?: string[];
  tier?: ModelTier;
  archived: boolean;
  pinned: boolean;
  workspace_mode: string;
  workspace_path?: string | null;
  worktree_branch?: string | null;
  summary_cache?: string | null;
  summary_up_to_msg_idx?: number | null;
  /** Auto-summary policy. `Auto` keeps the historical threshold-based fire,
   *  `OnDemand` waits for the agent to ask via the kronn-internal MCP tools,
   *  `Off` disables summarisation entirely. Per-disc override of the global
   *  per-agent default. Backward-compatible — pre-existing rows default to Auto. */
  summary_strategy?: 'Auto' | 'OnDemand' | 'Off';
  /** Cumulative count of `kronn-internal` MCP tool calls made by the agent
   *  on this discussion (disc_meta + disc_get_message + disc_summarize).
   *  Surfaces as a small "🔧 N" pill on the ChatHeader. */
  introspection_call_count?: number;
  shared_id?: string | null;
  shared_with?: string[];
  /** If set, this disc was spawned by a batch WorkflowRun — used for sidebar grouping */
  workflow_run_id?: string | null;
  /** Test mode — branch the main repo was on before the user entered test mode. */
  test_mode_restore_branch?: string | null;
  /** Test mode — auto-stash ref (kronn:auto-<disc_id>) pushed on enter, popped on exit. */
  test_mode_stash_ref?: string | null;
  created_at: string; // ISO 8601
  updated_at: string;
}

export interface DiscussionMessage {
  id: string;
  role: MessageRole;
  content: string;
  agent_type: AgentType | null;
  timestamp: string; // ISO 8601
  tokens_used: number;
  auth_mode: string | null;
  model_tier?: string | null;
  author_pseudo?: string | null;
  author_avatar_email?: string | null;
}

export type MessageRole = "User" | "Agent" | "System";

// ─── MCP Context ───────────────────────────────────────────────────────────

export interface McpContextEntry {
  slug: string;
  label: string;
  content: string;
}

export interface UpdateMcpContextRequest {
  content: string;
}

// ─── API Requests ───────────────────────────────────────────────────────────

export interface SetScanPathsRequest {
  paths: string[];
}

export interface CreateMcpConfigRequest {
  server_id: string;
  label: string;
  env: Record<string, string>;
  args_override: string[] | null;
  is_global: boolean;
  project_ids: string[];
  /**
   * Custom API plugin payload. Only honoured when `server_id == "api-custom"`.
   * The backend materializes a new `McpServer` (API-only, `source = Manual`)
   * from these fields, then proceeds with the normal config-creation path.
   * Auth type is always `None` for custom plugins; the agent reads the
   * description + docs_url + fields and figures out auth itself.
   */
  custom_spec?: CustomApiPayload | null;
}

export interface CustomApiPayload {
  name: string;
  base_url: string;
  description?: string;
  docs_url?: string | null;
  /** List of {label, value} pairs. Backend slugifies label → env_key. */
  fields?: CustomApiField[];
  /** 0.8.6 — endpoints declared at create time (often filled by
   *  `CustomApiAiHelper` after fetching `docs_url`). Without these,
   *  the executor's allowlist refuses every agent-driven ApiCall —
   *  declaring them flips `mcp_list`'s `hint` from `NEEDS_RESEARCH`
   *  to `READY`. Blank-path entries are dropped server-side. */
  endpoints?: ApiEndpoint[];
  /** 0.8.6 — auth scheme. Default = "None" (back-compat for any
   *  payload that omits the field). Set to Bearer / TokenExchange /
   *  etc. to make Kronn auto-inject the right Authorization header
   *  on every call to this plugin's endpoints. */
  auth?: ApiAuthKind;
}

export interface CustomApiField {
  label: string;
  value: string;
}

export interface UpdateMcpConfigRequest {
  label?: string;
  env?: Record<string, string>;
  args_override?: string[];
  is_global?: boolean;
  include_general?: boolean;
  host_sync?: HostSyncMode;
}

export interface AdoptHostMcpRequest {
  source_file: string;
  scope: HostScope;
  name: string;
}

export interface LinkMcpConfigRequest {
  project_ids: string[];
}

export interface RevealSecretRequest {
  config_id: string;
}

// 0.8.5 — `originating_qp_id` added at the bottom; stamped server-side
// from the snapshot table to compute the current version_index.
export interface CreateDiscussionRequest {
  project_id: string | null;
  title: string;
  agent: AgentType;
  language?: string;
  initial_prompt: string;
  skill_ids?: string[];
  profile_ids?: string[];
  directive_ids?: string[];
  workspace_mode?: string;
  base_branch?: string;
  tier?: ModelTier;
  /** 0.8.5 — when set, the backend resolves the QP's current
   *  version_index from the snapshot table and stamps both columns on
   *  `discussions`. Drives the QP-metrics aggregator. */
  originating_qp_id?: string | null;
}

export interface SendMessageRequest {
  content: string;
  target_agent?: AgentType;
}

export interface OrchestrationRequest {
  agents: AgentType[];
  max_rounds?: number;
  skill_ids?: string[];
  profile_ids?: string[];
  directive_ids?: string[];
}

export interface Contact {
  id: string;
  pseudo: string;
  avatar_email: string | null;
  kronn_url: string;
  invite_code: string;
  status: string;
  created_at: string;
  updated_at: string;
}

export interface AddContactResult {
  contact: Contact;
  warning: string | null;
}

export type WsMessage =
  | { type: 'presence'; from_pseudo: string; from_invite_code: string; online: boolean }
  | { type: 'ping'; timestamp: number }
  | { type: 'pong'; timestamp: number }
  | { type: 'chat_message'; shared_discussion_id: string; message_id: string; from_pseudo: string; from_avatar_email: string | null; from_invite_code: string; content: string; timestamp: number }
  | { type: 'discussion_invite'; shared_discussion_id: string; title: string; from_pseudo: string; from_invite_code: string }
  | { type: 'batch_run_finished'; run_id: string; discussion_id: string; batch_name: string | null; batch_total: number; batch_completed: number; batch_failed: number }
  | { type: 'batch_run_progress'; run_id: string; discussion_id: string; batch_total: number; batch_completed: number; batch_failed: number }
  | { type: 'workflow_run_updated'; run_id: string; workflow_id: string; status: string; step_index: number; total_steps: number; current_step: string | null }
  | { type: 'partial_response_recovered'; discussion_ids: string[] };

export interface DetectedIp {
  ip: string;
  kind: 'tailscale' | 'vpn' | 'lan';
  label: string;
}

export interface NetworkInfo {
  tailscale_ip: string | null;
  advertised_host: string;
  port: number;
  domain: string | null;
  detected_ips: DetectedIp[];
}

export interface ServerConfigPublic {
  host: string;
  port: number;
  domain: string | null;
  max_concurrent_agents: number;
  agent_stall_timeout_min: number;
  auth_enabled: boolean;
  pseudo: string | null;
  avatar_email: string | null;
  bio: string | null;
  debug_mode: boolean;
}

export interface DbInfo {
  size_bytes: number;
  project_count: number;
  discussion_count: number;
  message_count: number;
  mcp_count: number;
  workflow_count: number;
  workflow_run_count: number;
  custom_skill_count: number;
  custom_profile_count: number;
  custom_directive_count: number;
}

export interface DbExport {
  version: number;
  exported_at: string;
  projects: Project[];
  discussions: Discussion[];
  workflows?: any[];
  mcp_servers?: any[];
  mcp_configs?: any[];
  custom_skills?: Skill[];
  custom_directives?: Directive[];
  custom_profiles?: AgentProfile[];
  contacts?: Contact[];
}

export interface ImportResult {
  warnings: string[];
  invalid_paths: string[];
}

export interface TestStepRequest {
  step: WorkflowStep;
  project_id?: string | null;
  mock_previous_output?: string | null;
  mock_variables?: Record<string, string>;
  dry_run?: boolean;
}

export interface PromptVariable {
  name: string;
  label: string;
  placeholder: string;
  /** Human description of what this variable is for. Shown in batch UI. */
  description?: string | null;
  /** Must be filled before the prompt can run. Defaults to true. */
  required?: boolean;
}

// 0.8.5 — QP version history snapshot. One row written per
// insert/update via db::quick_prompts::snapshot_quick_prompt_version.
export interface QuickPromptVersion {
  id: string;
  quick_prompt_id: string;
  version_index: number;
  name: string;
  icon: string;
  prompt_template: string;
  variables: PromptVariable[];
  agent: AgentType;
  project_id?: string | null;
  skill_ids?: string[];
  profile_ids?: string[];
  directive_ids?: string[];
  tier?: ModelTier;
  description?: string;
  created_at: string;
}

// 0.8.5 — Per-version aggregated launch metrics. Returned by
// GET /api/quick-prompts/:id/metrics.
export interface QuickPromptVersionMetrics {
  version_index: number;
  launches: number;
  avg_tokens: number;
  avg_duration_ms?: number | null;
  avg_cost_usd?: number | null;
}

export interface QuickPrompt {
  id: string;
  name: string;
  icon: string;
  prompt_template: string;
  variables: PromptVariable[];
  agent: AgentType;
  project_id?: string | null;
  skill_ids?: string[];
  /** 0.8.5 — persona binding pinned at the QP level. */
  profile_ids?: string[];
  /** 0.8.5 — directive binding pinned at the QP level. */
  directive_ids?: string[];
  tier?: ModelTier;
  /** Human description of what this Quick Prompt does. Shown in batch picker. */
  description?: string;
  created_at: string;
  updated_at: string;
}

export interface CreateQuickPromptRequest {
  name: string;
  icon?: string | null;
  prompt_template: string;
  variables?: PromptVariable[];
  agent?: AgentType | null;
  project_id?: string | null;
  skill_ids?: string[];
  /** 0.8.5 — picked in the QP form, persisted as JSON column. */
  profile_ids?: string[];
  /** 0.8.5 — picked in the QP form, persisted as JSON column. */
  directive_ids?: string[];
  tier?: ModelTier;
  description?: string;
}

// ─── Quick APIs (0.6.0) ────────────────────────────────────────────────
// Reusable API call templates with variables. Mirror of QuickPrompt but
// the engine is HTTP, not LLM. Field names follow WorkflowStep ApiCall
// fields verbatim so ApiCallStepCard can edit either shape unchanged.

export interface QuickApi {
  id: string;
  name: string;
  icon: string;
  description?: string;
  project_id?: string | null;
  api_plugin_slug: string;
  api_config_id: string;
  api_endpoint_path: string;
  api_method?: string | null;
  api_query?: Record<string, string> | null;
  api_path_params?: Record<string, string> | null;
  api_headers?: Record<string, string> | null;
  /** JSON body — shape walked at runtime, string leaves are templated. */
  api_body?: unknown | null;
  api_extract?: ExtractSpec | null;
  api_pagination?: PaginationSpec | null;
  api_timeout_ms?: number | null;
  api_max_retries?: number | null;
  variables: PromptVariable[];
  /** 0.8.5 — bindings propagate to downstream LLM consumers (chained QP, compare). */
  profile_ids?: string[];
  /** 0.8.5 — bindings propagate to downstream LLM consumers (chained QP, compare). */
  directive_ids?: string[];
  created_at: string;
  updated_at: string;
}

export interface CreateQuickApiRequest {
  name: string;
  icon?: string | null;
  description?: string;
  project_id?: string | null;
  api_plugin_slug: string;
  api_config_id: string;
  api_endpoint_path: string;
  api_method?: string | null;
  api_query?: Record<string, string> | null;
  api_path_params?: Record<string, string> | null;
  api_headers?: Record<string, string> | null;
  api_body?: unknown | null;
  api_extract?: ExtractSpec | null;
  api_pagination?: PaginationSpec | null;
  api_timeout_ms?: number | null;
  api_max_retries?: number | null;
  variables?: PromptVariable[];
  /** 0.8.5 — same field as QuickPrompt; ignored by HTTP path, used by downstream LLM. */
  profile_ids?: string[];
  /** 0.8.5 — same field as QuickPrompt; ignored by HTTP path, used by downstream LLM. */
  directive_ids?: string[];
}

export interface QuickApiExportEnvelope {
  kind: string;
  version: number;
  exported_at: string;
  quick_api: QuickApi;
}

export interface ImportQuickApiRequest {
  content: string;
  project_id?: string | null;
}

export interface RunQuickApiRequest {
  variables?: Record<string, string>;
}

export interface RunQuickApiResponse {
  success: boolean;
  duration_ms: number;
  envelope?: unknown | null;
  error?: string | null;
}

export interface BatchRunQuickApiRequest {
  /** JSON array of strings (one per QA first variable) OR objects (keys map to variables). */
  items: unknown;
  concurrent_limit?: number | null;
}

export interface BatchRunQuickApiResponse {
  /** OK | PARTIAL | ERROR */
  status: string;
  duration_ms: number;
  envelope?: unknown | null;
  error?: string | null;
}
