feat(channels): add structured attachment support for file handling

- Add Attachment interface with type, data, filePath, mimeType, fileName
- Resolve attachments in ChannelBase before prompting bridge
- Update DingTalk, Telegram, Weixin adapters to use structured attachments
- Clean up placeholder text when files are received
- Export Attachment type from base package index

This enables proper handling of images and files across all channels,
separating attachment metadata from message text.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
tanzhenxin 2026-03-27 14:50:24 +00:00
parent 3d24a9c3fe
commit 3e0f213ea3
6 changed files with 84 additions and 17 deletions

View file

@ -238,6 +238,28 @@ export abstract class ChannelBase {
promptText = `[Replying to: "${envelope.referencedText}"]\n\n${promptText}`;
}
// Resolve attachments: extract image for bridge, append file paths to text
let imageBase64 = envelope.imageBase64;
let imageMimeType = envelope.imageMimeType;
if (envelope.attachments?.length) {
const filePaths: string[] = [];
for (const att of envelope.attachments) {
if (att.type === 'image' && att.data && !imageBase64) {
imageBase64 = att.data;
imageMimeType = att.mimeType;
} else if (att.filePath) {
const label = att.type === 'file' ? 'file' : att.type;
const name = att.fileName ? ` "${att.fileName}"` : '';
filePaths.push(
`User sent a ${label}${name}. It has been saved to: ${att.filePath}`,
);
}
}
if (filePaths.length > 0) {
promptText = promptText + '\n\n' + filePaths.join('\n');
}
}
// Prepend channel instructions on first message of a session
if (this.config.instructions && !this.instructedSessions.has(sessionId)) {
promptText = `${this.config.instructions}\n\n${promptText}`;
@ -269,8 +291,8 @@ export abstract class ChannelBase {
try {
const response = await this.bridge.prompt(sessionId, promptText, {
imageBase64: envelope.imageBase64,
imageMimeType: envelope.imageMimeType,
imageBase64,
imageMimeType,
});
if (response) {

View file

@ -16,6 +16,7 @@ export { SenderGate } from './SenderGate.js';
export type { SenderCheckResult } from './SenderGate.js';
export { SessionRouter } from './SessionRouter.js';
export type {
Attachment,
BlockStreamingChunkConfig,
BlockStreamingCoalesceConfig,
ChannelConfig,

View file

@ -45,6 +45,19 @@ export interface ChannelConfig {
blockStreamingCoalesce?: BlockStreamingCoalesceConfig;
}
export interface Attachment {
/** Content category. */
type: 'image' | 'file' | 'audio' | 'video';
/** Base64-encoded data (for images or small files). */
data?: string;
/** Absolute path to a local file (for large files saved to disk). */
filePath?: string;
/** MIME type (e.g. "image/jpeg", "application/pdf"). */
mimeType: string;
/** Original file name from the platform. */
fileName?: string;
}
export interface Envelope {
channelName: string;
senderId: string;
@ -63,6 +76,8 @@ export interface Envelope {
imageBase64?: string;
/** MIME type for the image (e.g. "image/jpeg", "image/png"). */
imageMimeType?: string;
/** Structured attachments (images, files, audio, video). */
attachments?: Attachment[];
}
export interface SessionTarget {