fix(msteams): download DM inline images via Graph API#52212
fix(msteams): download DM inline images via Graph API#52212BradGroux merged 1 commit intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR fixes three compounding bugs that prevented inline images from being downloaded in MS Teams 1:1 bot DM chats. Together the fixes wire up a complete Graph API fallback path: the conversation ID is now translated to the format the Graph API expects, the Graph fallback is now triggered even when mixed HTML/non-HTML attachments are present, and missing
Confidence Score: 5/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: extensions/msteams/src/monitor-handler/inbound-media.ts
Line: 55-59
Comment:
**Misleading variable name after `every` → `some` change**
The variable `onlyHtmlAttachments` no longer reflects its meaning. After the change to `some()`, the variable is `true` whenever *any* attachment is HTML — not only when all attachments are HTML. A future reader seeing `onlyHtmlAttachments` will expect an `every()`-style guard and may re-introduce the original bug.
```suggestion
attachments.some((att) => String(att.contentType ?? "").startsWith("text/html"));
if (onlyHtmlAttachments) {
```
Rename the variable and the condition name:
```
const hasAnyHtmlAttachment =
attachments.length > 0 &&
attachments.some((att) => String(att.contentType ?? "").startsWith("text/html"));
if (hasAnyHtmlAttachment) {
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(msteams): download DM inline images ..." | Re-trigger Greptile |
| const onlyHtmlAttachments = | ||
| attachments.length > 0 && | ||
| attachments.every((att) => String(att.contentType ?? "").startsWith("text/html")); | ||
| attachments.some((att) => String(att.contentType ?? "").startsWith("text/html")); | ||
|
|
||
| if (onlyHtmlAttachments) { |
There was a problem hiding this comment.
Misleading variable name after
every → some change
The variable onlyHtmlAttachments no longer reflects its meaning. After the change to some(), the variable is true whenever any attachment is HTML — not only when all attachments are HTML. A future reader seeing onlyHtmlAttachments will expect an every()-style guard and may re-introduce the original bug.
| const onlyHtmlAttachments = | |
| attachments.length > 0 && | |
| attachments.every((att) => String(att.contentType ?? "").startsWith("text/html")); | |
| attachments.some((att) => String(att.contentType ?? "").startsWith("text/html")); | |
| if (onlyHtmlAttachments) { | |
| attachments.some((att) => String(att.contentType ?? "").startsWith("text/html")); | |
| if (onlyHtmlAttachments) { |
Rename the variable and the condition name:
const hasAnyHtmlAttachment =
attachments.length > 0 &&
attachments.some((att) => String(att.contentType ?? "").startsWith("text/html"));
if (hasAnyHtmlAttachment) {
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/msteams/src/monitor-handler/inbound-media.ts
Line: 55-59
Comment:
**Misleading variable name after `every` → `some` change**
The variable `onlyHtmlAttachments` no longer reflects its meaning. After the change to `some()`, the variable is `true` whenever *any* attachment is HTML — not only when all attachments are HTML. A future reader seeing `onlyHtmlAttachments` will expect an `every()`-style guard and may re-introduce the original bug.
```suggestion
attachments.some((att) => String(att.contentType ?? "").startsWith("text/html"));
if (onlyHtmlAttachments) {
```
Rename the variable and the condition name:
```
const hasAnyHtmlAttachment =
attachments.length > 0 &&
attachments.some((att) => String(att.contentType ?? "").startsWith("text/html"));
if (hasAnyHtmlAttachment) {
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5466fb7c22
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const graphConversationId = | ||
| isDirectMessage && | ||
| conversationId.startsWith("a:") && | ||
| from.aadObjectId && | ||
| appId | ||
| ? `19:${from.aadObjectId}_${appId}@unq.gbl.spaces` | ||
| : conversationId; |
There was a problem hiding this comment.
Resolve DM Graph chat IDs instead of synthesizing them
For personal chats whose Graph ID is not exactly 19:{userAadId}_{appId}@unq.gbl.spaces, this branch still builds /chats/{chatId} URLs with the wrong ID, so inline-media fetches keep 404ing. We already treat these IDs as opaque elsewhere: extensions/msteams/src/graph-upload.ts resolves them via Graph because valid DM chat IDs can be @thread.tacv2 or other forms, and extensions/msteams/src/send.test.ts has fixtures for that. Reusing the existing resolver/cache here would avoid leaving those DMs broken.
Useful? React with 👍 / 👎.
| const ab = await valRes.arrayBuffer(); | ||
| buffer = Buffer.from(ab); |
There was a problem hiding this comment.
Enforce maxBytes before buffering hosted content values
This new $value path loads the entire hosted-content response into memory with arrayBuffer() and only rejects oversized files afterwards. In DM chats where Graph omits contentBytes for a large hosted file, the gateway now allocates the full payload before discarding it, which can spike RSS or crash the worker; the existing remote-media path in extensions/msteams/src/attachments/remote-media.ts avoids that by applying the size cap while reading.
Useful? React with 👍 / 👎.
5466fb7 to
4b54ca5
Compare
4b54ca5 to
f94392d
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f94392d329
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| return isDirectMessage && conversationId.startsWith("a:") && aadObjectId && appId | ||
| ? `19:${aadObjectId}_${appId}@unq.gbl.spaces` | ||
| : conversationId; |
There was a problem hiding this comment.
Resolve non-a: DM chat IDs before building Graph URLs
resolveMSTeamsInboundMedia() now feeds buildMSTeamsGraphMessageUrls() with the output of this helper, but this helper only rewrites Bot Framework IDs that start with a:. Our own Teams send path already treats DM conversation IDs as opaque: extensions/msteams/src/graph-upload.ts resolves them through Graph, and extensions/msteams/src/graph-upload.test.ts has an explicit 8:orgid:user-object-id fixture. For those DMs, this branch still builds /chats/8%3Aorgid.../messages/... URLs, so inline-media downloads remain broken. Fresh evidence: the repo already tests 8:orgid: as a valid personal-chat input in graph-upload.test.ts.
Useful? React with 👍 / 👎.
Three fixes for personal chat image downloads: 1. Translate Bot Framework conversation ID (a:xxx) to Graph format (19:[email protected]) 2. Trigger Graph fallback when any (not all) attachments are HTML 3. Fetch hosted content bytes from $value endpoint when not inline Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
f94392d to
e5dc963
Compare
Fix three bugs preventing inline image downloads in Teams 1:1 DM chats: wrong conversation ID format for Graph API, missing media URL extraction, and incorrect content type detection. Fixes #24797 Thanks @Ted-developer
Fix three bugs preventing inline image downloads in Teams 1:1 DM chats: wrong conversation ID format for Graph API, missing media URL extraction, and incorrect content type detection. Fixes openclaw#24797 Thanks @Ted-developer
Summary
Fixes #24797 — Images sent in personal (1:1) bot DM chats are never downloaded due to three bugs in the media download pipeline.
Changes
Bug 1: Wrong conversation ID format for Graph API
File:
extensions/msteams/src/monitor-handler/message-handler.tsBot Framework uses
a:xxxconversation IDs for personal chats, but the Graph API requires19:{userId}_{botAppId}@unq.gbl.spaces. Added translation before callingresolveMSTeamsInboundMedia.Bug 2: Graph fallback blocked by non-HTML attachments
File:
extensions/msteams/src/monitor-handler/inbound-media.tsChanged
every()tosome()— trigger Graph fallback when any attachment is HTML, not only when all are. Image messages include non-HTML metadata alongside HTML content.Bug 3: Hosted content bytes not returned inline
File:
extensions/msteams/src/attachments/graph.tsWhen
contentBytesis null in the/hostedContentslist response (common in bot DM chats), fall back to fetching binary data from the individual/hostedContents/{id}/$valueendpoint.Testing