4316dbd3be
- Extract shared splitMessage() into channels/utils.ts (dedup 4 adapters) - Add Slack user name resolution with caching (users.info API) - Add withRetry() with exponential backoff + jitter, isRetryable() filter - Wire retry config into ModelRouter.chat() (non-streaming only) - Add assembleSystemPrompt() multi-file template system (SOUL/AGENTS/IDENTITY/USER/TOOLS.md) - Add usage tracking accumulators in NativeAgent + AgentOrchestrator - Add estimateCost() with per-model pricing table - Add /usage TUI command with full usage report formatting - Add retrySchema and promptSchema to config schema Tests: 569 passing, typecheck clean
87 lines
3.2 KiB
TypeScript
87 lines
3.2 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { splitMessage } from './utils.js';
|
|
|
|
describe('splitMessage', () => {
|
|
it('returns single chunk for empty string', () => {
|
|
const result = splitMessage('', 100);
|
|
// empty string never enters the while loop → returns empty array
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it('returns single chunk when text is under maxLength', () => {
|
|
const result = splitMessage('hello world', 100);
|
|
expect(result).toEqual(['hello world']);
|
|
});
|
|
|
|
it('returns single chunk when text equals maxLength', () => {
|
|
const text = 'a'.repeat(50);
|
|
const result = splitMessage(text, 50);
|
|
expect(result).toEqual([text]);
|
|
});
|
|
|
|
it('splits at newline when possible', () => {
|
|
const text = 'line one\nline two\nline three';
|
|
// maxLength 18 → "line one\nline two\n" is 18 chars, lastIndexOf('\n', 18) = 17
|
|
const result = splitMessage(text, 18);
|
|
expect(result).toEqual(['line one\nline two', 'line three']);
|
|
});
|
|
|
|
it('splits at space when no newline available', () => {
|
|
const text = 'word1 word2 word3 word4';
|
|
// maxLength 12 → "word1 word2 " lastIndexOf(' ', 12) = 11
|
|
const result = splitMessage(text, 12);
|
|
expect(result[0]).toBe('word1 word2');
|
|
expect(result.length).toBeGreaterThanOrEqual(2);
|
|
});
|
|
|
|
it('hard-cuts when no whitespace available', () => {
|
|
const text = 'abcdefghijklmnop';
|
|
const result = splitMessage(text, 5);
|
|
expect(result[0]).toBe('abcde');
|
|
expect(result[1]).toBe('fghij');
|
|
expect(result[2]).toBe('klmno');
|
|
expect(result[3]).toBe('p');
|
|
});
|
|
|
|
it('produces multiple chunks for long text', () => {
|
|
const text = 'chunk one\nchunk two\nchunk three\nchunk four';
|
|
const result = splitMessage(text, 20);
|
|
expect(result.length).toBeGreaterThan(1);
|
|
// Every chunk respects the limit
|
|
for (const chunk of result) {
|
|
expect(chunk.length).toBeLessThanOrEqual(20);
|
|
}
|
|
});
|
|
|
|
it('preserves all content (joined chunks equal original minus trimmed whitespace)', () => {
|
|
const text = 'The quick brown fox jumps over the lazy dog. ' +
|
|
'Pack my box with five dozen liquor jugs. ' +
|
|
'How vexingly quick daft zebras jump.';
|
|
const result = splitMessage(text, 30);
|
|
|
|
// Reassemble: since trimStart() removes leading whitespace between chunks,
|
|
// we verify all words are preserved
|
|
const originalWords = text.split(/\s+/);
|
|
const resultWords = result.join(' ').split(/\s+/);
|
|
expect(resultWords).toEqual(originalWords);
|
|
});
|
|
|
|
it('prefers newline split over space split', () => {
|
|
// Place newline at a good position and space later
|
|
const text = 'first part\nsecond part of the message';
|
|
// maxLength 15: lastIndexOf('\n', 15) = 10, which is >= 15/2 = 7.5 → splits at newline
|
|
const result = splitMessage(text, 15);
|
|
expect(result[0]).toBe('first part');
|
|
});
|
|
|
|
it('falls back to space when newline is too early', () => {
|
|
// Newline at position 2, which is < maxLength/2 for maxLength=14
|
|
const text = 'ab\ncdefghij klmnopqrst';
|
|
// lastIndexOf('\n', 14) = 2, but 2 < 14/2=7 → falls back to space
|
|
// lastIndexOf(' ', 14) = 11, which is >= 7 → splits at space
|
|
const result = splitMessage(text, 14);
|
|
expect(result[0]).toBe('ab\ncdefghij');
|
|
expect(result[1]).toBe('klmnopqrst');
|
|
});
|
|
});
|