src / agents / tools / command.ts
src / agents / tools / command.ts
import { tool, type Tool } from "@lmstudio/sdk";
import { z } from "zod";
import { AgenticError } from "../../core/errors";
import { errorResult, okResult } from "../../core/result";
import type { AgentToolContext } from "./context";
/** `run_command` — an allowlisted executable, never a shell. */
export function createRunCommandTool(ctx: AgentToolContext): Tool {
const { state, beforeTool } = ctx;
return tool({
name: "run_command",
description:
"Run an allowlisted executable without a shell. Use for tests, builds, linters, and inspection commands.",
parameters: {
executable: z.string(),
args: z.array(z.string()).optional(),
cwd: z.string().optional(),
timeout_seconds: z.number().min(1).optional(),
idempotency_key: z.string().optional(),
},
implementation: async (input: {
executable: string;
args?: string[];
cwd?: string;
timeout_seconds?: number;
idempotency_key?: string;
}) => {
await beforeTool("run_command");
if (!state.allowCommands) {
return errorResult(
"command.run",
new AgenticError("PROCESS_DISABLED", "Command execution is disabled for this run."),
);
}
try {
const result = await ctx.processes.run({
executable: input.executable,
args: input.args,
cwd: input.cwd,
timeoutSeconds: input.timeout_seconds,
idempotencyKey: input.idempotency_key
? `${state.id}:${input.idempotency_key}`
: `${state.id}:pass${state.pass}:tool${state.toolCalls}`,
runId: state.id,
});
state.commands = [
...state.commands.filter((command) => command.id !== result.id),
{
id: result.id,
status: result.status,
summary: `${result.executable} ${result.args.join(" ")} -> ${
result.exitCode ?? result.signal ?? result.status
}`,
},
].slice(-50);
await ctx.store.save(state);
return okResult({
operation: "command.run",
summary: state.commands[state.commands.length - 1].summary,
data: {
id: result.id,
status: result.status,
exitCode: result.exitCode,
durationMs: result.durationMs,
stdout: result.stdoutPreview,
stderr: result.stderrPreview,
stdoutBytes: result.stdoutBytes,
stderrBytes: result.stderrBytes,
stdoutCapturedBytes: result.stdoutCapturedBytes,
stderrCapturedBytes: result.stderrCapturedBytes,
stdoutTruncated: result.stdoutTruncated,
stderrTruncated: result.stderrTruncated,
},
artifacts: result.artifacts,
importance: result.status === "completed" ? "normal" : "high",
facts: [state.commands[state.commands.length - 1].summary],
omit: ["data.stdout", "data.stderr"],
});
} catch (error) {
return errorResult("command.run", error);
}
},
});
}
import { tool, type Tool } from "@lmstudio/sdk";
import { z } from "zod";
import { AgenticError } from "../../core/errors";
import { errorResult, okResult } from "../../core/result";
import type { AgentToolContext } from "./context";
/** `run_command` — an allowlisted executable, never a shell. */
export function createRunCommandTool(ctx: AgentToolContext): Tool {
const { state, beforeTool } = ctx;
return tool({
name: "run_command",
description:
"Run an allowlisted executable without a shell. Use for tests, builds, linters, and inspection commands.",
parameters: {
executable: z.string(),
args: z.array(z.string()).optional(),
cwd: z.string().optional(),
timeout_seconds: z.number().min(1).optional(),
idempotency_key: z.string().optional(),
},
implementation: async (input: {
executable: string;
args?: string[];
cwd?: string;
timeout_seconds?: number;
idempotency_key?: string;
}) => {
await beforeTool("run_command");
if (!state.allowCommands) {
return errorResult(
"command.run",
new AgenticError("PROCESS_DISABLED", "Command execution is disabled for this run."),
);
}
try {
const result = await ctx.processes.run({
executable: input.executable,
args: input.args,
cwd: input.cwd,
timeoutSeconds: input.timeout_seconds,
idempotencyKey: input.idempotency_key
? `${state.id}:${input.idempotency_key}`
: `${state.id}:pass${state.pass}:tool${state.toolCalls}`,
runId: state.id,
});
state.commands = [
...state.commands.filter((command) => command.id !== result.id),
{
id: result.id,
status: result.status,
summary: `${result.executable} ${result.args.join(" ")} -> ${
result.exitCode ?? result.signal ?? result.status
}`,
},
].slice(-50);
await ctx.store.save(state);
return okResult({
operation: "command.run",
summary: state.commands[state.commands.length - 1].summary,
data: {
id: result.id,
status: result.status,
exitCode: result.exitCode,
durationMs: result.durationMs,
stdout: result.stdoutPreview,
stderr: result.stderrPreview,
stdoutBytes: result.stdoutBytes,
stderrBytes: result.stderrBytes,
stdoutCapturedBytes: result.stdoutCapturedBytes,
stderrCapturedBytes: result.stderrCapturedBytes,
stdoutTruncated: result.stdoutTruncated,
stderrTruncated: result.stderrTruncated,
},
artifacts: result.artifacts,
importance: result.status === "completed" ? "normal" : "high",
facts: [state.commands[state.commands.length - 1].summary],
omit: ["data.stdout", "data.stderr"],
});
} catch (error) {
return errorResult("command.run", error);
}
},
});
}