refactor(server): extract SpeechService and defer init until after listen

Speech runtime was leaking 8+ individual resolvers into bootstrap and
blocking server startup for ~3s with synchronous Sherpa native model
loading. Refactor into a self-contained SpeechService that owns its
full lifecycle (init, download, monitor, cleanup) and defer start()
until after httpServer.listen() so native loading doesn't block
accepting connections. Server startup drops from ~3.2s to ~0.5s.

Also removes unused client-triggered speech model download/list path
(CLI commands, session handlers, message types) — the service manages
its own models.
This commit is contained in:
Mohamed Boudra
2026-03-30 14:07:55 +07:00
parent db39417be9
commit 7504d20b67
10 changed files with 100 additions and 444 deletions
@@ -39,8 +39,6 @@ import type {
ListCommandsResponse,
ListProviderModelsResponseMessage,
ListAvailableProvidersResponse,
SpeechModelsListResponse,
SpeechModelsDownloadResponse,
ListTerminalsResponse,
CreateTerminalResponse,
SubscribeTerminalResponse,
@@ -217,8 +215,6 @@ type FileExplorerPayload = FileExplorerResponse["payload"];
type FileDownloadTokenPayload = FileDownloadTokenResponse["payload"];
type ListProviderModelsPayload = ListProviderModelsResponseMessage["payload"];
type ListAvailableProvidersPayload = ListAvailableProvidersResponse["payload"];
type SpeechModelsListPayload = SpeechModelsListResponse["payload"];
type SpeechModelsDownloadPayload = SpeechModelsDownloadResponse["payload"];
type ListCommandsPayload = ListCommandsResponse["payload"];
type ListCommandsDraftConfig = Pick<
AgentSessionConfig,
@@ -2487,32 +2483,6 @@ export class DaemonClient {
});
}
async listSpeechModels(requestId?: string): Promise<SpeechModelsListPayload> {
return this.sendCorrelatedSessionRequest({
requestId,
message: {
type: "speech_models_list_request",
},
responseType: "speech_models_list_response",
timeout: 30000,
});
}
async downloadSpeechModels(options?: {
modelIds?: string[];
requestId?: string;
}): Promise<SpeechModelsDownloadPayload> {
return this.sendCorrelatedSessionRequest({
requestId: options?.requestId,
message: {
type: "speech_models_download_request",
modelIds: options?.modelIds,
},
responseType: "speech_models_download_response",
timeout: 30 * 60 * 1000,
});
}
async listCommands(agentId: string, requestId?: string): Promise<ListCommandsPayload>;
async listCommands(agentId: string, options?: ListCommandsOptions): Promise<ListCommandsPayload>;
async listCommands(