mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-03 22:01:40 +00:00
fix(cli): propagate web provider failures (#116674)
This commit is contained in:
@@ -3409,6 +3409,32 @@ describe("capability cli", () => {
|
||||
expect(webSearchRuntime.runWebSearch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reports structured web search failures in the envelope and exits nonzero", async () => {
|
||||
const webSearchRuntime = await import("../web-search/runtime.js");
|
||||
vi.mocked(webSearchRuntime.runWebSearch).mockResolvedValueOnce({
|
||||
provider: "kitchen-sink-search",
|
||||
result: {
|
||||
ok: false,
|
||||
statusCode: 429,
|
||||
error: { code: "rate_limited", message: "Kitchen Sink rate limit." },
|
||||
results: [],
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
runCap("capability", "web", "search", "--query", "rate limit", "--json"),
|
||||
).rejects.toThrow("exit 1");
|
||||
|
||||
expect(firstJsonOutput()).toEqual(
|
||||
expect.objectContaining({
|
||||
ok: false,
|
||||
capability: "web.search",
|
||||
provider: "kitchen-sink-search",
|
||||
error: "Kitchen Sink rate limit.",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("uses the infer web search provider override when resolving SecretRefs", async () => {
|
||||
const unresolvedConfig = {
|
||||
tools: { web: { search: { provider: "exa", enabled: true } } },
|
||||
@@ -3623,6 +3649,33 @@ describe("capability cli", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("reports structured web fetch failures in the envelope and exits nonzero", async () => {
|
||||
const webFetchRuntime = await import("../web-fetch/runtime.js");
|
||||
vi.mocked(webFetchRuntime.resolveWebFetchDefinition).mockReturnValueOnce({
|
||||
provider: { id: "kitchen-sink-fetch" },
|
||||
definition: {
|
||||
execute: vi.fn(async () => ({
|
||||
ok: false,
|
||||
statusCode: 504,
|
||||
error: { code: "timeout", message: "Kitchen Sink fetch timed out." },
|
||||
})),
|
||||
},
|
||||
} as never);
|
||||
|
||||
await expect(
|
||||
runCap("capability", "web", "fetch", "--url", "kitchen://fixture/timeout", "--json"),
|
||||
).rejects.toThrow("exit 1");
|
||||
|
||||
expect(firstJsonOutput()).toEqual(
|
||||
expect.objectContaining({
|
||||
ok: false,
|
||||
capability: "web.fetch",
|
||||
provider: "kitchen-sink-fetch",
|
||||
error: "Kitchen Sink fetch timed out.",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("surfaces available, configured, and selected for web providers", async () => {
|
||||
mocks.loadConfig.mockReturnValue({
|
||||
tools: {
|
||||
|
||||
@@ -25,6 +25,29 @@ import {
|
||||
resolveLocalCapabilityRuntimeConfig,
|
||||
} from "./shared.js";
|
||||
|
||||
function describeWebResultFailure(result: Record<string, unknown>): string | undefined {
|
||||
const statusCode =
|
||||
typeof result.statusCode === "number" && Number.isFinite(result.statusCode)
|
||||
? result.statusCode
|
||||
: undefined;
|
||||
const error = result.error;
|
||||
const errorMessage =
|
||||
typeof error === "string"
|
||||
? error
|
||||
: error &&
|
||||
typeof error === "object" &&
|
||||
typeof (error as { message?: unknown }).message === "string"
|
||||
? (error as { message: string }).message
|
||||
: undefined;
|
||||
if (result.ok !== false && (statusCode === undefined || statusCode < 400) && !errorMessage) {
|
||||
return undefined;
|
||||
}
|
||||
return (
|
||||
errorMessage ??
|
||||
(statusCode ? `provider returned status ${statusCode}` : "provider reported failure")
|
||||
);
|
||||
}
|
||||
|
||||
async function runWebSearchCommand(params: { query: string; provider?: string; limit?: number }) {
|
||||
const rawConfig = getRuntimeConfig();
|
||||
const scopedTargets = getCapabilityWebSearchCommandSecretTargets(rawConfig, {
|
||||
@@ -51,13 +74,15 @@ async function runWebSearchCommand(params: { query: string; provider?: string; l
|
||||
limit: params.limit,
|
||||
},
|
||||
});
|
||||
const error = describeWebResultFailure(result.result);
|
||||
return {
|
||||
ok: true,
|
||||
ok: error === undefined,
|
||||
capability: "web.search",
|
||||
transport: "local" as const,
|
||||
provider: result.provider,
|
||||
attempts: [],
|
||||
outputs: [{ result: result.result }],
|
||||
...(error ? { error } : {}),
|
||||
} satisfies CapabilityEnvelope;
|
||||
}
|
||||
|
||||
@@ -89,13 +114,15 @@ async function runWebFetchCommand(params: { url: string; provider?: string; form
|
||||
url: params.url,
|
||||
format: params.format,
|
||||
});
|
||||
const error = describeWebResultFailure(result);
|
||||
return {
|
||||
ok: true,
|
||||
ok: error === undefined,
|
||||
capability: "web.fetch",
|
||||
transport: "local" as const,
|
||||
provider: resolved.provider.id,
|
||||
attempts: [],
|
||||
outputs: [{ result }],
|
||||
...(error ? { error } : {}),
|
||||
} satisfies CapabilityEnvelope;
|
||||
}
|
||||
|
||||
@@ -110,14 +137,19 @@ export function registerWebCapabilityCommands(capability: Command): void {
|
||||
.option("--limit <n>", "Result limit")
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts) => {
|
||||
let failed = false;
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
const result = await runWebSearchCommand({
|
||||
query: String(opts.query),
|
||||
provider: opts.provider as string | undefined,
|
||||
limit: parseOptionalPositiveInteger(opts.limit, "--limit"),
|
||||
});
|
||||
failed = !result.ok;
|
||||
emitJsonOrText(defaultRuntime, Boolean(opts.json), result, formatEnvelopeForText);
|
||||
});
|
||||
if (failed) {
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
web
|
||||
@@ -128,14 +160,19 @@ export function registerWebCapabilityCommands(capability: Command): void {
|
||||
.option("--format <format>", "Format hint")
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts) => {
|
||||
let failed = false;
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
const result = await runWebFetchCommand({
|
||||
url: String(opts.url),
|
||||
provider: opts.provider as string | undefined,
|
||||
format: opts.format as string | undefined,
|
||||
});
|
||||
failed = !result.ok;
|
||||
emitJsonOrText(defaultRuntime, Boolean(opts.json), result, formatEnvelopeForText);
|
||||
});
|
||||
if (failed) {
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
web
|
||||
|
||||
Reference in New Issue
Block a user