mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-15 05:45:20 +00:00
241 lines
6 KiB
TypeScript
241 lines
6 KiB
TypeScript
|
|
import { expect, test, vi } from 'vitest'
|
||
|
|
import { fileReferenceToString, getAnswerPartFromAssistantMessage, groupMessageIntoSteps } from './utils'
|
||
|
|
import { FILE_REFERENCE_REGEX, ANSWER_TAG } from './constants';
|
||
|
|
import { SBChatMessage, SBChatMessagePart } from './types';
|
||
|
|
|
||
|
|
// Mock the env module
|
||
|
|
vi.mock('@/env.mjs', () => ({
|
||
|
|
env: {
|
||
|
|
SOURCEBOT_CHAT_FILE_MAX_CHARACTERS: 4000,
|
||
|
|
}
|
||
|
|
}));
|
||
|
|
|
||
|
|
|
||
|
|
test('fileReferenceToString formats file references correctly', () => {
|
||
|
|
expect(fileReferenceToString({
|
||
|
|
fileName: 'auth.ts'
|
||
|
|
})).toBe('@file:{auth.ts}');
|
||
|
|
|
||
|
|
expect(fileReferenceToString({
|
||
|
|
fileName: 'auth.ts',
|
||
|
|
range: {
|
||
|
|
startLine: 45,
|
||
|
|
endLine: 60,
|
||
|
|
}
|
||
|
|
})).toBe('@file:{auth.ts:45-60}');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('fileReferenceToString matches FILE_REFERENCE_REGEX', () => {
|
||
|
|
expect(FILE_REFERENCE_REGEX.test(fileReferenceToString({
|
||
|
|
fileName: 'auth.ts'
|
||
|
|
}))).toBe(true);
|
||
|
|
|
||
|
|
FILE_REFERENCE_REGEX.lastIndex = 0;
|
||
|
|
expect(FILE_REFERENCE_REGEX.test(fileReferenceToString({
|
||
|
|
fileName: 'auth.ts',
|
||
|
|
range: {
|
||
|
|
startLine: 45,
|
||
|
|
endLine: 60,
|
||
|
|
}
|
||
|
|
}))).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('groupMessageIntoSteps returns an empty array when there are no parts', () => {
|
||
|
|
const parts: SBChatMessagePart[] = []
|
||
|
|
|
||
|
|
const steps = groupMessageIntoSteps(parts);
|
||
|
|
|
||
|
|
expect(steps).toEqual([]);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('groupMessageIntoSteps returns a single group when there is only one step-start part', () => {
|
||
|
|
const parts: SBChatMessagePart[] = [
|
||
|
|
{
|
||
|
|
type: 'step-start',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'text',
|
||
|
|
text: 'Hello, world!',
|
||
|
|
}
|
||
|
|
]
|
||
|
|
|
||
|
|
const steps = groupMessageIntoSteps(parts);
|
||
|
|
|
||
|
|
expect(steps).toEqual([
|
||
|
|
[
|
||
|
|
{
|
||
|
|
type: 'step-start',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'text',
|
||
|
|
text: 'Hello, world!',
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('groupMessageIntoSteps returns a multiple groups when there is multiple step-start parts', () => {
|
||
|
|
const parts: SBChatMessagePart[] = [
|
||
|
|
{
|
||
|
|
type: 'step-start',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'text',
|
||
|
|
text: 'Hello, world!',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'step-start',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'text',
|
||
|
|
text: 'Ok lets go',
|
||
|
|
},
|
||
|
|
]
|
||
|
|
|
||
|
|
const steps = groupMessageIntoSteps(parts);
|
||
|
|
|
||
|
|
expect(steps).toEqual([
|
||
|
|
[
|
||
|
|
{
|
||
|
|
type: 'step-start',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'text',
|
||
|
|
text: 'Hello, world!',
|
||
|
|
}
|
||
|
|
],
|
||
|
|
[
|
||
|
|
{
|
||
|
|
type: 'step-start',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'text',
|
||
|
|
text: 'Ok lets go',
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('groupMessageIntoSteps returns a single group when there is no step-start part', () => {
|
||
|
|
const parts: SBChatMessagePart[] = [
|
||
|
|
{
|
||
|
|
type: 'text',
|
||
|
|
text: 'Hello, world!',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'text',
|
||
|
|
text: 'Ok lets go',
|
||
|
|
},
|
||
|
|
]
|
||
|
|
|
||
|
|
const steps = groupMessageIntoSteps(parts);
|
||
|
|
|
||
|
|
expect(steps).toEqual([
|
||
|
|
[
|
||
|
|
{
|
||
|
|
type: 'text',
|
||
|
|
text: 'Hello, world!',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'text',
|
||
|
|
text: 'Ok lets go',
|
||
|
|
}
|
||
|
|
]
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('getAnswerPartFromAssistantMessage returns text part when it starts with ANSWER_TAG while not streaming', () => {
|
||
|
|
const message: SBChatMessage = {
|
||
|
|
role: 'assistant',
|
||
|
|
parts: [
|
||
|
|
{
|
||
|
|
type: 'text',
|
||
|
|
text: 'Some initial text'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'text',
|
||
|
|
text: `${ANSWER_TAG}This is the answer to your question.`
|
||
|
|
}
|
||
|
|
]
|
||
|
|
} as SBChatMessage;
|
||
|
|
|
||
|
|
const result = getAnswerPartFromAssistantMessage(message, false);
|
||
|
|
|
||
|
|
expect(result).toEqual({
|
||
|
|
type: 'text',
|
||
|
|
text: `${ANSWER_TAG}This is the answer to your question.`
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test('getAnswerPartFromAssistantMessage returns text part when it starts with ANSWER_TAG while streaming', () => {
|
||
|
|
const message: SBChatMessage = {
|
||
|
|
role: 'assistant',
|
||
|
|
parts: [
|
||
|
|
{
|
||
|
|
type: 'text',
|
||
|
|
text: 'Some initial text'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'text',
|
||
|
|
text: `${ANSWER_TAG}This is the answer to your question.`
|
||
|
|
}
|
||
|
|
]
|
||
|
|
} as SBChatMessage;
|
||
|
|
|
||
|
|
const result = getAnswerPartFromAssistantMessage(message, true);
|
||
|
|
|
||
|
|
expect(result).toEqual({
|
||
|
|
type: 'text',
|
||
|
|
text: `${ANSWER_TAG}This is the answer to your question.`
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test('getAnswerPartFromAssistantMessage returns last text part as fallback when not streaming and no ANSWER_TAG', () => {
|
||
|
|
const message: SBChatMessage = {
|
||
|
|
role: 'assistant',
|
||
|
|
parts: [
|
||
|
|
{
|
||
|
|
type: 'text',
|
||
|
|
text: 'First text part'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'tool-call',
|
||
|
|
id: 'call-1',
|
||
|
|
name: 'search',
|
||
|
|
args: {}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'text',
|
||
|
|
text: 'This is the last text part without answer tag'
|
||
|
|
}
|
||
|
|
]
|
||
|
|
} as SBChatMessage;
|
||
|
|
|
||
|
|
const result = getAnswerPartFromAssistantMessage(message, false);
|
||
|
|
|
||
|
|
expect(result).toEqual({
|
||
|
|
type: 'text',
|
||
|
|
text: 'This is the last text part without answer tag'
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test('getAnswerPartFromAssistantMessage returns undefined when streaming and no ANSWER_TAG', () => {
|
||
|
|
const message: SBChatMessage = {
|
||
|
|
role: 'assistant',
|
||
|
|
parts: [
|
||
|
|
{
|
||
|
|
type: 'text',
|
||
|
|
text: 'Some text without answer tag'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'text',
|
||
|
|
text: 'Another text part'
|
||
|
|
}
|
||
|
|
]
|
||
|
|
} as SBChatMessage;
|
||
|
|
|
||
|
|
const result = getAnswerPartFromAssistantMessage(message, true);
|
||
|
|
|
||
|
|
expect(result).toBeUndefined();
|
||
|
|
});
|