Files
zesdex/apps/interfaces/cli/src/compose.ts
T

122 lines
3.5 KiB
TypeScript

/**
* 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<WiredRuntime> {
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;
}