122 lines
3.5 KiB
TypeScript
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;
|
|
}
|