import type {Server as HttpServer} from 'node:http'; // Token permissions are capabilities, not the complete RPC method list. export const RELAY_METHODS = ['bot.snapshot', 'bot.logs', 'bot.subscribe', 'bot.unsubscribe', 'bot.chat', 'execute_function', 'bot.files', 'workflow.control'] as const; export const WORKFLOW_ACTIONS = ['run_once', 'trigger_start', 'trigger_stop', 'stop', 'stop_run', 'reload'] as const; export const WORKFLOW_METHODS = ['workflow.start', 'workflow.stop', 'workflow.snapshot', 'workflow.catalog', 'workflow.control', 'workflow.command', 'workflow.runtime'] as const; export const WORLD_METHODS = ['world.snapshot', 'world.chunk', 'world.unwatch'] as const; export const BOT_EVENTS = ['status_update', 'log', 'message', 'bot_error'] as const; export type RelayMethod = typeof RELAY_METHODS[number]; export type BotEventName = typeof BOT_EVENTS[number]; export type BotStatus = 'offline' | 'ready' | 'connecting' | 'online' | 'disconnecting' | 'reconnecting'; export type ForwardErrorCode = 'INVALID_REQUEST' | 'UNAUTHORIZED' | 'FORBIDDEN' | 'NODE_OFFLINE' | 'RELAY_UNAVAILABLE' | 'SESSION_STALE' | 'TOKEN_EXPIRED' | 'TIMEOUT' | 'CANCELLED' | 'NO_CONTEXT' | 'BOT_NOT_ONLINE' | 'RATE_LIMITED' | 'RESYNC_REQUIRED' | 'INTERNAL_ERROR'; export type ForwardAck = {status: 'success'; data: T} | { status: 'error'; data: {code: ForwardErrorCode; message: string; retryable: boolean}; }; export type AckCallback = (result: ForwardAck) => void; export interface SessionScope { forward_id: number; forward_session_id: string; node_session_id: string; node_id: number; bot_id: number; } export interface RelayClaims extends SessionScope { v: 1; role: 'node' | 'web'; user_id?: number; grant_id?: string; permissions: RelayMethod[]; issued_at: number; join_before: number; expires_at: number; jti: string; } export interface JoinRoomReady extends SessionScope { role: 'node' | 'web'; node_ready: boolean; expires_at: number; } export interface NodeReadyRequest {forward_session_id: string} export interface NodeReadyResult {node_ready: true} export interface SessionClosed { forward_session_id?: string; reason: string; code: ForwardErrorCode; } export interface RelayRequest { request_id: string; method: RelayMethod | keyof WorkflowPayloads | keyof WorldPayloads | 'get_log_history' | 'tab_complete' | 'get_context_info' | 'get_data_file_list' | 'get_file_data'; payload: unknown; } export interface NodeRelayRequest extends RelayRequest { correlation_id: string; forward_session_id: string; deadline_at: number; } export interface WorldChunkRevision {x: number; z: number; revision: string} export interface WorldChunk extends WorldChunkRevision { palette: Array<{name: string; color: number}>; // Canonical Base64 UInt16LE[256]; heights/blocks retain the top visible surface. heights: string; blocks: string; // Emitted together for chunks with fluids; omitted by legacy snapshots. terrain?: {heights: string; blocks: string}; fluids?: {heights: string; blocks: string; levels: string}; spikes?: {heights: string; blocks: string; segments: Array<{ index: number; height: number; direction: 'up' | 'down'; thickness: 'tip' | 'tip_merge' | 'frustum' | 'middle' | 'base'; }>}; features?: {heights: string; blocks: string; entries: Array<{ index: number; height: number; block: number; properties: Record; }>}; } export interface WorldPayloads { 'world.snapshot': {radius: number; known: WorldChunkRevision[]; world_generation?: string; watch_id?: string}; 'world.chunk': {x: number; z: number; world_generation: string; watch_id: string}; 'world.unwatch': {watch_id: string}; } export interface WorldVoxelChunk extends WorldChunkRevision { world_generation: string; min_y: number; height: number; palette: Array<{name: string; color: number; properties: Record; occluding: boolean}>; // Sparse sections ordered by y relative to min_y; blocks are canonical Base64 UInt16LE[4096]. sections: Array<{y: number; blocks: string}>; } export interface WorldSnapshot { world_generation: string; context_generation: string; stream_id: string; watch_id: string; version: string; dimension: string; min_y: number; height: number; center: {x: number; z: number}; position: {x: number; y: number; z: number; yaw: number}; chunks: WorldChunk[]; loaded: WorldChunkRevision[]; partial: boolean; } export interface BotSnapshot { bot_id: number; bot_status: BotStatus; context_exists: boolean; context_generation: string | null; stream_id: string; seq: number; captured_at: number; } export interface LogSegment { message: string; font_color: string; font_style: Array<'normal' | 'italic' | 'bold' | 'light' | 'underline' | 'url'>; hoverEvent?: {action: 'show_text' | 'show_item' | 'show_entity'; value: string; segments?: Array>}; clickEvent?: {action: string; value: string}; insertion?: string; } export interface BotLogRecord { id: string; timestamp: number; context_generation: string | null; seq: number; segments: LogSegment[]; } export interface BotLogsResult { stream_id: string; seq: number; records: BotLogRecord[]; truncated: boolean; } export interface TabCompleteResult { matches: string[]; start: number; length: number; } export type DataFileNode = {type: 'directory'; name: string; children: DataFileNode[]} | {type: 'file'; name: string; id: string; size: number; modified_at: number}; export interface DataFileListResult { listing_id: string; expires_at: number; partial: boolean; children: DataFileNode[]; } export interface FileDataResult { id: string; name: string; encoding: 'base64'; offset: number; next_offset: number; total_bytes: number; eof: boolean; data: string; } export type JsonValue = null | boolean | number | string | JsonValue[] | {[key: string]: JsonValue}; export type WorkflowAction = typeof WORKFLOW_ACTIONS[number]; export interface WorkflowPayloads { 'workflow.start': {workflow_id: string; trigger?: Record}; 'workflow.stop': {run_id: string}; 'workflow.snapshot': {run_id?: string; include_branches?: boolean}; 'workflow.catalog': {}; 'workflow.control': {command_id: string; context_generation: string; action: WorkflowAction; workflow_id?: string; run_id?: string; expected_generation?: number; input?: Record}; 'workflow.command': {command_id: string; context_generation: string}; 'workflow.runtime': {}; } export interface WorkflowStartResult {status: 'started' | 'queued' | 'skipped'; run_id?: string; request_id?: string} export interface WorkflowRunSnapshot { run_id: string; workflow_id: string; status: 'running' | 'stopping' | 'completed' | 'failed' | 'cancelled' | 'timed_out'; steps: number; active_nodes: string[]; started_at: number; ended_at: number | null; stop_reason: 'manual' | 'disconnect' | 'timeout' | 'disposed' | 'updated' | 'reloaded' | null; error: {code: string; message: string; node_id: string} | null; // Summary responses omit trigger and branches; legacy responses may omit the marker. trigger?: Record; trigger_available?: boolean; branches?: Array<{ trigger: Record; vars: Record; outputs: Record}>; last_result: {status: 'success' | 'failure'; output: JsonValue; node_id: string; error?: NonNullable} | null; inputs: Record; retry_counts: Record; }>; } export interface WorkflowCommand { command_id: string; action: WorkflowAction; workflow_id: string | null; state: 'accepted' | 'completed' | 'failed'; result?: WorkflowStartResult | {stopped: true} | {generation: number}; error?: {code: string; message: string}; } export interface WorkflowRuntimeState { workflow_id: string; generation: number; enabled: boolean; start_type: 'common' | 'event' | 'timer'; trigger_state: 'stopped' | 'listening' | 'scheduled' | 'exhausted' | 'reloading' | 'error'; accepted_count: number; max_runs: number | null; next_fire_at: number | null; active_run_ids: string[]; queued_count: number; last_error: {code: string; message: string} | null; } export interface WorkflowRuntimeSnapshot { source_config_revision: number; workflows: WorkflowRuntimeState[]; commands: WorkflowCommand[]; } export interface ContextInfoI18n { lang: string; name: string; description: string; fields?: Record; values?: Record; } export interface ContextInfo { name: string; i18n: ContextInfoI18n; value: JsonValue; } export interface InfoUpdate { forward_session_id: string; bot_id: number; context_generation: string; stream_id: string; seq: number; emitted_at: number; data: {name: string; value: JsonValue}; } export interface BotEventPayloads { status_update: {status: BotStatus; context_exists: boolean}; log: {records: BotLogRecord[]}; message: {text: string; position: string; json?: unknown}; bot_error: {code?: string; message: string}; } export type BotEvent = {[K in BotEventName]: { forward_session_id: string; bot_id: number; context_generation: string | null; stream_id: string; seq: number; emitted_at: number; name: K; payload: BotEventPayloads[K]; }}[BotEventName]; export interface RelayEventPayloads { bot_event: Extract; bot_log: Extract; info_update: InfoUpdate; } export interface OpenSessionCommand extends SessionScope {lease_expires_at: number} export interface AuthorizeGrantCommand { forward_session_id: string; grant_id: string; user_id: number; permissions: RelayMethod[]; expires_at: number; } export interface RevokeSessionCommand {forward_session_id: string; reason: string} export interface RevokeGrantCommand extends RevokeSessionCommand {grant_id: string} export interface RelayStatus { forward_session_id: string; node_ready: boolean; reason: string; } export interface ForwardSessionStatus extends RelayStatus, SessionScope { state: 'open' | 'ready' | 'unavailable' | 'closed'; reported_at: number; } export const CONTROL_EVENTS = ['forward_open_session', 'forward_authorize_grant', 'forward_revoke_session', 'forward_revoke_grant'] as const; export type ControlEvent = typeof CONTROL_EVENTS[number]; export interface RelayLimits { auth_timeout_ms: number; close_timeout_ms: number; rpc_timeout_ms: number; unused_timeout_ms: number; idle_timeout_ms: number; max_token_age_ms: number; join_window_ms: number; clock_skew_ms: number; max_lease_ms: number; max_token_bytes: number; max_payload_bytes: number; max_chat_length: number; max_connections: number; max_sessions: number; max_grants_per_session: number; max_pending_per_web: number; max_pending_total: number; requests_per_second: number; events_per_second: number; max_dedupe_entries: number; dedupe_ttl_ms: number; max_buffered_packets: number; } export interface RelayLogger {log(input: unknown): void; error(message: unknown): unknown} export interface ControlTransport { on(event: string, listener: (...args: any[]) => void): unknown; off(event: string, listener: (...args: any[]) => void): unknown; emit(event: string, ...args: any[]): unknown; } export interface ForwardServerOptions { forward_id: number; key: string; http_server?: HttpServer; port?: number; host?: string; path?: string; namespace?: string; allowed_origins: string[]; allow_no_origin?: boolean; control?: ControlTransport; logger?: RelayLogger; limits?: Partial; }