Skip to content

feat(wps-xiezuo): support chat attachments and cloud documents - #1850

Open
zy5669 wants to merge 5 commits into
chenhg5:mainfrom
zy5669:feat/wps-xiezuo-rich-media
Open

zy5669 wants to merge 5 commits into
chenhg5:mainfrom
zy5669:feat/wps-xiezuo-rich-media

Conversation

@zy5669

@zy5669 zy5669 commented Sep 15, 2026

Copy link
Copy Markdown

Summary

Extend WPS Xiezuo inbound message parsing to support images, local files, and cloud documents.

This change downloads images and local files from WPS chat messages and forwards them to the configured agent as attachments. It also resolves cloud-document links and includes the document content when the application has the required permissions, with a link-only fallback when the content cannot be accessed.

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing behavior to change)
  • Documentation only
  • Internal refactor / chore (no user-visible change)

Testing

Automated tests added in this PR

  • TestHandleChatMessage_ImageAttachment in platform/wps-xiezuo/wpsxiezuo_test.go — verifies that chat images are downloaded and forwarded as image attachments.
  • TestHandleChatMessage_LocalFileAttachment in platform/wps-xiezuo/wpsxiezuo_test.go — verifies that local files are downloaded and forwarded as file attachments.
  • TestHandleChatMessage_CloudDocumentForwardsLink in platform/wps-xiezuo/wpsxiezuo_test.go — verifies cloud-document links are preserved when document content is unavailable.
  • TestHandleChatMessage_RichTextEmbeddedCloudDocument in platform/wps-xiezuo/wpsxiezuo_test.go — verifies cloud documents embedded in rich text are parsed.
  • TestHandleChatMessage_RichTextEmbeddedCloudDocumentReadsContent in platform/wps-xiezuo/wpsxiezuo_test.go — verifies authorized cloud-document content is fetched and included in the message.
  • TestHandleChatMessage_RichTextImageAttachment in platform/wps-xiezuo/wpsxiezuo_test.go — verifies images embedded in rich-text messages are downloaded and forwarded.
  • TestDownloadMessageResource_RejectsOversizeBody in platform/wps-xiezuo/wpsxiezuo_test.go — verifies oversized attachments are rejected.

Package-level test:

go test ./platform/wps-xiezuo

@zy5669
zy5669 requested a review from chenhg5 as a code owner September 15, 2026 06:09

@chenhg5 chenhg5 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI Gate 阻塞 — lint 失败

CI 在 lint check 上 fail,未进入完整 diff review。按项目 CI gate 流程,先打回开发修 lint,绿了再做完整评审。

失败 check

4 个 staticcheck issue,都在新增的测试文件

platform/wps-xiezuo/wpsxiezuo_test.go:692:3: QF1002: could use tagged switch on r.URL.Path (staticcheck)
platform/wps-xiezuo/wpsxiezuo_test.go:754:3: QF1002: could use tagged switch on r.URL.Path (staticcheck)
platform/wps-xiezuo/wpsxiezuo_test.go:877:2: S1021: should merge variable declaration with assignment on next line (staticcheck)
platform/wps-xiezuo/wpsxiezuo_test.go:939:3: QF1002: could use tagged switch on r.URL.Path (staticcheck)

修复建议

  1. QF1002 (3 处):把 switch { case r.URL.Path == "/x": ... } 改为 switch r.URL.Path { case "/x": ... }(tagged switch)— 测试 fixture 里都是 path 字符串比较,改成 tagged form 可读性更高,也是 staticcheck 推荐写法
  2. S1021 (1 处):var srv *httptest.Server + 下一行 srv = httptest.NewServer(...) 合并为 srv := httptest.NewServer(...)

影响与下一步

  • 不阻塞 merge flow,但阻塞 review flow:CI 必须 5/5 green 后再做代码评审
  • 改动小,作者可以快速 rebase / amend 后重推,无需新 PR
  • 等 CI 5/5 green 后,我会进入完整 diff review(注意 PR 1101 行 / 4 文件,需要更多 token)

QA 决策报告: 待 CI 绿了再创建 doc-XXXXX-PR-1850-QA-...(避免在 lint fail 时记录"已批准",污染后续 re-review)

— qa-claudecode

@chenhg5 chenhg5 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conclusion: Approve

This is a thoughtful, well-tested feature addition for the wps-xiezuo platform. The diff demonstrates strong security awareness (size limits at multiple layers, path traversal sanitization, MIME detection with sensible fallback, explicit auth-signing test helper) and the documentation/config updates correctly guide users through the new WPS Open Platform permissions.

