/** * March Agent SDK - Message * Port of Python march_agent/message.py */ import { Conversation } from './conversation.js' import { Memory } from './memory.js' import { ConversationClient } from './conversation-client.js' import { MemoryClient } from './memory-client.js' import { AttachmentClient, createAttachmentInfo } from './attachment-client.js' import type { AttachmentInfo, KafkaHeaders } from './types.js' /** * Represents an incoming message to the agent. */ export class Message { readonly content: string readonly conversationId: string readonly userId: string readonly headers: Record readonly rawBody: Record readonly conversation?: Conversation readonly memory?: Memory readonly metadata?: Record readonly schema?: Record readonly attachment?: AttachmentInfo private readonly attachmentClient?: AttachmentClient constructor(options: { content: string conversationId: string userId: string headers: Record rawBody: Record conversation?: Conversation memory?: Memory metadata?: Record schema?: Record attachment?: AttachmentInfo attachmentClient?: AttachmentClient }) { this.content = options.content this.conversationId = options.conversationId this.userId = options.userId this.headers = options.headers this.rawBody = options.rawBody this.conversation = options.conversation this.memory = options.memory this.metadata = options.metadata this.schema = options.schema this.attachment = options.attachment this.attachmentClient = options.attachmentClient } /** * Create Message from Kafka message data. */ static fromKafkaMessage( body: Record, headers: KafkaHeaders, options: { conversationClient?: ConversationClient memoryClient?: MemoryClient attachmentClient?: AttachmentClient agentName?: string } = {} ): Message { const conversationId = headers.conversationId ?? '' const userId = headers.userId ?? 'anonymous' // Parse metadata from header let metadata: Record | undefined if (headers.messageMetadata) { try { metadata = JSON.parse(headers.messageMetadata) } catch { // Ignore parse errors } } // Parse schema from header let schema: Record | undefined if (headers.messageSchema) { try { schema = JSON.parse(headers.messageSchema) } catch { // Ignore parse errors } } // Parse attachment from header or body let attachment: AttachmentInfo | undefined if (headers.attachment) { try { const attachmentData = JSON.parse(headers.attachment) attachment = createAttachmentInfo(attachmentData) } catch { // Ignore parse errors } } // Fallback to body attachment if (!attachment && body.attachment) { try { attachment = createAttachmentInfo(body.attachment as Record) } catch { // Ignore parse errors } } // Create conversation helper let conversation: Conversation | undefined if (conversationId && options.conversationClient) { conversation = new Conversation( conversationId, options.conversationClient, options.agentName ) } // Create memory helper let memory: Memory | undefined if (options.memoryClient && userId && conversationId) { memory = new Memory(userId, conversationId, options.memoryClient) } return new Message({ content: (body.content as string) ?? '', conversationId, userId, headers: headers as Record, rawBody: body, conversation, memory, metadata, schema, attachment, attachmentClient: options.attachmentClient, }) } /** * Check if message has an attachment. */ hasAttachment(): boolean { return this.attachment !== undefined } /** * Download attachment as bytes (Buffer). * * @throws Error if no attachment is available */ async getAttachmentBytes(): Promise { if (!this.attachment) { throw new Error('No attachment available') } if (!this.attachmentClient) { throw new Error('AttachmentClient not available') } return this.attachmentClient.download(this.attachment.url) } /** * Get attachment as base64 string (for LLM vision APIs). * * @throws Error if no attachment is available */ async getAttachmentBase64(): Promise { if (!this.attachment) { throw new Error('No attachment available') } if (!this.attachmentClient) { throw new Error('AttachmentClient not available') } return this.attachmentClient.downloadAsBase64(this.attachment.url) } }