mono/packages/kbot/cpp/orchestrator/presets.js
2026-03-30 12:07:13 +02:00

187 lines
6.0 KiB
JavaScript

/**
* 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 <arg>`. */
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`
);
}