✅ What looks good

  • Defense in depth on file size: Three layers — declared size check, Content-Length pre-check, io.LimitReader + post-read validation with +1 byte probe. TestDownloadMessageResource_RejectsOversizeBody directly verifies the third layer with a 4-byte cap against a 5-byte body.
  • Path traversal protection: sanitizeAttachmentName uses filepath.Base, and TestHandleChatMessage_LocalFileAttachment proves it works (folder/report.txtreport.txt).
  • Authentication test coverage: The new assertWPSResourceAuth helper verifies both Authorization: Bearer <token> and X-Kso-Authorization: KSO-1 <appid>:<hmac-sha256> on every signed API call. This catches signing regressions across all four download paths.
  • Clean refactor: signWSHeadersignKSO1Header(method, requestURI, contentType, body) is the right extraction. It already accepts body []byte for SHA256 hashing, so the code is ready for future POST/PUT endpoints without further refactor. Also http.DefaultClientp.client() for connection reuse + timeout config is a correct hygiene fix across 4 call sites.
  • Graceful degradation: Cloud document with no token / no link_id falls back to forwarding just the link_url text (no crash, no content). TestHandleChatMessage_CloudDocumentForwardsLink covers this exact path.
  • Explicit auth marker for cloud docs: The [WPS云文档正文(已由应用授权读取,请优先基于以下正文回答,不要通过网页链接再次访问)] marker is the right pattern — it tells the agent that the content is application-authorized and discourages re-fetching via the login-protected web link (which would either fail or hit a different access path).

🔵 Optional nit (P3, non-blocking)

  • SSRF hardening: validateResourceDownloadURL requires http(s) scheme + non-empty host + no userinfo, but does not block private IP ranges (127.0.0.1, 169.254.169.254, 10.0.0.0/8). Since the URL comes from the WPS API (which the caller has already authenticated to with app_id + app_secret + bearer token), the practical attack surface is limited — but adding an IP/host denylist would be a worthwhile follow-up hardening for the entire platform/wps-xiezuo/ package. Not a blocker for this PR.
  • Hardcoded 50 MiB cap: defaultMaxAttachmentBytes = 50 * 1024 * 1024 is hardcoded. WPS allows up to 2 GiB. If users later want to handle larger files (datasets, archives), consider exposing max_attachment_bytes in config.example.toml for this platform. Not needed for this PR.

❓ Question

  • The new doc string [WPS云文档正文(已由应用授权读取,请优先基于以下正文回答,不要通过网页链接再次访问)] is hardcoded in two places: enrichCloudDocument (creates it) and appendCloudDocumentContent (checks for it). Is there a core-level helper for "marker-prefixed forwarded content" that I missed, or is this the right layer for it? Just asking because if the same pattern needs to apply to future platform adapters (e.g. Lark, DingTalk), centralizing it would reduce duplication.

Testing / Risk

  • CI: 5/5 SUCCESS (run 34972435417) — lint / unit-test / smoke-test / regression-test / performance-test all green
  • Merge state: CLEAN
  • Local verification: gofmt -l clean on changed files. Sandbox lacks vendored deps so I couldn't run go test -race locally, but the CI covers it.
  • Test quality: All 9 new tests use httptest.NewServer end-to-end, so they exercise real HTTP plumbing, JSON parsing, signing, and MIME detection together — not just isolated units. The assertWPSResourceAuth helper is the kind of cross-cutting assertion that catches signing regressions when someone later changes the KSO-1 scheme.
  • Remaining risk: minimal. Behavior change is scoped to the wps-xiezuo platform and only activates for the new message types (image, file, rich_text). Existing text-only path is unchanged. Single-workspace / other-platform users see zero impact.

Next step

Maintainer merge. No blockers, no required follow-ups from this PR.

@zy5669

zy5669 commented Sep 16, 2026

Copy link
Copy Markdown
Author

Conclusion: Approve

This is a thoughtful, well-tested feature addition for the wps-xiezuo platform. The diff demonstrates strong security awareness (size limits at multiple layers, path traversal sanitization, MIME detection with sensible fallback, explicit auth-signing test helper) and the documentation/config updates correctly guide users through the new WPS Open Platform permissions.

