fix(vision): fix Lark inline image parsing and MCP bridge read_image (#396)

* fix(vision): fix Lark inline image parsing and MCP bridge read_image

Two fixes for image reading through Lark channel with claude-cli provider:

1. Lark post messages use flat format {"content":[[...]]} but resolvePostElements
   only handled language-wrapped format {"zh_cn":{"content":[...]}}. Now checks
   flat format first, and skips non-map values in fallback iteration.

2. read_image tool failed via MCP bridge because bridge creates fresh HTTP context
   losing WithMediaImages. Fix: enrich <media:image> tags with path attribute
   (like documents), pass workspace through X-Workspace bridge header with HMAC,
   so read_image can load images from disk via path parameter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): add HMAC backward compat and guard workspace injection

- VerifyBridgeContext falls back to old signature format (without workspace)
  for sessions with pre-existing MCP configs, preventing 403 on server restart
- Workspace context injection now requires agent context (agentID/userID)
  to prevent unauthenticated path injection bypassing HMAC

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: viettranx <viettranx@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-03-23 23:11:33 +07:00
1 parent 462727ec8e
commit 7db55c67f6
7 files changed
+70 -31

No files matched your search

+13 -3
View File
@@ -100,13 +100,21 @@ func parseMessageContent(rawContent, messageType string) string {
}
// resolvePostElements extracts the flattened element list from a Lark post message JSON.
// Handles language variant lookup (zh_cn → en_us → any first key).
// Handles two formats:
// 1. Language-wrapped: {"zh_cn": {"title":"...", "content": [[...]]}}
// 2. Flat (no language key): {"title":"...", "content": [[...]]}
func resolvePostElements(rawContent string) []any {
var post map[string]any
if err := json.Unmarshal([]byte(rawContent), &post); err != nil {
return nil
}
// Format 2: flat — "content" at top level (no language wrapper).
if contentArr, ok := post["content"].([]any); ok {
return contentArr
}
// Format 1: language-wrapped — try known locales, then first map value.
var langContent any
for _, lang := range []string{"zh_cn", "en_us"} {
if lc, ok := post[lang]; ok {
@@ -116,8 +124,10 @@ func resolvePostElements(rawContent string) []any {
}
if langContent == nil {
for _, v := range post {
langContent = v
break
if _, ok := v.(map[string]any); ok {
langContent = v
break
}
}
}
if langContent == nil {