feat: add gateway TUI
This commit is contained in:
161
src/tui/gateway-chat.ts
Normal file
161
src/tui/gateway-chat.ts
Normal 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 };
|
||||
}
|
||||
Reference in New Issue
Block a user