feat(core): add command, intent, and routing primitives
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { rankMessagesByImportance, scoreMessageImportance, selectImportantMessages } from './weighting.js';
|
||||
import type { Message } from '../models/types.js';
|
||||
|
||||
describe('weighting', () => {
|
||||
it('scores low-value chatter lower than preference and tool outcomes', () => {
|
||||
const chatter: Message = { role: 'user', content: 'hello there' };
|
||||
const preference: Message = { role: 'user', content: 'I prefer concise bullet points in responses.' };
|
||||
const outcome: Message = { role: 'assistant', content: 'Command succeeded with exit code 0. Output saved.' };
|
||||
|
||||
const chatterScore = scoreMessageImportance(chatter);
|
||||
const preferenceScore = scoreMessageImportance(preference);
|
||||
const outcomeScore = scoreMessageImportance(outcome);
|
||||
|
||||
expect(preferenceScore).toBeGreaterThan(chatterScore);
|
||||
expect(outcomeScore).toBeGreaterThan(chatterScore);
|
||||
});
|
||||
|
||||
it('applies recency as a small tie-break boost', () => {
|
||||
const messages: Message[] = [
|
||||
{ role: 'user', content: 'simple note' },
|
||||
{ role: 'user', content: 'simple note' },
|
||||
];
|
||||
|
||||
const ranked = rankMessagesByImportance(messages);
|
||||
expect(ranked[1].score).toBeGreaterThan(ranked[0].score);
|
||||
});
|
||||
|
||||
it('selects important messages above threshold and keeps original order', () => {
|
||||
const messages: Message[] = [
|
||||
{ role: 'user', content: 'hello' },
|
||||
{ role: 'user', content: 'I prefer markdown tables for reports.' },
|
||||
{ role: 'assistant', content: 'Tool result: command failed with exit code 1' },
|
||||
{ role: 'assistant', content: 'ok' },
|
||||
];
|
||||
|
||||
const selected = selectImportantMessages(messages, {
|
||||
threshold: 0.45,
|
||||
maxMessages: 4,
|
||||
});
|
||||
|
||||
expect(selected).toHaveLength(2);
|
||||
expect(selected[0].index).toBe(1);
|
||||
expect(selected[1].index).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import type { Message } from '../models/types.js';
|
||||
import { getMessageText } from '../models/media.js';
|
||||
|
||||
export interface WeightedMessage {
|
||||
index: number;
|
||||
message: Message;
|
||||
score: number;
|
||||
}
|
||||
|
||||
const TOOL_OUTCOME_PATTERN = /(tool|command|exit code|stack trace|traceback|error|failed|succeeded|output|result)/i;
|
||||
const CORRECTION_PATTERN = /(actually|correction|instead|sorry|to clarify|i meant|wrong)/i;
|
||||
const PREFERENCE_PATTERN = /(prefer|preference|always|never|please|timezone|call me|i like|i dislike|do not|don't)/i;
|
||||
|
||||
export function scoreMessageImportance(message: Message): number {
|
||||
const text = getMessageText(message).trim();
|
||||
if (text.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let score = 0.1;
|
||||
|
||||
if (TOOL_OUTCOME_PATTERN.test(text)) {
|
||||
score += 0.45;
|
||||
}
|
||||
if (CORRECTION_PATTERN.test(text)) {
|
||||
score += 0.35;
|
||||
}
|
||||
if (PREFERENCE_PATTERN.test(text)) {
|
||||
score += 0.4;
|
||||
}
|
||||
if (text.includes('```')) {
|
||||
score += 0.2;
|
||||
}
|
||||
|
||||
if (message.role === 'user') {
|
||||
score += 0.05;
|
||||
}
|
||||
|
||||
if (text.length > 240) {
|
||||
score += 0.1;
|
||||
}
|
||||
|
||||
return Math.max(0, Math.min(1, score));
|
||||
}
|
||||
|
||||
export function rankMessagesByImportance(messages: Message[]): WeightedMessage[] {
|
||||
return messages.map((message, index) => {
|
||||
const base = scoreMessageImportance(message);
|
||||
const recencyBoost = messages.length > 1 ? (index / (messages.length - 1)) * 0.08 : 0;
|
||||
return {
|
||||
index,
|
||||
message,
|
||||
score: Math.max(0, Math.min(1, base + recencyBoost)),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function selectImportantMessages(messages: Message[], opts: {
|
||||
threshold: number;
|
||||
maxMessages: number;
|
||||
}): WeightedMessage[] {
|
||||
if (messages.length === 0 || opts.maxMessages <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const ranked = rankMessagesByImportance(messages)
|
||||
.filter(item => item.score >= opts.threshold)
|
||||
.sort((a, b) => b.score - a.score || b.index - a.index)
|
||||
.slice(0, opts.maxMessages)
|
||||
.sort((a, b) => a.index - b.index);
|
||||
|
||||
return ranked;
|
||||
}
|
||||
Reference in New Issue
Block a user