From 9ef7f241f605bd598aceef77d1be065cc2603908 Mon Sep 17 00:00:00 2001 From: MkDev11 <94194147+MkDev11@users.noreply.github.com> Date: Sat, 2 May 2026 16:34:44 -0700 Subject: [PATCH] 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 head 56069df9030685ad698bd72682d51275c1b31614. - Required merge gates passed before the squash merge. Prepared head SHA: 56069df9030685ad698bd72682d51275c1b31614 Review: https://github.com/openclaw/openclaw/pull/75550#issuecomment-4358608888 Co-authored-by: mkdev11 --- CHANGELOG.md | 1 + ...ded-subscribe.handlers.tools.media.test.ts | 110 +++++++++++------- .../pi-embedded-subscribe.handlers.tools.ts | 27 +---- 3 files changed, 68 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c89bbfdd0f..59de65de876 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/src/agents/pi-embedded-subscribe.handlers.tools.media.test.ts b/src/agents/pi-embedded-subscribe.handlers.tools.media.test.ts index 053a4507224..1e515aad667 100644 --- a/src/agents/pi-embedded-subscribe.handlers.tools.media.test.ts +++ b/src/agents/pi-embedded-subscribe.handlers.tools.media.test.ts @@ -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(); diff --git a/src/agents/pi-embedded-subscribe.handlers.tools.ts b/src/agents/pi-embedded-subscribe.handlers.tools.ts index a1b313afdde..5996d17d7ac 100644 --- a/src/agents/pi-embedded-subscribe.handlers.tools.ts +++ b/src/agents/pi-embedded-subscribe.handlers.tools.ts @@ -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);