feat(S-102): Test-drive and implement --timeout flag, config layering, and default in

This commit is contained in:
2026-05-19 19:39:46 -05:00
parent 5375c83c77
commit dc3fe8d6eb
6 changed files with 182 additions and 15 deletions
+11 -4
View File
@@ -21,7 +21,7 @@ export interface CliDeps {
prompt: string,
config?: { client?: ClientName; defaultClient?: ClientName }
) => ClientName | null;
resolveConfig?: () => { paths: Partial<Record<ClientName, string>>; defaultClient?: ClientName };
resolveConfig?: () => { paths: Partial<Record<ClientName, string>>; defaultClient?: ClientName; timeout?: number };
stdoutWrite?: (chunk: string) => void;
stderrWrite?: (chunk: string) => void;
}
@@ -54,7 +54,7 @@ export async function main(
rawArgs[0]?.includes("cli.ts") ? rawArgs.slice(1) : rawArgs;
const args = minimist(parseArgs, {
string: ["client", "prompt"],
string: ["client", "prompt", "timeout"],
boolean: ["json", "text", "help", "debug"],
alias: { h: "help" },
});
@@ -114,8 +114,12 @@ export async function main(
return 1;
}
const config = resolveConfig();
const timeoutMs =
typeof args.timeout === "string" ? Number(args.timeout) : config.timeout;
try {
const result = await executePrompt(client, prompt);
const result = await executePrompt(client, prompt, { timeoutMs });
if (jsonMode) {
console.log(JSON.stringify(result, null, 2));
} else {
@@ -167,8 +171,11 @@ export async function main(
return 1;
}
const timeoutMs =
typeof args.timeout === "string" ? Number(args.timeout) : config.timeout;
try {
const result = await executePrompt(client, prompt);
const result = await executePrompt(client, prompt, { timeoutMs });
if (jsonMode) {
console.log(JSON.stringify(result, null, 2));
} else {
+18
View File
@@ -10,6 +10,7 @@ import { CLIENT_NAMES, isWindows } from "./constants.js";
export interface ResolvedConfig {
paths: Partial<Record<ClientName, string>>;
defaultClient?: ClientName;
timeout?: number;
}
export interface ResolveConfigOptions {
@@ -89,5 +90,22 @@ export function resolveConfig(
) {
result.defaultClient = defaultClient as ClientName;
}
const flagTimeout =
typeof flags.timeout === "string" ? Number(flags.timeout) : undefined;
const envTimeout =
typeof env.AI_CLI_TIMEOUT === "string"
? Number(env.AI_CLI_TIMEOUT)
: undefined;
const fileTimeout =
typeof fileConfig.timeout === "number" ? fileConfig.timeout : undefined;
const resolvedTimeout =
(Number.isFinite(flagTimeout) ? flagTimeout : undefined) ??
(Number.isFinite(envTimeout) ? envTimeout : undefined) ??
(Number.isFinite(fileTimeout) ? fileTimeout : undefined) ??
600_000;
result.timeout = resolvedTimeout;
return result;
}
+20 -5
View File
@@ -28,12 +28,14 @@ export async function executePrompt(
stdout: "",
stderr: "",
exitCode: -1,
client,
durationMs: 0,
});
}
const spawnImpl = options.spawn ?? defaultSpawn;
const existsSyncImpl = options.existsSync ?? defaultExistsSync;
const timeoutMs = options.timeoutMs ?? 300_000;
const timeoutMs = options.timeoutMs ?? 600_000;
const command = options.clientPath ?? client;
if (options.clientPath && !existsSyncImpl(options.clientPath)) {
@@ -46,6 +48,8 @@ export async function executePrompt(
stdout: "",
stderr: "",
exitCode: -1,
client,
durationMs: 0,
});
}
const args = argBuilder(prompt);
@@ -55,6 +59,7 @@ export async function executePrompt(
let timedOut = false;
let stdout = "";
let stderr = "";
const startMs = Date.now();
const child = spawnImpl(command, args, {
shell: false,
@@ -80,8 +85,16 @@ export async function executePrompt(
if (settled) return;
settled = true;
clearTimeout(timeout);
if (err) reject(err);
else resolve(result!);
const durationMs = Date.now() - startMs;
if (err) {
if (err instanceof ExecError) {
err.result.client = client;
err.result.durationMs = durationMs;
}
reject(err);
} else {
resolve({ ...result!, client, durationMs });
}
}
child.on("error", (err: NodeJS.ErrnoException) => {
@@ -89,7 +102,7 @@ export async function executePrompt(
settle(new ClientNotFoundError(client));
} else {
settle(
new ExecError(err.message, { stdout, stderr, exitCode: -1 })
new ExecError(err.message, { stdout, stderr, exitCode: -1, client, durationMs: 0 })
);
}
});
@@ -101,10 +114,12 @@ export async function executePrompt(
stdout,
stderr,
exitCode: -1,
client,
durationMs: 0,
})
);
} else {
settle(undefined, { stdout, stderr, exitCode: code ?? -1 });
settle(undefined, { stdout, stderr, exitCode: code ?? -1, client, durationMs: 0 });
}
});
});