OpenMAIC-React / src /lib /orchestration /summarizers /message-converter.ts
muthuk1's picture
Convert OpenMAIC from Next.js to React (Vite)
f56a29b verified
import type { StatelessChatRequest } from '@/lib/types/chat';
// ==================== Message Conversion ====================
/**
* Convert UI messages to OpenAI format
* Includes tool call information so the model knows what actions were taken
*/
export function convertMessagesToOpenAI(
messages: StatelessChatRequest['messages'],
currentAgentId?: string,
): Array<{ role: 'system' | 'user' | 'assistant'; content: string }> {
return messages
.filter((msg) => msg.role === 'user' || msg.role === 'assistant')
.map((msg) => {
if (msg.role === 'assistant') {
// Assistant messages use JSON array format to serve as few-shot examples
// that match the expected output format from the system prompt
const items: Array<{ type: string; [key: string]: string }> = [];
if (msg.parts) {
for (const part of msg.parts) {
const p = part as Record<string, unknown>;
if (p.type === 'text' && p.text) {
items.push({ type: 'text', content: p.text as string });
} else if ((p.type as string)?.startsWith('action-') && p.state === 'result') {
const actionName = (p.actionName ||
(p.type as string).replace('action-', '')) as string;
const output = p.output as Record<string, unknown> | undefined;
const isSuccess = output?.success === true;
const resultSummary = isSuccess
? output?.data
? `result: ${JSON.stringify(output.data).slice(0, 100)}`
: 'success'
: (output?.error as string) || 'failed';
items.push({
type: 'action',
name: actionName,
result: resultSummary,
});
}
}
}
const content = items.length > 0 ? JSON.stringify(items) : '';
const msgAgentId = msg.metadata?.agentId;
// When currentAgentId is provided and this message is from a DIFFERENT agent,
// convert to user role with agent name attribution
if (currentAgentId && msgAgentId && msgAgentId !== currentAgentId) {
const agentName = msg.metadata?.senderName || msgAgentId;
return {
role: 'user' as const,
content: content ? `[${agentName}]: ${content}` : '',
};
}
return {
role: 'assistant' as const,
content,
};
}
// User messages: keep plain text concatenation
const contentParts: string[] = [];
if (msg.parts) {
for (const part of msg.parts) {
const p = part as Record<string, unknown>;
if (p.type === 'text' && p.text) {
contentParts.push(p.text as string);
} else if ((p.type as string)?.startsWith('action-') && p.state === 'result') {
const actionName = (p.actionName ||
(p.type as string).replace('action-', '')) as string;
const output = p.output as Record<string, unknown> | undefined;
const isSuccess = output?.success === true;
const resultSummary = isSuccess
? output?.data
? `result: ${JSON.stringify(output.data).slice(0, 100)}`
: 'success'
: (output?.error as string) || 'failed';
contentParts.push(`[Action ${actionName}: ${resultSummary}]`);
}
}
}
// Extract speaker name from metadata (e.g. other agents' messages in discussion)
const senderName = msg.metadata?.senderName;
let content = contentParts.join('\n');
if (senderName) {
content = `[${senderName}]: ${content}`;
}
// Annotate interrupted messages so the LLM knows context was cut short
const isInterrupted =
(msg as unknown as Record<string, unknown>).metadata &&
((msg as unknown as Record<string, unknown>).metadata as Record<string, unknown>)
?.interrupted;
return {
role: 'user' as const,
content: isInterrupted
? `${content}\n[This response was interrupted — do NOT continue it. Start a new JSON array response.]`
: content,
};
})
.filter((msg) => {
// Drop empty messages and messages with only dots/ellipsis/whitespace
// (produced by failed agent streams)
const stripped = msg.content.replace(/[.\s…]+/g, '');
return stripped.length > 0;
});
}