✅ What looks good

  • Defense in depth on file size: Three layers — declared size check, Content-Length pre-check, io.LimitReader + post-read validation with +1 byte probe. TestDownloadMessageResource_RejectsOversizeBody directly verifies the third layer with a 4-byte cap against a 5-byte body.
  • Path traversal protection: sanitizeAttachmentName uses filepath.Base, and TestHandleChatMessage_LocalFileAttachment proves it works (folder/report.txtreport.txt).
  • Authentication test coverage: The new assertWPSResourceAuth helper verifies both Authorization: Bearer <token> and X-Kso-Authorization: KSO-1 <appid>:<hmac-sha256> on every signed API call. This catches signing regressions across all four download paths.
  • Clean refactor: signWSHeadersignKSO1Header(method, requestURI, contentType, body) is the right extraction. It already accepts body []byte for SHA256 hashing, so the code is ready for future POST/PUT endpoints without further refactor. Also http.DefaultClientp.client() for connection reuse + timeout config is a correct hygiene fix across 4 call sites.
  • Graceful degradation: Cloud document with no token / no link_id falls back to forwarding just the link_url text (no crash, no content). TestHandleChatMessage_CloudDocumentForwardsLink covers this exact path.
  • Explicit auth marker for cloud docs: The [WPS云文档正文(已由应用授权读取,请优先基于以下正文回答,不要通过网页链接再次访问)] marker is the right pattern — it tells the agent that the content is application-authorized and discourages re-fetching via the login-protected web link (which would either fail or hit a different access path).

🔵 Optional nit (P3, non-blocking)

  • SSRF hardening: validateResourceDownloadURL requires http(s) scheme + non-empty host + no userinfo, but does not block private IP ranges (127.0.0.1, 169.254.169.254, 10.0.0.0/8). Since the URL comes from the WPS API (which the caller has already authenticated to with app_id + app_secret + bearer token), the practical attack surface is limited — but adding an IP/host denylist would be a worthwhile follow-up hardening for the entire platform/wps-xiezuo/ package. Not a blocker for this PR.
  • Hardcoded 50 MiB cap: defaultMaxAttachmentBytes = 50 * 1024 * 1024 is hardcoded. WPS allows up to 2 GiB. If users later want to handle larger files (datasets, archives), consider exposing max_attachment_bytes in config.example.toml for this platform. Not needed for this PR.

❓ Question

  • The new doc string [WPS云文档正文(已由应用授权读取,请优先基于以下正文回答,不要通过网页链接再次访问)] is hardcoded in two places: enrichCloudDocument (creates it) and appendCloudDocumentContent (checks for it). Is there a core-level helper for "marker-prefixed forwarded content" that I missed, or is this the right layer for it? Just asking because if the same pattern needs to apply to future platform adapters (e.g. Lark, DingTalk), centralizing it would reduce duplication.

Testing / Risk

  • CI: 5/5 SUCCESS (run 34972435417) — lint / unit-test / smoke-test / regression-test / performance-test all green
  • Merge state: CLEAN
  • Local verification: gofmt -l clean on changed files. Sandbox lacks vendored deps so I couldn't run go test -race locally, but the CI covers it.
  • Test quality: All 9 new tests use httptest.NewServer end-to-end, so they exercise real HTTP plumbing, JSON parsing, signing, and MIME detection together — not just isolated units. The assertWPSResourceAuth helper is the kind of cross-cutting assertion that catches signing regressions when someone later changes the KSO-1 scheme.
  • Remaining risk: minimal. Behavior change is scoped to the wps-xiezuo platform and only activates for the new message types (image, file, rich_text). Existing text-only path is unchanged. Single-workspace / other-platform users see zero impact.

Next step

Maintainer merge. No blockers, no required follow-ups from this PR.

(1)
Thanks for pointing this out. I verified the limits against the actual APIs, and these are two different upload flows.
For local chat attachments, /v7/chats/resources/upload rejects files over 50 MiB with 400424604 FileSizeExceedsLimit. Therefore, increasing max_attachment_bytes to 2 GiB would not work: it would only allow cc-connect to read a larger file into memory before WPS rejects it. I have kept the WPS chat-attachment limit at 50 MiB and made the configuration lowerable only.
The larger-file limit documented by WPS applies to the separate cloud-document/document-attachment upload flow, not the chat-resource upload endpoint.
For generated files larger than 50 MiB, the intended follow-up flow is:

  1. The agent checks the generated file size.
  2. The agent invokes the WPS Drive skill to upload it as a cloud file.
  3. The skill returns link_id and link_url.
  4. cc-connect sends a WPS cloud-file message to the current chat, with cloud.id set to link_id.
    That cloud-file delivery bridge is separate from the local attachment support in this PR.

(2)
There is no existing core-level helper for this pattern.
I think keeping it in platform/wps-xiezuo is the right layer for now because the marker describes WPS-specific behavior: the content was fetched through the authorized WPS cloud-document APIs, and the same WPS adapter uses the marker to avoid processing it again.
I did consolidate the duplicated literal into a package-level constant so creation and detection cannot drift apart.
If another adapter later needs the same provenance/forwarded-content behavior, I would prefer introducing a structured core-level contract at that point rather than moving this WPS-specific marker string into core.

