mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:20:43 +00:00
fix: keep media provider inventory internal (#75550)
Summary: - The branch removes the compact image/video provider-inventory output bypass, adds media handler regression tests for hidden and verbose modes, and adds an Unreleased changelog fix entry. - Reproducibility: yes. On current main, a synthetic image_generate or video_generate list result with details ... ut false reaches emitToolOutput; the existing media handler test locks in that old video_generate behavior. ClawSweeper fixups: - Included follow-up commit: chore: rerun ci - Included follow-up commit: chore: move changelog entry below media fixes Validation: - ClawSweeper review passed for head56069df903. - Required merge gates passed before the squash merge. Prepared head SHA:56069df903Review: https://github.com/openclaw/openclaw/pull/75550#issuecomment-4358608888 Co-authored-by: mkdev11 <MkDev11@users.noreply.github.com>
This commit is contained in:
@@ -474,6 +474,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Telegram/agents: keep typing indicators and optional generation tools off the reply critical path, so fresh Telegram replies no longer stall while provider catalogs and media models load. (#75360) Thanks @obviyus.
|
||||
- Agents/commitments: run hidden follow-up extraction on the configured agent/default model instead of falling back to direct OpenAI, so OpenAI Codex OAuth-only gateways no longer spam background API-key failures. Fixes #75334. Thanks @sene1337.
|
||||
- Agents/media: keep async music generation completions on the requester-session wake path even when direct-send completion is enabled, so finished audio stays agent-mediated while video can still opt into direct channel delivery. (#75335) Thanks @vincentkoc.
|
||||
- Agents/media: keep image and video provider inventory internal when tool output is hidden, so shared chat surfaces no longer expose provider/model/auth-hint details from list results. Fixes #75166. Thanks @MkDev11.
|
||||
- Security/config-audit: redact CLI argv and execArgv secrets before persisting config audit records, covering write, observe, and recovery paths. Fixes #60826. Thanks @koshaji.
|
||||
- Gateway/models: keep default and configured model-list views responsive when provider catalog discovery stalls, without hiding real catalog load failures, while `--all` still waits for the exact full catalog. Fixes #75297; refs #74404. Thanks @lisandromachado and @najef1979-code.
|
||||
- Plugins/runtime-deps: accept already materialized package-level runtime-deps supersets as converged, so later lazy plugin activation no longer prunes and relaunches `pnpm install` after gateway startup pre-staging, reducing event-loop pressure from repeated runtime-deps repair on packaged installs. Fixes #75283; refs #75297 and #72338. Thanks @brokemac79, @lisandromachado, and @midhunmonachan.
|
||||
|
||||
@@ -142,6 +142,44 @@ async function handleCaseVariantBuiltinMedia(mediaPathOrUrl: string) {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
const providerInventoryText = [
|
||||
"openai: default=sora-2 | models=sora-2",
|
||||
"google: default=veo-3.1-fast-generate-preview | models=veo-3.1-fast-generate-preview",
|
||||
].join("\n");
|
||||
|
||||
async function handleProviderInventoryListResult(params: {
|
||||
toolName: "image_generate" | "video_generate";
|
||||
shouldEmitToolOutput: boolean;
|
||||
}) {
|
||||
const ctx = createMockContext({
|
||||
shouldEmitToolOutput: params.shouldEmitToolOutput,
|
||||
onToolResult: vi.fn(),
|
||||
toolResultFormat: "plain",
|
||||
});
|
||||
|
||||
await handleToolExecutionEnd(ctx, {
|
||||
type: "tool_execution_end",
|
||||
toolName: params.toolName,
|
||||
toolCallId: "tc-1",
|
||||
isError: false,
|
||||
result: {
|
||||
content: [{ type: "text", text: providerInventoryText }],
|
||||
details: {
|
||||
providers: [
|
||||
{ id: "openai", defaultModel: "sora-2", models: ["sora-2"] },
|
||||
{
|
||||
id: "google",
|
||||
defaultModel: "veo-3.1-fast-generate-preview",
|
||||
models: ["veo-3.1-fast-generate-preview"],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
describe("handleToolExecutionEnd media emission", () => {
|
||||
it("does not warn for read tool when path is provided via file_path alias", async () => {
|
||||
const ctx = createMockContext();
|
||||
@@ -424,52 +462,36 @@ describe("handleToolExecutionEnd media emission", () => {
|
||||
expect(ctx.state.pendingToolMediaUrls).toEqual(["/tmp/generated.png"]);
|
||||
});
|
||||
|
||||
it("emits provider inventory output for compact video_generate list results", async () => {
|
||||
const ctx = createMockContext({
|
||||
shouldEmitToolOutput: false,
|
||||
onToolResult: vi.fn(),
|
||||
toolResultFormat: "plain",
|
||||
});
|
||||
it.each(["image_generate", "video_generate"] as const)(
|
||||
"keeps %s provider inventory internal when tool output is hidden",
|
||||
async (toolName) => {
|
||||
const ctx = await handleProviderInventoryListResult({
|
||||
toolName,
|
||||
shouldEmitToolOutput: false,
|
||||
});
|
||||
|
||||
await handleToolExecutionEnd(ctx, {
|
||||
type: "tool_execution_end",
|
||||
toolName: "video_generate",
|
||||
toolCallId: "tc-1",
|
||||
isError: false,
|
||||
result: {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: [
|
||||
"openai: default=sora-2 | models=sora-2",
|
||||
"google: default=veo-3.1-fast-generate-preview | models=veo-3.1-fast-generate-preview",
|
||||
].join("\n"),
|
||||
},
|
||||
],
|
||||
details: {
|
||||
providers: [
|
||||
{ id: "openai", defaultModel: "sora-2", models: ["sora-2"] },
|
||||
{
|
||||
id: "google",
|
||||
defaultModel: "veo-3.1-fast-generate-preview",
|
||||
models: ["veo-3.1-fast-generate-preview"],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(ctx.emitToolOutput).not.toHaveBeenCalled();
|
||||
expect(ctx.state.pendingToolMediaUrls).toEqual([]);
|
||||
},
|
||||
);
|
||||
|
||||
expect(ctx.emitToolOutput).toHaveBeenCalledWith(
|
||||
"video_generate",
|
||||
undefined,
|
||||
[
|
||||
"openai: default=sora-2 | models=sora-2",
|
||||
"google: default=veo-3.1-fast-generate-preview | models=veo-3.1-fast-generate-preview",
|
||||
].join("\n"),
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(ctx.state.pendingToolMediaUrls).toEqual([]);
|
||||
});
|
||||
it.each(["image_generate", "video_generate"] as const)(
|
||||
"emits %s provider inventory when verbose tool output is enabled",
|
||||
async (toolName) => {
|
||||
const ctx = await handleProviderInventoryListResult({
|
||||
toolName,
|
||||
shouldEmitToolOutput: true,
|
||||
});
|
||||
|
||||
expect(ctx.emitToolOutput).toHaveBeenCalledWith(
|
||||
toolName,
|
||||
undefined,
|
||||
providerInventoryText,
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(ctx.state.pendingToolMediaUrls).toEqual([]);
|
||||
},
|
||||
);
|
||||
|
||||
it("does NOT emit media for error results", async () => {
|
||||
const onToolResult = vi.fn();
|
||||
|
||||
@@ -361,30 +361,6 @@ async function collectEmittedToolOutputMediaUrls(
|
||||
return filterToolResultMediaUrls(toolName, mediaUrls, result);
|
||||
}
|
||||
|
||||
const COMPACT_PROVIDER_INVENTORY_TOOLS = new Set(["image_generate", "video_generate"]);
|
||||
|
||||
function hasProviderInventoryDetails(result: unknown): boolean {
|
||||
if (!result || typeof result !== "object") {
|
||||
return false;
|
||||
}
|
||||
const details = readToolResultDetailsRecord(result);
|
||||
return Array.isArray(details?.providers);
|
||||
}
|
||||
|
||||
function shouldEmitCompactToolOutput(params: {
|
||||
toolName: string;
|
||||
result: unknown;
|
||||
outputText?: string;
|
||||
}): boolean {
|
||||
if (!COMPACT_PROVIDER_INVENTORY_TOOLS.has(params.toolName)) {
|
||||
return false;
|
||||
}
|
||||
if (!hasProviderInventoryDetails(params.result)) {
|
||||
return false;
|
||||
}
|
||||
return Boolean(params.outputText?.trim());
|
||||
}
|
||||
|
||||
function readExecApprovalPendingDetails(result: unknown): {
|
||||
approvalId: string;
|
||||
approvalSlug: string;
|
||||
@@ -560,8 +536,7 @@ async function emitToolResultOutput(params: {
|
||||
isToolError,
|
||||
hasDeliverableStructuredMedia: hasStructuredMedia && mediaUrls.length > 0,
|
||||
builtinToolNames: ctx.builtinToolNames,
|
||||
}) &&
|
||||
(ctx.shouldEmitToolOutput() || shouldEmitCompactToolOutput({ toolName, result, outputText }));
|
||||
}) && ctx.shouldEmitToolOutput();
|
||||
if (shouldEmitOutput) {
|
||||
if (outputText) {
|
||||
ctx.emitToolOutput(rawToolName, meta, outputText, result);
|
||||
|
||||
Reference in New Issue
Block a user