chore: initial commit
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
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 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<T> = {status: 'success'; data: T} | {
|
||||
status: 'error'; data: {code: ForwardErrorCode; message: string; retryable: boolean};
|
||||
};
|
||||
export type AckCallback<T = unknown> = (result: ForwardAck<T>) => 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 | '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 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<Pick<LogSegment, 'message' | 'font_color' | 'font_style'>>};
|
||||
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<string, JsonValue>};
|
||||
'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<string, JsonValue>};
|
||||
'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<string, JsonValue>;
|
||||
trigger_available?: boolean;
|
||||
branches?: Array<{
|
||||
trigger: Record<string, JsonValue>;
|
||||
vars: Record<string, JsonValue>;
|
||||
outputs: Record<string, {status: 'success' | 'failure'; output: JsonValue; execution: number;
|
||||
error?: NonNullable<WorkflowRunSnapshot['error']>}>;
|
||||
last_result: {status: 'success' | 'failure'; output: JsonValue; node_id: string;
|
||||
error?: NonNullable<WorkflowRunSnapshot['error']>} | null;
|
||||
inputs: Record<string, JsonValue>;
|
||||
retry_counts: Record<string, number>;
|
||||
}>;
|
||||
}
|
||||
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<string, ContextInfoI18n>;
|
||||
values?: Record<string, ContextInfoI18n>;
|
||||
}
|
||||
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 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<RelayLimits>;
|
||||
}
|
||||
Reference in New Issue
Block a user