/** * Single-process composition root — wires every concrete implementation once. * Mirrors the `run_single_process` composition in the Rust gateway/TUI. * * Assembles: Store → Settings/AppConfig repos → LLM provider → ToolCtx + * executor → AgentTurnServiceImpl. */ import { newStore, ensureStoreDirs, type AgentTurnParams, type TurnEvent, type TurnEventSink, type ChatMessage, type Store, userMessage, resolveEffectiveModel, } from "@zesdex/domain"; import { JsonSettingsRepository, JsonAppConfigRepository, LlmClient, resolveApiKey, InfrastructureToolExecutor, allTools, toolDefs, } from "@zesdex/infrastructure"; import type { ToolCtx } from "@zesdex/infrastructure"; import { AgentTurnServiceImpl } from "@zesdex/application"; /** Wired runtime for single-process mode. */ export interface WiredRuntime { store: Store; executor: InfrastructureToolExecutor; turnService: AgentTurnServiceImpl; apiKey: string; model: string; apiBase?: string; } /** * Wire all layers together once. * Returns the assembled runtime that both the REPL loop and future * interface servers share. */ export async function runSingleProcess(): Promise { const store = newStore(); await ensureStoreDirs(store); // Load settings + app config from the store directory (repos are async). const settingsRepo = new JsonSettingsRepository(); const appConfigRepo = new JsonAppConfigRepository(); const settings = await settingsRepo.load(store.base_dir); const appConfig = await appConfigRepo.load(store.base_dir); // Resolve the effective provider / model / base URL / API key. const provider = settings.provider; const model = resolveEffectiveModel(settings, appConfig); const baseUrl = appConfig.providers[provider]?.api_base ?? undefined; const apiKey = resolveApiKey(settings, appConfig); // Build the LLM provider service (implements the ProviderService port). const llmClient = new LlmClient(apiKey, model, baseUrl); // Build the tool context + executor. const executor = new InfrastructureToolExecutor(buildToolCtx(store)); // Assemble the turn service (provider + executor + tool definitions). const turnService = new AgentTurnServiceImpl( llmClient, executor, toolDefs(allTools()) as never, ); return { store, executor, turnService, apiKey, model, apiBase: baseUrl }; } /** Rooted, long-lived event sink that collects events into an array. */ export class ArrayTurnEventSink implements TurnEventSink { constructor(public events: TurnEvent[] = []) {} push(event: TurnEvent): void { this.events.push(event); } drain(): TurnEvent[] { const out = this.events; this.events = []; return out; } } /** Build a fresh set of turn params for a single user query. */ export function buildTurnParams( runtime: WiredRuntime, userText: string, events: TurnEvent[], abort: AbortController, ): AgentTurnParams { const sink = new ArrayTurnEventSink(events); const messages: ChatMessage[] = [userMessage(userText)]; return { messages, session_dir: runtime.store.base_dir, workspace_roots: [process.cwd()], turn_events: sink, in_flight: { value: false }, abort, api_key: runtime.apiKey, model: runtime.model, api_base: runtime.apiBase, }; } /** Build a ToolCtx with the standard fields. */ function buildToolCtx(store: Store): ToolCtx { return { sessionDir: store.base_dir, workspaces: [process.cwd()], turnEvents: new ArrayTurnEventSink(), workflowFindings: [], } as unknown as ToolCtx; }