@chenhg5 chenhg5 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QA Re-review — PR #1850 (head 26f7fce)

Two new commits since my last review (d64dabb + 35b7ac1 + 26f7fce). CI still 5/5 green, mergeStateStatus CLEAN.

Conclusion: still APPROVE. No new blockers introduced; in fact one of my prior P3 nits is now fixed.

What changed

  1. max_attachment_bytes config — replaces the hardcoded 50 MiB default with a user-tunable setting (default 2 GiB, hard cap 5 GiB). Strict parseWPSAttachmentLimit validates >0, <= 5 GiB, and accepts int/int8-64/uint8-64/float32-64/string input. This resolves the P3 nit I raised in the original review ("config layer didn't expose the limit") — thanks.
  2. Two-way file/image upload — implements core.ImageSender + core.FileSender. New SendFile / SendImage methods use a 2-step upload-then-message-create flow against WPS's /v7/chats/resources/uploadupload_entry URL → /v7/messages/create. Extracted createWPSMessage consolidates the KSO-1 signing path that was previously duplicated.
  3. messageContent.Text/Image/File are now pointers — JSON omitempty works correctly for image/file-only messages.
  4. checkWPSAPIResponse unifies response-code parsing — handles WPS API quirks where code is sometimes an int, sometimes a string (uses json.RawMessage + dual-decoder).
  5. wpsCloudDocumentMarker extracted to a const — fixes a brittle inline string that appeared in two places.
  6. core.RedactToken used in error messages — prevents bearer token leak to logs on non-200 responses.

✅ What looks good

  • 5 new tests cover the new code: TestNew_CustomAttachmentLimit, TestNew_RejectsAttachmentLimitAboveWPSBound, TestSendFile_UploadsResourceAndCreatesMessage, TestSendFile_RejectsConfiguredSizeLimit, TestSendImage_UploadsResourceAndCreatesImageMessage. TestPlatformImplementsInterfaces now asserts both core.ImageSender and core.FileSender.
  • validateResourceUploadURL mirrors the download SSRF guard — same scheme/host/userinfo constraints, parameterized by operation name.
  • getToken is mutex-guarded with expires_in caching — the double-call in SendFile (upload + create) is effectively free on the second hit.
  • normalizeWPSImageMIME whitelists only image/jpg|png|gif|webp — explicit reject of unknown image types rather than silent fallback to a generic image type.
  • The messageContent pointer refactor is the right call — without it, sending an image-only message would emit "text": {} and confuse the WPS server.
  • Documentation in config.example.toml and docs/wps-xiezuo.md is honest about the limit semantics: "This is an adapter-side bound, not a claim that the chat-resource API guarantees 5 GiB."

🟠 Should improve (not blocking)

  • In-memory upload for large files: core.FileAttachment.Data []byte and multipart.NewWriter over bytes.Buffer both hold the entire payload in RAM. With the new 5 GiB cap, a single SendFile call can reserve up to 5 GiB of heap. Realistic triggers are limited (agent send_file / send_image tool calls, cc-connect send CLI), and the failure mode (OOM kill) is observable rather than silent. But this is a real concern. Suggested follow-up: streaming upload with io.Reader + chunked PUT, or document the RAM cost loudly in user-facing docs.
  • 40× default-jump from 50 MiB to 2 GiB: previously anyone hitting the cap got a clear "exceeds limit" error. Now users will silently get larger uploads accepted up to 2 GiB; if WPS API itself rejects, the failure surfaces correctly but the user has no early warning. The config docs already flag this — good.

🔵 Optional / future

  • The SSRF doesn't block private IPs P3 nit from the original review still applies (127.0.0.1, 169.254.169.254). Author didn't address it. Threat model remains limited (attacker would need valid app_secret + bearer to inject a malicious URL), so still not blocking.
  • A test that exercises parseWPSAttachmentLimit with non-int types (float64 fractional, string with whitespace, zero, negative, exactly 5 GiB) would lock in the bounds. The unit tests cover happy path + over-limit only.

Testing / Risk

  • CI: 5/5 PASS. mergeStateStatus: CLEAN.
  • I matched the 5 new test names against the new code paths; the SendFile test verifies checksum (sha256) round-trip and KSO-1 signing, the SendImage test verifies image data round-trip and storage key propagation.

Next step

  • Approve for merge. Author addressed my prior P3 nit. New risks are bounded (in-memory upload for large files) but not blocking. Re-review addendum doc: doc-20260916-ssx61w.

— QA claude (cc-connect/qa-claudecode)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants