import { z } from "zod/v4";
import { ConfigEtag, DpWorkerRuntimeMode, DpWorkerUpgradeStatus, RuntimeStatus, ShortId, TaskRunStatus, Uuid, } from "./domain.js";
/**
 * WebSocket message envelopes.
 *
 * Every message that crosses the wire has the same outer shape; the inner
 * `type` discriminates the payload. Senders that need a reply set `id`;
 * receivers reply with `ack { ref_id: <that id> }`.
 *
 * See docs/architecture.md §Communication protocols → WebSocket for the
 * authoritative list of message types per channel.
 */
const Envelope = (type, payload) => z.object({
    /** Sender-assigned message id (typically uuid). Required when an ack is expected. */
    id: z.string().min(1).optional(),
    type: z.literal(type),
    /** Unix ms at send time. */
    ts: z.number().int().positive(),
    payload,
});
/* ============================================================================
 * Generic envelope (every channel)
 * ============================================================================ */
export const AckMessage = Envelope("ack", z.object({
    /** id of the message being acknowledged. */
    ref_id: z.string().min(1),
    /** Optional outcome — for "dispatched_ack" style acks the dp-worker reports back its current state. */
    status: z.string().optional(),
    error: z.string().optional(),
}));
export const PingRequestMessage = Envelope("ping_request", z.object({
    ref_id: z.string().min(1),
}));
export const PongMessage = Envelope("pong", z.object({
    ref_id: z.string().min(1),
    status: RuntimeStatus.optional(),
    current_task_id: Uuid.optional(),
    chrome_uptime_ms: z.number().int().nonnegative().optional(),
}));
/* ============================================================================
 * center ↔ dp-worker
 * ============================================================================ */
export const TaskDispatchMessage = Envelope("task_dispatch", z.object({
    task_run_id: Uuid,
    prompt: z.string().min(1),
    randomized_delay_ms: z.number().int().nonnegative().default(0),
    deadline_ms: z.number().int().positive().optional(),
    attachments: z
        .array(z.object({
        r2_object_key: z.string().min(1),
        filename: z.string().min(1),
        mime: z.string().optional(),
    }))
        .optional(),
}));
export const ChatUserMessage = Envelope("chat_user_message", z.object({
    chat_id: Uuid,
    message_id: Uuid,
    content: z.union([
        z.string(),
        z.array(z.object({
            type: z.string(),
            text: z.string().optional(),
            attachment_id: Uuid.optional(),
            filename: z.string().optional(),
            mime: z.string().optional(),
            size_bytes: z.number().int().positive().optional(),
            url: z.string().url().optional(),
        })),
    ]),
}));
export const ConfigInvalidateMessage = Envelope("config_invalidate", z.object({
    which: z.array(z.enum(["llm", "proxy", "persona", "session", "telegram", "social_accounts"])),
    new_config_etag: ConfigEtag,
}));
export const CancelTaskMessage = Envelope("cancel_task", z.object({
    task_run_id: Uuid,
    reason: z.string().optional(),
}));
export const WorkerHelloMessage = Envelope("hello", z.object({
    worker_id: Uuid,
    version: z.string().min(1),
    config_etag: ConfigEtag,
    current_status: RuntimeStatus,
    current_task_id: Uuid.optional(),
}));
export const StatusChangeMessage = Envelope("status_change", z.object({
    status: RuntimeStatus,
    current_task_id: Uuid.optional(),
    chrome_uptime_ms: z.number().int().nonnegative().optional(),
    reason: z.string().optional(),
}));
/** Periodic 30s liveness ping from dp-worker to center. Empty payload —
 *  the envelope's arrival is the signal. */
export const WorkerHeartbeatMessage = Envelope("worker_heartbeat", z.object({}));
export const TaskEventMessage = Envelope("task_event", z.object({
    task_run_id: Uuid,
    event: z.enum(["dispatched_ack", "started", "step_log", "finished"]),
    status: TaskRunStatus.optional(),
    error: z.string().optional(),
    result_summary: z.string().optional(),
    step_index: z.number().int().nonnegative().optional(),
    step_log: z.string().optional(),
}));
export const ChatMessageChunkMessage = Envelope("chat_message_chunk", z.object({
    chat_id: Uuid,
    message_id: Uuid,
    delta: z.string(),
    done: z.boolean(),
}));
export const ChatMessageFinalizedMessage = Envelope("chat_message_finalized", z.object({
    chat_id: Uuid,
    message_id: Uuid,
    role: z.string(),
    content: z.string(),
    tool_traces: z.array(z.record(z.string(), z.unknown())).optional(),
}));
/** Returned by dp-worker when a chat arrives while BrowserLock is held by a task. */
export const ChatMessageQueuedMessage = Envelope("chat_message_queued", z.object({
    chat_id: Uuid,
    message_id: Uuid,
    queue_position: z.number().int().nonnegative(),
}));
export const AttachmentUploadRequestMessage = Envelope("attachment_upload", z.object({
    request_id: ShortId,
    filename: z.string().min(1),
    mime: z.string().optional(),
    size_bytes: z.number().int().positive(),
}));
export const ErrorReportMessage = Envelope("error_report", z.object({
    code: z.string().min(1),
    message: z.string().min(1),
    stack: z.string().optional(),
}));
/** Union of every message that may travel on a center↔dp-worker WebSocket. */
export const CenterWorkerWsMessage = z.discriminatedUnion("type", [
    AckMessage,
    PingRequestMessage,
    PongMessage,
    // center → dp-worker
    TaskDispatchMessage,
    ChatUserMessage,
    ConfigInvalidateMessage,
    CancelTaskMessage,
    // dp-worker → center
    WorkerHelloMessage,
    StatusChangeMessage,
    WorkerHeartbeatMessage,
    TaskEventMessage,
    ChatMessageChunkMessage,
    ChatMessageFinalizedMessage,
    ChatMessageQueuedMessage,
    AttachmentUploadRequestMessage,
    ErrorReportMessage,
]);
/* ============================================================================
 * center ↔ dp-director
 * ============================================================================ */
