feat: add gateway TUI

This commit is contained in:
Peter Steinberger
2026-01-03 04:46:04 +01:00
parent 928631309e
commit 08ce608ae7
10 changed files with 743 additions and 0 deletions

161
src/tui/gateway-chat.ts Normal file
View File

@@ -0,0 +1,161 @@
import { randomUUID } from "node:crypto";
import { loadConfig } from "../config/config.js";
import { GatewayClient } from "../gateway/client.js";
import { PROTOCOL_VERSION } from "../gateway/protocol/index.js";
import { VERSION } from "../version.js";
export type GatewayConnectionOptions = {
url?: string;
token?: string;
password?: string;
};
export type ChatSendOptions = {
sessionKey: string;
message: string;
thinking?: string;
deliver?: boolean;
timeoutMs?: number;
};
export type GatewayEvent = {
event: string;
payload?: unknown;
};
export class GatewayChatClient {
private client: GatewayClient;
private readyPromise: Promise<void>;
private resolveReady?: () => void;
readonly connection: { url: string; token?: string; password?: string };
onEvent?: (evt: GatewayEvent) => void;
onConnected?: () => void;
onDisconnected?: (reason: string) => void;
onGap?: (info: { expected: number; received: number }) => void;
constructor(opts: GatewayConnectionOptions) {
const resolved = resolveGatewayConnection(opts);
this.connection = resolved;
this.readyPromise = new Promise((resolve) => {
this.resolveReady = resolve;
});
this.client = new GatewayClient({
url: resolved.url,
token: resolved.token,
password: resolved.password,
clientName: "clawdis-tui",
clientVersion: VERSION,
platform: process.platform,
mode: "tui",
instanceId: randomUUID(),
minProtocol: PROTOCOL_VERSION,
maxProtocol: PROTOCOL_VERSION,
onHelloOk: () => {
this.resolveReady?.();
this.onConnected?.();
},
onEvent: (evt) => {
this.onEvent?.({ event: evt.event, payload: evt.payload });
},
onClose: (_code, reason) => {
this.onDisconnected?.(reason);
},
onGap: (info) => {
this.onGap?.(info);
},
});
}
start() {
this.client.start();
}
stop() {
this.client.stop();
}
async waitForReady() {
await this.readyPromise;
}
async sendChat(opts: ChatSendOptions): Promise<{ runId: string }>
{
const runId = randomUUID();
await this.client.request("chat.send", {
sessionKey: opts.sessionKey,
message: opts.message,
thinking: opts.thinking,
deliver: opts.deliver,
timeoutMs: opts.timeoutMs,
idempotencyKey: runId,
});
return { runId };
}
async abortChat(opts: { sessionKey: string; runId: string }) {
return await this.client.request<{ ok: boolean; aborted: boolean }>(
"chat.abort",
{
sessionKey: opts.sessionKey,
runId: opts.runId,
},
);
}
async loadHistory(opts: { sessionKey: string; limit?: number }) {
return await this.client.request("chat.history", {
sessionKey: opts.sessionKey,
limit: opts.limit,
});
}
async listSessions(opts?: { limit?: number; activeMinutes?: number }) {
return await this.client.request("sessions.list", {
limit: opts?.limit,
activeMinutes: opts?.activeMinutes,
});
}
}
export function resolveGatewayConnection(opts: GatewayConnectionOptions) {
const config = loadConfig();
const isRemoteMode = config.gateway?.mode === "remote";
const remote = isRemoteMode ? config.gateway?.remote : undefined;
const authToken = config.gateway?.auth?.token;
const url =
(typeof opts.url === "string" && opts.url.trim().length > 0
? opts.url.trim()
: undefined) ||
(typeof remote?.url === "string" && remote.url.trim().length > 0
? remote.url.trim()
: undefined) ||
"ws://127.0.0.1:18789";
const token =
(typeof opts.token === "string" && opts.token.trim().length > 0
? opts.token.trim()
: undefined) ||
(isRemoteMode
? typeof remote?.token === "string" && remote.token.trim().length > 0
? remote.token.trim()
: undefined
: process.env.CLAWDIS_GATEWAY_TOKEN?.trim() ||
(typeof authToken === "string" && authToken.trim().length > 0
? authToken.trim()
: undefined));
const password =
(typeof opts.password === "string" && opts.password.trim().length > 0
? opts.password.trim()
: undefined) ||
process.env.CLAWDIS_GATEWAY_PASSWORD?.trim() ||
(typeof remote?.password === "string" && remote.password.trim().length > 0
? remote.password.trim()
: undefined);
return { url, token, password };
}