97 lines
2.9 KiB
TypeScript
97 lines
2.9 KiB
TypeScript
|
|
import type { Message, MessageContent } from '../types';
|
||
|
|
import { mergeMessageContent } from '../../message-stream.mjs';
|
||
|
|
|
||
|
|
export function createUserMessage(
|
||
|
|
text: string,
|
||
|
|
options?: { agentText?: string; displayText?: string },
|
||
|
|
): Message {
|
||
|
|
const displayText = options?.displayText ?? text;
|
||
|
|
const agentText = options?.agentText ?? text;
|
||
|
|
return {
|
||
|
|
id: crypto.randomUUID(),
|
||
|
|
role: 'user',
|
||
|
|
created: Math.floor(Date.now() / 1000),
|
||
|
|
content: [{ type: 'text', text: agentText }],
|
||
|
|
metadata: {
|
||
|
|
userVisible: true,
|
||
|
|
agentVisible: true,
|
||
|
|
...(displayText !== agentText ? { displayText } : {}),
|
||
|
|
},
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getDisplayText(message: Message): string {
|
||
|
|
return (
|
||
|
|
message.metadata.displayText ??
|
||
|
|
getSystemNotificationText(message) ??
|
||
|
|
getVisibleText(message)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function pushMessage(messages: Message[], incoming: Message): Message[] {
|
||
|
|
const last = messages[messages.length - 1];
|
||
|
|
if (last?.id && incoming.id && last.id === incoming.id) {
|
||
|
|
return [
|
||
|
|
...messages.slice(0, -1),
|
||
|
|
{
|
||
|
|
...last,
|
||
|
|
content: mergeMessageContent(last.content, incoming.content) as MessageContent[],
|
||
|
|
},
|
||
|
|
];
|
||
|
|
}
|
||
|
|
return [...messages, incoming];
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getVisibleText(message: Message): string {
|
||
|
|
return message.content
|
||
|
|
.filter((c): c is Extract<MessageContent, { type: 'text' }> => c.type === 'text')
|
||
|
|
.map((c) => c.text)
|
||
|
|
.join('');
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getSystemNotificationText(message: Message): string | null {
|
||
|
|
const item = message.content.find(
|
||
|
|
(c): c is Extract<MessageContent, { type: 'systemNotification' }> =>
|
||
|
|
c.type === 'systemNotification',
|
||
|
|
);
|
||
|
|
return item?.msg ?? null;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function isCreditsExhaustedNotification(message: Message): boolean {
|
||
|
|
return message.content.some(
|
||
|
|
(c) => c.type === 'systemNotification' && c.notificationType === 'creditsExhausted',
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Goose surfaces upstream relay 500 as a visible assistant error message. */
|
||
|
|
export function isRelayServerErrorMessage(message: Message): boolean {
|
||
|
|
if (message.role !== 'assistant') return false;
|
||
|
|
const text = getVisibleText(message);
|
||
|
|
if (!text) return false;
|
||
|
|
return (
|
||
|
|
/Server error \(500 Internal Server Error\)/i.test(text) &&
|
||
|
|
/relay|andu\.tkmind\.cn/i.test(text)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getThinking(message: Message): string | null {
|
||
|
|
const parts = message.content
|
||
|
|
.filter((c): c is Extract<MessageContent, { type: 'thinking' }> => c.type === 'thinking')
|
||
|
|
.map((c) => c.thinking);
|
||
|
|
return parts.length > 0 ? parts.join('') : null;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getToolConfirmation(message: Message) {
|
||
|
|
const item = message.content.find(
|
||
|
|
(c): c is Extract<MessageContent, { type: 'actionRequired' }> =>
|
||
|
|
c.type === 'actionRequired' && c.data.actionType === 'toolConfirmation',
|
||
|
|
);
|
||
|
|
if (!item) return null;
|
||
|
|
return {
|
||
|
|
id: item.data.id,
|
||
|
|
toolName: item.data.toolName,
|
||
|
|
arguments: item.data.arguments,
|
||
|
|
prompt: item.data.prompt,
|
||
|
|
};
|
||
|
|
}
|