mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-16 09:31:37 +00:00
118 lines
3.8 KiB
TypeScript
118 lines
3.8 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import { appendAssistantTextPart, chatMessageText, renderMediaTags, toChatMessages, upsertToolPart } from './chat-messages'
|
|
|
|
describe('toChatMessages', () => {
|
|
it('keeps a turn with interleaved tool-only rows in a single bubble', () => {
|
|
const messages = toChatMessages([
|
|
{ role: 'assistant', content: 'Planning.', timestamp: 1 },
|
|
{
|
|
role: 'assistant',
|
|
content: '',
|
|
timestamp: 2,
|
|
tool_calls: [{ id: 'tc', function: { name: 'terminal', arguments: '{}' } }]
|
|
},
|
|
{ role: 'assistant', content: 'Done.', timestamp: 3 }
|
|
])
|
|
|
|
expect(messages).toHaveLength(1)
|
|
expect(messages[0].parts.map(p => p.type)).toEqual(['text', 'tool-call', 'text'])
|
|
expect(chatMessageText(messages[0])).toBe('Planning.Done.')
|
|
})
|
|
|
|
it('hides attached context payloads from user message display', () => {
|
|
const [message] = toChatMessages([
|
|
{
|
|
role: 'user',
|
|
content:
|
|
'what is this file\n\n--- Attached Context ---\n\n📄 @file:tsconfig.tsbuildinfo (981 tokens)\n```json\n{"root":["./src/main.tsx"]}\n```',
|
|
timestamp: 1
|
|
}
|
|
])
|
|
|
|
expect(chatMessageText(message)).toBe('@file:tsconfig.tsbuildinfo\n\nwhat is this file')
|
|
})
|
|
|
|
it('renders MEDIA tags as assistant attachment links', () => {
|
|
const [message] = toChatMessages([
|
|
{
|
|
role: 'assistant',
|
|
content: "MEDIA:/Users/brooklyn/.hermes/cache/audio/tts_20260501_222725.mp3\n\nhow's that sound?",
|
|
timestamp: 1
|
|
}
|
|
])
|
|
|
|
expect(chatMessageText(message)).toBe(
|
|
"[Audio: tts_20260501_222725.mp3](#media:%2FUsers%2Fbrooklyn%2F.hermes%2Fcache%2Faudio%2Ftts_20260501_222725.mp3)\n\nhow's that sound?"
|
|
)
|
|
})
|
|
|
|
it('coerces non-string message content without throwing', () => {
|
|
const [message] = toChatMessages([
|
|
{
|
|
content: {
|
|
text: 'hello from object content'
|
|
},
|
|
role: 'assistant',
|
|
timestamp: 1
|
|
}
|
|
])
|
|
|
|
expect(chatMessageText(message)).toBe('hello from object content')
|
|
})
|
|
|
|
it('applies attached-context filtering when user content is object-shaped', () => {
|
|
const [message] = toChatMessages([
|
|
{
|
|
content: {
|
|
text:
|
|
'look\n\n--- Attached Context ---\n\n📄 @file:foo.ts (10 tokens)\n```ts\nconst x = 1\n```'
|
|
},
|
|
role: 'user',
|
|
timestamp: 1
|
|
}
|
|
])
|
|
|
|
expect(chatMessageText(message)).toBe('@file:foo.ts\n\nlook')
|
|
})
|
|
})
|
|
|
|
describe('renderMediaTags', () => {
|
|
it('renders standalone and inline MEDIA tags as links', () => {
|
|
expect(renderMediaTags('here\nMEDIA:/tmp/voice.mp3\nthere')).toBe(
|
|
'here\n[Audio: voice.mp3](#media:%2Ftmp%2Fvoice.mp3)\nthere'
|
|
)
|
|
expect(renderMediaTags('audio: MEDIA:/tmp/voice.mp3 done')).toBe(
|
|
'audio: [Audio: voice.mp3](#media:%2Ftmp%2Fvoice.mp3) done'
|
|
)
|
|
expect(renderMediaTags('MEDIA:/tmp/demo.mp4')).toBe('[Video: demo.mp4](#media:%2Ftmp%2Fdemo.mp4)')
|
|
})
|
|
|
|
it('renders streamed assistant media once the tag is complete', () => {
|
|
const parts = appendAssistantTextPart(appendAssistantTextPart([], 'ok\nMEDIA:'), '/tmp/voice.mp3')
|
|
const text = chatMessageText({ id: 'a', role: 'assistant', parts })
|
|
|
|
expect(text).toBe('ok\n[Audio: voice.mp3](#media:%2Ftmp%2Fvoice.mp3)')
|
|
})
|
|
})
|
|
|
|
describe('upsertToolPart', () => {
|
|
it('preserves inline diffs from tool completion events', () => {
|
|
const parts = upsertToolPart(
|
|
[],
|
|
{
|
|
inline_diff: '--- a/foo.ts\n+++ b/foo.ts\n@@\n-old\n+new',
|
|
name: 'patch',
|
|
tool_id: 'tool-1'
|
|
},
|
|
'complete'
|
|
)
|
|
|
|
const [part] = parts
|
|
|
|
expect(part?.type).toBe('tool-call')
|
|
expect(part && 'result' in part ? part.result : undefined).toMatchObject({
|
|
inline_diff: '--- a/foo.ts\n+++ b/foo.ts\n@@\n-old\n+new'
|
|
})
|
|
})
|
|
})
|