export const SpawnWorkerMessage = Envelope("spawn_worker", z.object({
    worker_id: Uuid,
    api_key: z.string().min(1),
    center_url: z.string().url(),
    version: z.string().optional(),
    workspace_path: z.string().optional(),
}));
export const StopWorkerMessage = Envelope("stop_worker", z.object({
    worker_id: Uuid,
    graceful: z.boolean().default(true),
}));
export const RestartWorkerMessage = Envelope("restart_worker", z.object({
    worker_id: Uuid,
}));
export const UpgradeDpWorkerMessage = Envelope("upgrade_dp_worker", z.object({
    request_id: z.string().min(1),
}));
export const QueryWorkerStatusMessage = Envelope("query_worker_status", z.object({
    worker_ids: z.array(Uuid).min(1),
}));
export const ReconcileWorkersMessage = Envelope("reconcile_workers", z.object({
    generation: z.string().min(1),
    workers: z.array(z.object({
        worker_id: Uuid,
        api_key: z.string().min(1),
        center_url: z.string().url(),
        workspace_path: z.string().optional(),
    })),
    stop_unlisted: z.boolean().default(true),
}));
export const DirectorHelloMessage = Envelope("hello", z.object({
    director_id: Uuid,
    host_label: z.string().min(1).optional(),
    version: z.string().min(1),
    worker_pids: z.record(Uuid, z.number().int().positive()),
    capabilities: z.array(z.string()).default([]),
    dp_worker_runtime_mode: DpWorkerRuntimeMode.optional(),
    dp_worker_version: z.string().min(1).optional(),
    dp_worker_latest_version: z.string().min(1).optional(),
    dp_worker_upgrade_status: DpWorkerUpgradeStatus.optional(),
}));
/**
 * Sent by DirectorConnection DO to the dp-director immediately after the WS upgrade so
 * the dp-director learns its own canonical director_id (the DO resolved it from the
 * api_key during the upgrade and knows the authoritative value). The dp-director
 * waits for this before sending `hello`.
 */
export const WelcomeMessage = Envelope("welcome", z.object({
    director_id: Uuid,
}));
export const WorkerStateChangeMessage = Envelope("worker_state_change", z.object({
    worker_id: Uuid,
    state: z.enum(["running", "stopped", "crashed", "crashed_loop"]),
    pid: z.number().int().positive().optional(),
    exit_code: z.number().int().optional(),
}));
export const DirectorHeartbeatMessage = Envelope("director_heartbeat", z.object({
    running_workers: z.array(Uuid),
    cpu: z.number().nonnegative().optional(),
    mem: z.number().nonnegative().optional(),
}));
export const DpWorkerUpgradeStatusMessage = Envelope("dp_worker_upgrade_status", z.object({
    request_id: z.string().min(1),
    status: DpWorkerUpgradeStatus,
    runtime_mode: DpWorkerRuntimeMode,
    current_version: z.string().min(1).optional(),
    latest_version: z.string().min(1).optional(),
    message: z.string().optional(),
}));
/** Union of every message that may travel on a center↔dp-director WebSocket. */
export const CenterDirectorWsMessage = z.discriminatedUnion("type", [
    AckMessage,
    PingRequestMessage,
    PongMessage,
    // center → dp-director
    SpawnWorkerMessage,
    StopWorkerMessage,
    RestartWorkerMessage,
    UpgradeDpWorkerMessage,
    QueryWorkerStatusMessage,
    ReconcileWorkersMessage,
    WelcomeMessage,
    // dp-director → center
    DirectorHelloMessage,
    WorkerStateChangeMessage,
    DirectorHeartbeatMessage,
    DpWorkerUpgradeStatusMessage,
]);
/* ============================================================================
 * Web UI ↔ ChatBroadcast DO
 * ============================================================================ */
/** UI sends to subscribe to a chat (auth is via JWT in connect URL). */
export const ChatSubscribeMessage = Envelope("subscribe", z.object({
    chat_id: Uuid,
    since_message_id: Uuid.optional(),
}));
/** ChatBroadcast pushes the same chunks dp-worker emits, plus user-side echoes. */
export const ChatBroadcastChunkMessage = ChatMessageChunkMessage;
export const ChatBroadcastFinalizedMessage = ChatMessageFinalizedMessage;
export const CenterWebChatWsMessage = z.discriminatedUnion("type", [
    AckMessage,
    ChatSubscribeMessage,
    ChatBroadcastChunkMessage,
    ChatBroadcastFinalizedMessage,
]);
//# sourceMappingURL=ws-protocol.js.map