mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 21:07:01 -06:00
fix(cli): reject missing repeatable inference inputs (#130608)
This commit is contained in:
committed by
GitHub
parent
59fc8fc03f
commit
d3d43eb6cf
@@ -101,6 +101,7 @@ A good infer-based skill maps common user intents to the right subcommand, inclu
|
||||
|
||||
- Use `--json` when the output feeds another command or script; text output otherwise.
|
||||
- Use `--provider` or `--model provider/model` to pin a specific backend.
|
||||
- `image edit` and `image describe-many` require at least one `--file`; `embedding create` requires at least one `--text`. Repeat the flag for multiple inputs. Omitting it is a usage error, not an empty successful result, and no inference request is sent.
|
||||
- Use `model run --thinking <level>` for a one-shot thinking/reasoning override: `off`, `minimal`, `low`, `medium`, `high`, `adaptive`, `xhigh`, or `max`.
|
||||
- For `image describe`, `audio transcribe`, and `video describe`, `--model` must use the form `<provider/model>`.
|
||||
- For `image describe`, `--file` accepts local paths and HTTP(S) URLs; remote URLs go through the normal media-fetch SSRF policy.
|
||||
|
||||
@@ -568,6 +568,32 @@ vi.mock("../plugins/web-search-providers.runtime.js", () => ({
|
||||
}));
|
||||
|
||||
describe("capability cli", () => {
|
||||
it.each(
|
||||
[
|
||||
{ args: ["image", "edit", "--prompt", "crop the image"], option: "--file <path>" },
|
||||
{ args: ["image", "describe-many"], option: "--file <path>" },
|
||||
{ args: ["embedding", "create"], option: "--text <text>" },
|
||||
].flatMap(({ args, option }) =>
|
||||
["infer", "capability"].map((root) => ({ args, option, root })),
|
||||
),
|
||||
)("rejects missing required repeatable input for $root $args", async ({ root, args, option }) => {
|
||||
const argv = [root, ...args, "--json"];
|
||||
const program = new Command().exitOverride().configureOutput({ writeErr: () => {} });
|
||||
await registerCapabilityCli(program, ["node", "openclaw", ...argv]);
|
||||
|
||||
await expect(
|
||||
program.parseAsync(argv, { from: "user" }).then(() => undefined),
|
||||
).rejects.toMatchObject({
|
||||
code: "commander.missingMandatoryOptionValue",
|
||||
message: `error: required option '${option}' not specified`,
|
||||
});
|
||||
expect(mocks.resolveCommandConfigWithSecrets).not.toHaveBeenCalled();
|
||||
expect(mocks.generateImage).not.toHaveBeenCalled();
|
||||
expect(mocks.describeImageFile).not.toHaveBeenCalled();
|
||||
expect(mocks.createEmbeddingProvider).not.toHaveBeenCalled();
|
||||
expect(mocks.runtime.writeJson).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
vi.unstubAllEnvs();
|
||||
@@ -2417,6 +2443,8 @@ describe("capability cli", () => {
|
||||
"edit",
|
||||
"--file",
|
||||
inputPath,
|
||||
"--file",
|
||||
inputPath,
|
||||
"--prompt",
|
||||
"make three variants",
|
||||
"--count",
|
||||
@@ -2425,6 +2453,7 @@ describe("capability cli", () => {
|
||||
);
|
||||
|
||||
expect(firstImageGenerationCall()?.count).toBe(3);
|
||||
expect(firstImageGenerationCall()?.inputImages).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("rejects unsupported image output format and background hints", async () => {
|
||||
@@ -3678,15 +3707,20 @@ describe("capability cli", () => {
|
||||
);
|
||||
|
||||
it("uses only embedding providers for embedding creation", async () => {
|
||||
await runCapability("embedding", "create", "--text", "hello", "--json");
|
||||
await runCapability("embedding", "create", "--text", "hello", "--text", "world", "--json");
|
||||
|
||||
expect(firstEmbeddingProviderCall()?.provider).toBe("auto");
|
||||
expect(firstEmbeddingProviderCall()?.fallback).toBe("none");
|
||||
expect(firstJsonOutput()?.capability).toBe("embedding.create");
|
||||
expect(firstJsonOutput()?.provider).toBe("openai");
|
||||
expect(firstJsonOutput()?.model).toBe("text-embedding-3-small");
|
||||
expect(firstJsonOutput()).toMatchObject({ outputs: [{ embedding: [0.1, 0.2] }] });
|
||||
expect(mocks.embedBatch).toHaveBeenCalledWith(["hello"], { inputType: "document" });
|
||||
expect(firstJsonOutput()).toMatchObject({
|
||||
outputs: [
|
||||
{ text: "hello", embedding: [0.1, 0.2] },
|
||||
{ text: "world", embedding: [0.1, 0.2] },
|
||||
],
|
||||
});
|
||||
expect(mocks.embedBatch).toHaveBeenCalledWith(["hello", "world"], { inputType: "document" });
|
||||
expect(closeEmbeddingProviderMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ export function registerEmbeddingCapabilityCommands(capability: Command): void {
|
||||
embedding
|
||||
.command("create")
|
||||
.description("Create embeddings")
|
||||
.requiredOption("--text <text>", "Input text", collectOption, [])
|
||||
.requiredOption("--text <text>", "Input text", collectOption)
|
||||
.option("--provider <id>", "Provider id")
|
||||
.option("--model <provider/model>", "Model override")
|
||||
.option(
|
||||
|
||||
@@ -366,7 +366,7 @@ export function registerImageCapabilityCommands(capability: Command): void {
|
||||
image
|
||||
.command("edit")
|
||||
.description("Edit images with one or more input files")
|
||||
.requiredOption("--file <path>", "Input file", collectOption, [])
|
||||
.requiredOption("--file <path>", "Input file", collectOption)
|
||||
.requiredOption("--prompt <text>", "Prompt text"),
|
||||
).action(async (opts, command) => {
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
@@ -410,7 +410,7 @@ export function registerImageCapabilityCommands(capability: Command): void {
|
||||
image
|
||||
.command("describe-many")
|
||||
.description("Describe multiple image files")
|
||||
.requiredOption("--file <path>", "Image file", collectOption, [])
|
||||
.requiredOption("--file <path>", "Image file", collectOption)
|
||||
.option("--prompt <text>", "Prompt hint")
|
||||
.option("--model <provider/model>", "Model override")
|
||||
.option("--timeout-ms <ms>", "Provider request timeout in milliseconds")
|
||||
|
||||
Reference in New Issue
Block a user