From bf33ab622fd9081baefcd658a7719147633520db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BF=AE=E9=9B=A8?= Date: Mon, 3 Aug 2026 20:27:42 +0800 Subject: [PATCH] fix(chat): restore download media JSON result --- CHANGELOG.md | 4 + internal/helpers/chat.go | 16 +++- internal/helpers/chat_download_media_test.go | 95 ++++++++++++++++++++ 3 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 internal/helpers/chat_download_media_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 95265ad3..9e1a57dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th ## [Unreleased] +### Fixed + +- **Chat media download JSON compatibility** — `dws chat message download-media --format json` once again returns a clean `{success, downloadUrl, output}` result after the file is saved, preserving the temporary URL and resolved local path without progress text corrupting JSON stdout. + ## [1.0.56-beta.3] - 2026-08-03 This beta adds PRs #846 and #851 on top of v1.0.56-beta.2. It adds a diff --git a/internal/helpers/chat.go b/internal/helpers/chat.go index f4861bc5..35708870 100644 --- a/internal/helpers/chat.go +++ b/internal/helpers/chat.go @@ -3434,6 +3434,7 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成 conversationID := mustGetFlag(cmd, "open-conversation-id") messageID := mustGetFlag(cmd, "message-id") outputPath := mustGetFlag(cmd, "output") + jsonMode := deps.Caller.Format() == "json" switch resourceType { case "mediaId": @@ -3455,7 +3456,9 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成 ctx := context.Background() // Step 1: 获取下载 URL - deps.Out.PrintInfo("[1/2] 获取资源下载链接...") + if !jsonMode { + deps.Out.PrintInfo("[1/2] 获取资源下载链接...") + } text, err := callMCPToolReturnTextOnServer(ctx, "im", "get_resource_download_url", map[string]any{ "resourceType": resourceType, "resourceId": resourceID, @@ -3488,11 +3491,20 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成 } // Step 2: HTTP GET 下载文件 - deps.Out.PrintInfo(fmt.Sprintf("[2/2] 下载资源到 %s ...", outputPath)) + if !jsonMode { + deps.Out.PrintInfo(fmt.Sprintf("[2/2] 下载资源到 %s ...", outputPath)) + } if err := httpGetFile(ctx, resourceURL, dlHeaders, outputPath); err != nil { return err } + if jsonMode { + return deps.Out.PrintJSONUnescaped(map[string]any{ + "success": true, + "downloadUrl": resourceURL, + "output": outputPath, + }) + } deps.Out.PrintInfo(fmt.Sprintf("下载完成: %s", outputPath)) return nil }, diff --git a/internal/helpers/chat_download_media_test.go b/internal/helpers/chat_download_media_test.go new file mode 100644 index 00000000..e35244e9 --- /dev/null +++ b/internal/helpers/chat_download_media_test.go @@ -0,0 +1,95 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package helpers + +import ( + "bytes" + "context" + "encoding/json" + "io" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestChatDownloadMediaJSONPreservesLegacyResult(t *testing.T) { + previousDeps, previousArgs := deps, os.Args + previousHTTPGetFile := httpGetFile + os.Args = []string{"dws", "chat"} + t.Cleanup(func() { + deps = previousDeps + os.Args = previousArgs + httpGetFile = previousHTTPGetFile + }) + + const downloadURL = "https://download.example.test/photo.jpg?token=one&part=two" + caller := &scriptedToolCaller{ + format: "json", + steps: []scriptedToolStep{{ + text: `{"resourceUrl":"` + downloadURL + `"}`, + }}, + } + InitDeps(caller) + var stdout bytes.Buffer + deps.Out = &Formatter{w: &stdout, errW: io.Discard} + + outputDir := t.TempDir() + wantOutput := filepath.Join(outputDir, "photo.jpg") + httpGetFile = func(_ context.Context, gotURL string, _ map[string]string, gotOutput string) error { + if gotURL != downloadURL { + t.Fatalf("download URL = %q, want %q", gotURL, downloadURL) + } + if gotOutput != wantOutput { + t.Fatalf("download output = %q, want %q", gotOutput, wantOutput) + } + return nil + } + + root := newChatCommand() + installExampleGlobalFlags(root) + root.SilenceErrors = true + root.SilenceUsage = true + root.SetOut(io.Discard) + root.SetErr(io.Discard) + root.SetArgs([]string{ + "message", "download-media", + "--type=mediaId", + "--resource-id=resource", + "--message-id=message", + "--open-conversation-id=conversation", + "--output=" + outputDir, + }) + if err := root.ExecuteContext(context.Background()); err != nil { + t.Fatal(err) + } + + var got struct { + Success bool `json:"success"` + DownloadURL string `json:"downloadUrl"` + Output string `json:"output"` + } + if err := json.Unmarshal(stdout.Bytes(), &got); err != nil { + t.Fatalf("stdout is not JSON: %v\n%s", err, stdout.String()) + } + if !got.Success || got.DownloadURL != downloadURL || got.Output != wantOutput { + t.Fatalf("result = %#v, want success=true downloadUrl=%q output=%q", got, downloadURL, wantOutput) + } + if strings.Contains(stdout.String(), "[INFO]") { + t.Fatalf("JSON stdout contains progress text: %s", stdout.String()) + } + if !strings.Contains(stdout.String(), "?token=one&part=two") { + t.Fatalf("downloadUrl was escaped or changed: %s", stdout.String()) + } +}