mirror of
https://github.com/cline/cline.git
synced 2026-10-24 02:02:27 +08:00
fix(core): reject an empty run_commands list instead of reporting success (#15089)
* fix(core): reject an empty run_commands list instead of reporting success
`{"commands":[]}` validated, ran nothing, and came back as a successful
call with no results. Fail it with a clear tool error so the model sees
that no command was given, whether it emitted the empty list itself or
a repair produced it.
* fix(core): advertise the run_commands list as non-empty
The lenient input union still accepts an empty list so the normalizer
can reject it with one clear message; the advertised schema now also
tells the model up front, as minItems: 1.
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import { zodToJsonSchema } from "@cline/shared";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeRunCommandsInput } from "./helpers";
|
||||
import { RunCommandsInputSchema } from "./schemas";
|
||||
|
||||
describe("normalizeRunCommandsInput", () => {
|
||||
it("accepts the documented shapes", () => {
|
||||
expect(normalizeRunCommandsInput({ commands: ["ls", "pwd"] })).toEqual([
|
||||
"ls",
|
||||
"pwd",
|
||||
]);
|
||||
expect(normalizeRunCommandsInput({ commands: "ls" })).toEqual(["ls"]);
|
||||
expect(normalizeRunCommandsInput(["ls"])).toEqual(["ls"]);
|
||||
expect(normalizeRunCommandsInput("ls")).toEqual(["ls"]);
|
||||
expect(normalizeRunCommandsInput({ command: "ls" })).toEqual(["ls"]);
|
||||
expect(
|
||||
normalizeRunCommandsInput({ command: "git", args: ["status"] }),
|
||||
).toEqual([{ command: "git", args: ["status"] }]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ label: "an object with an empty commands list", input: { commands: [] } },
|
||||
{ label: "a bare empty list", input: [] },
|
||||
])("rejects $label instead of reporting an empty success", ({ input }) => {
|
||||
expect(() => normalizeRunCommandsInput(input)).toThrow(
|
||||
"run_commands requires at least one command",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("RunCommandsInputSchema", () => {
|
||||
it("advertises the command list as non-empty", () => {
|
||||
const schema = zodToJsonSchema(RunCommandsInputSchema) as {
|
||||
properties: { commands: { minItems?: number } };
|
||||
};
|
||||
expect(schema.properties.commands.minItems).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { validateWithZod } from "@cline/shared";
|
||||
import type { z } from "zod";
|
||||
import {
|
||||
type EditFileInput,
|
||||
INPUT_ARG_CHAR_LIMIT,
|
||||
@@ -137,8 +138,24 @@ export function coalesceOrphanReadRanges(input: unknown): unknown {
|
||||
export function normalizeRunCommandsInput(
|
||||
input: unknown,
|
||||
): Array<string | StructuredCommandInput> {
|
||||
const validate = validateWithZod(RunCommandsInputUnionSchema, input);
|
||||
const commands = collectRunCommands(
|
||||
validateWithZod(RunCommandsInputUnionSchema, input),
|
||||
);
|
||||
// An empty list is well-formed but never a real request: nothing would
|
||||
// run and the call would still report success. Fail it so the model sees
|
||||
// that no command was given instead of an empty result it has to read
|
||||
// into.
|
||||
if (commands.length === 0) {
|
||||
throw new Error(
|
||||
"run_commands requires at least one command; the commands list was empty.",
|
||||
);
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
|
||||
function collectRunCommands(
|
||||
validate: z.infer<typeof RunCommandsInputUnionSchema>,
|
||||
): Array<string | StructuredCommandInput> {
|
||||
if (typeof validate === "string") {
|
||||
return [validate];
|
||||
}
|
||||
|
||||
@@ -149,6 +149,10 @@ export const StructuredCommandEntrySchema = z.union([
|
||||
export const RunCommandsInputSchema = z.object({
|
||||
commands: z
|
||||
.array(CommandInputSchema)
|
||||
// Advertised to the model as minItems: 1. The lenient union below still
|
||||
// accepts an empty list so the normalizer can reject it with one clear
|
||||
// message instead of a union error.
|
||||
.min(1)
|
||||
.describe("Array of complete shell command strings to execute."),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user