/** * orchestrator/presets.js — defaults for IPC integration tests (extend here as suites grow). * * Llama local runner (llama-basics.test.ts): OpenAI-compatible API at http://localhost:8888/v1, * router `ollama` + `base_url` override, model `default` (server picks loaded GGUF). */ import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { spawn } from 'node:child_process'; import { existsSync } from 'node:fs'; import { probeTcpPort } from './test-commons.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); export const platform = { isWin: process.platform === 'win32', }; /** kbot/cpp root (parent of orchestrator/). */ export const paths = { orchestratorDir: __dirname, cppRoot: resolve(__dirname, '..'), /** Same as packages/kbot/cpp/scripts/run-7b.sh — llama-server on :8888 */ run7bScript: resolve(__dirname, '../scripts/run-7b.sh'), }; /** Dist binary name for the current OS. */ export function exeName() { return platform.isWin ? 'kbot.exe' : 'kbot'; } /** Absolute path to kbot binary given orchestrator/ directory (where test-ipc.mjs lives). */ export function distExePath(orchestratorDir) { return resolve(orchestratorDir, '..', 'dist', exeName()); } /** UDS / TCP listen argument passed to `kbot worker --uds `. */ export const uds = { tcpPort: 4001, unixPath: '/tmp/kbot-test-ipc.sock', /** Value for `--uds` on this OS (Windows: port string; Unix: socket path). */ workerArg() { return platform.isWin ? String(this.tcpPort) : this.unixPath; }, /** Options for `net.connect` to reach the worker. */ connectOpts(cppUdsArg) { return platform.isWin ? { port: this.tcpPort, host: '127.0.0.1' } : cppUdsArg; }, }; /** Millisecond timeouts — tune per step in new tests. */ export const timeouts = { ipcDefault: 5000, kbotAi: 180_000, /** Llama local arithmetic (same order of magnitude as kbot-ai). */ llamaKbotAi: 180_000, /** Max wait for :8888 after spawning run-7b.sh (model load can be slow). */ llamaServerStart: Number(process.env.KBOT_LLAMA_START_TIMEOUT_MS || 600_000), connectAttempts: 15, connectRetryMs: 400, postShutdownMs: 200, }; export const router = { default: 'openrouter', fromEnv() { return process.env.KBOT_ROUTER || this.default; }, }; /** * Local llama.cpp HTTP server — mirrors tests/unit/llama-basics.test.ts (LLAMA_OPTS). * Uses router `ollama` so api_key resolves to dummy `ollama`; `base_url` points at :8888/v1. */ export const llama = { get port() { return Number(process.env.KBOT_LLAMA_PORT || 8888); }, get host() { return process.env.KBOT_LLAMA_HOST || '127.0.0.1'; }, get baseURL() { return process.env.KBOT_LLAMA_BASE_URL || `http://localhost:${this.port}/v1`; }, router: 'ollama', get model() { return process.env.KBOT_LLAMA_MODEL || 'default'; }, prompts: { /** Same idea as llama-basics completion tests. */ add5_3: 'What is 5 + 3? Reply with just the number, nothing else.', }, }; /** * IPC payload for kbot-ai → local llama-server (OpenAI-compatible). * Pass `base_url` so LLMClient uses port 8888 instead of default ollama :11434. */ export function kbotAiPayloadLlamaLocal(overrides = {}) { const merged = { prompt: llama.prompts.add5_3, router: llama.router, model: llama.model, base_url: llama.baseURL, ...overrides, }; merged.base_url = merged.base_url ?? merged.baseURL ?? llama.baseURL; delete merged.baseURL; return merged; } /** Stock prompts and assertions helpers for LLM smoke tests. */ export const prompts = { germanyCapital: 'What is the capital of Germany? Reply in one short sentence.', }; /** Build `kbot-ai` IPC payload from env + presets (OpenRouter-friendly defaults). */ export function kbotAiPayloadFromEnv() { const payload = { prompt: process.env.KBOT_IPC_PROMPT || prompts.germanyCapital, router: router.fromEnv(), }; if (process.env.KBOT_IPC_MODEL) { payload.model = process.env.KBOT_IPC_MODEL; } return payload; } /** True when using the default Germany prompt (for optional Berlin assertion). */ export function usingDefaultGermanyPrompt() { return !process.env.KBOT_IPC_PROMPT; } /** * If nothing listens on llama.port, optionally spawn `scripts/run-7b.sh` (requires `sh` on PATH, e.g. Git Bash on Windows). * * @param {{ autostart?: boolean, startTimeoutMs?: number }} [opts] * @returns {Promise<{ ok: boolean, alreadyRunning: boolean, started?: boolean, pid?: number }>} */ export async function ensureLlamaLocalServer(opts = {}) { const autostart = opts.autostart ?? true; const startTimeoutMs = opts.startTimeoutMs ?? timeouts.llamaServerStart; const host = llama.host; const port = llama.port; const scriptPath = paths.run7bScript; if (await probeTcpPort(host, port, 1500)) { return { ok: true, alreadyRunning: true }; } if (!autostart) { throw new Error( `[llama] Nothing listening on ${host}:${port}. Start the server (e.g. sh scripts/run-7b.sh), or remove KBOT_IPC_LLAMA_AUTOSTART=0 to allow autostart` ); } if (!existsSync(scriptPath)) { throw new Error(`[llama] Script missing: ${scriptPath}`); } console.log(`[llama] Port ${port} closed — starting ${scriptPath} (timeout ${startTimeoutMs}ms) …`); const child = spawn('sh', [scriptPath], { detached: true, stdio: 'ignore', cwd: dirname(scriptPath), env: { ...process.env }, }); child.unref(); const deadline = Date.now() + startTimeoutMs; while (Date.now() < deadline) { if (await probeTcpPort(host, port, 1500)) { return { ok: true, alreadyRunning: false, started: true, pid: child.pid }; } await new Promise((r) => setTimeout(r, 1500)); } throw new Error( `[llama] Server did not open ${host}:${port} within ${startTimeoutMs}ms — check llama-server / GPU / model path` ); }