Files
kandev/apps/web/lib/api/domains/user-shell-api.ts
T
2026-04-25 18:40:31 +01:00

113 lines
3.1 KiB
TypeScript

import { getWebSocketClient } from "@/lib/ws/connection";
import { getBackendConfig } from "@/lib/config";
/**
* Terminal info returned by HTTP API (for SSR).
*/
export type TerminalInfo = {
terminal_id: string;
process_id: string;
running: boolean;
label: string;
closable: boolean;
initial_command?: string;
};
/**
* Fetch terminals for a session via HTTP (for SSR).
* This endpoint auto-creates the first "Terminal" if none exist.
*/
export async function fetchTerminals(sessionId: string): Promise<TerminalInfo[]> {
const { apiBaseUrl } = getBackendConfig();
const url = `${apiBaseUrl}/api/v1/sessions/${sessionId}/terminals`;
try {
const response = await fetch(url);
if (!response.ok) {
console.warn("Failed to fetch terminals:", response.status);
return [];
}
const data = await response.json();
return data.terminals ?? [];
} catch (error) {
console.warn("Failed to fetch terminals:", error);
return [];
}
}
/**
* Result of creating a new user shell.
*/
export type CreateUserShellResult = {
terminalId: string;
label: string;
closable: boolean;
initialCommand?: string;
};
/**
* Options for {@link createUserShell}. Pass `scriptId` to run a stored
* RepositoryScript, or `command` (with optional `label`) to run an arbitrary
* command — used for the repository's dev_script. Omit both for a plain shell.
*/
export type CreateUserShellOptions = {
scriptId?: string;
command?: string;
label?: string;
};
/**
* Create a new user shell terminal.
* Backend assigns the terminal ID, label, and closable status.
* First terminal is "Terminal" and not closable, subsequent are "Terminal 2", etc. and closable.
*/
export async function createUserShell(
sessionId: string,
options?: CreateUserShellOptions,
): Promise<CreateUserShellResult> {
const client = getWebSocketClient();
if (!client) {
throw new Error("WebSocket client not available");
}
const payload: Record<string, string> = { session_id: sessionId };
if (options?.scriptId) payload.script_id = options.scriptId;
if (options?.command) payload.command = options.command;
if (options?.label) payload.label = options.label;
const response = (await client.request("user_shell.create", payload)) as {
terminal_id: string;
label: string;
closable: boolean;
initial_command?: string;
};
return {
terminalId: response.terminal_id,
label: response.label,
closable: response.closable,
initialCommand: response.initial_command,
};
}
/**
* Stop a user shell terminal process.
* This is called when closing a terminal tab to clean up the backend process.
*/
export async function stopUserShell(sessionId: string, terminalId: string): Promise<void> {
const client = getWebSocketClient();
if (!client) {
// WebSocket not available, silently fail (best-effort cleanup)
return;
}
try {
await client.request("user_shell.stop", {
session_id: sessionId,
terminal_id: terminalId,
});
} catch (error) {
// Log but don't throw - this is best-effort cleanup
console.warn("Failed to stop user shell:", error);
}
}