my harness for niri
1import path from "path"
2
3/** Docker container name used for command execution. */
4export const CONTAINER_NAME = (process.env.NIRI_CONTAINER ?? "niri").trim() || "niri"
5/** Linux user inside the container used for command execution. */
6export const CONTAINER_USER = (process.env.NIRI_USER ?? "niri").trim() || "niri"
7
8/** Absolute image root allowed for `image_tool` operations. */
9export const IMAGE_ROOT = (() => {
10 const configured = (process.env.IMAGE_ROOT ?? "").trim()
11 const root = configured || `/home/${CONTAINER_USER}/images`
12 if (!root.startsWith("/")) {
13 throw new Error(`IMAGE_ROOT must be an absolute path, got: ${root}`)
14 }
15 return path.posix.normalize(root)
16})()
17
18const DEFAULT_IMAGE_MAX_BYTES = 150_000
19/** Maximum image size (bytes) accepted by `image_tool`. */
20export const IMAGE_MAX_BYTES = (() => {
21 const parsed = parseInt(process.env.IMAGE_TOOL_MAX_BYTES ?? `${DEFAULT_IMAGE_MAX_BYTES}`, 10)
22 if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_IMAGE_MAX_BYTES
23 return parsed
24})()
25
26/** Default max lines returned by shell enough for most output without flooding context. */
27const DEFAULT_MAX_LINES = 150
28
29/**
30 * Commands that routinely produce thousands of lines of noise.
31 * When matched, the default cap is tightened to VERBOSE_MAX_LINES.
32 */
33const VERBOSE_PATTERNS: RegExp[] = [
34 /\bapt(-get)?\s+(install|upgrade|update|dist-upgrade|autoremove)\b/,
35 /\bpip3?\s+install\b/,
36 /\bnpm\s+(install|ci|i)\b/,
37 /\byarn\s+(install|add)\b/,
38 /\bcargo\s+(build|install|fetch|update)\b/,
39 /\bgo\s+(get|install|build|mod\s+download)\b/,
40 /\bdpkg\b/,
41 /\bsnap\s+install\b/,
42]
43
44const VERBOSE_MAX_LINES = 40
45/** Default timeout for shell command execution in milliseconds. */
46export const DEFAULT_COMMAND_TIMEOUT_MS = 30_000
47/** Default timeout for file operations in milliseconds. */
48export const DEFAULT_FILE_TIMEOUT_MS = 120_000
49/** Upper bound for all tool timeouts in milliseconds. */
50export const MAX_TIMEOUT_MS = 10 * 60_000
51
52/**
53 * Returns the effective image root path exposed to model/tool descriptions.
54 *
55 * @returns The normalized absolute image root path.
56 */
57export function imageRootForModelInput(): string {
58 return IMAGE_ROOT
59}
60
61/**
62 * Resolves the effective output line cap for a shell command.
63 *
64 * @param command - Command text used to detect verbose command patterns.
65 * @param requested - Optional explicit line cap override.
66 * @returns Effective max line count (`0` means no truncation).
67 */
68export function resolveMaxLines(command: string, requested?: number): number {
69 if (requested !== undefined) return requested
70 return VERBOSE_PATTERNS.some((p) => p.test(command)) ? VERBOSE_MAX_LINES : DEFAULT_MAX_LINES
71}
72
73/**
74 * Normalizes a timeout value by applying defaults, integer coercion, and hard cap.
75 *
76 * @param requested - User-provided timeout value.
77 * @param fallback - Default timeout to use when the provided value is invalid.
78 * @returns A bounded timeout in milliseconds.
79 */
80export function normalizeTimeoutMs(requested: number | undefined, fallback: number): number {
81 const numeric = Number(requested)
82 if (!Number.isFinite(numeric) || numeric <= 0) return fallback
83 return Math.min(Math.trunc(numeric), MAX_TIMEOUT_MS)
84}