genai-lite 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -37
- package/dist/llm/LLMService.d.ts +29 -2
- package/dist/llm/LLMService.js +80 -36
- package/dist/llm/config.js +4 -4
- package/dist/llm/services/SettingsManager.js +17 -11
- package/dist/llm/types.d.ts +81 -22
- package/dist/prompting/parser.d.ts +2 -2
- package/dist/prompting/parser.js +2 -2
- package/package.json +1 -1
- package/dist/llm/LLMService.createMessages.test.d.ts +0 -4
- package/dist/llm/LLMService.createMessages.test.js +0 -364
- package/dist/llm/LLMService.original.d.ts +0 -147
- package/dist/llm/LLMService.original.js +0 -656
- package/dist/llm/LLMService.prepareMessage.test.d.ts +0 -1
- package/dist/llm/LLMService.prepareMessage.test.js +0 -303
- package/dist/llm/LLMService.presets.test.d.ts +0 -1
- package/dist/llm/LLMService.presets.test.js +0 -210
- package/dist/llm/LLMService.sendMessage.preset.test.d.ts +0 -1
- package/dist/llm/LLMService.sendMessage.preset.test.js +0 -153
- package/dist/llm/LLMService.test.d.ts +0 -1
- package/dist/llm/LLMService.test.js +0 -639
- package/dist/llm/clients/AnthropicClientAdapter.test.d.ts +0 -1
- package/dist/llm/clients/AnthropicClientAdapter.test.js +0 -273
- package/dist/llm/clients/GeminiClientAdapter.test.d.ts +0 -1
- package/dist/llm/clients/GeminiClientAdapter.test.js +0 -405
- package/dist/llm/clients/LlamaCppClientAdapter.test.d.ts +0 -1
- package/dist/llm/clients/LlamaCppClientAdapter.test.js +0 -447
- package/dist/llm/clients/LlamaCppServerClient.test.d.ts +0 -1
- package/dist/llm/clients/LlamaCppServerClient.test.js +0 -294
- package/dist/llm/clients/MockClientAdapter.test.d.ts +0 -1
- package/dist/llm/clients/MockClientAdapter.test.js +0 -250
- package/dist/llm/clients/OpenAIClientAdapter.test.d.ts +0 -1
- package/dist/llm/clients/OpenAIClientAdapter.test.js +0 -258
- package/dist/llm/clients/adapterErrorUtils.test.d.ts +0 -1
- package/dist/llm/clients/adapterErrorUtils.test.js +0 -123
- package/dist/llm/config.test.d.ts +0 -1
- package/dist/llm/config.test.js +0 -176
- package/dist/llm/services/AdapterRegistry.test.d.ts +0 -1
- package/dist/llm/services/AdapterRegistry.test.js +0 -239
- package/dist/llm/services/ModelResolver.test.d.ts +0 -1
- package/dist/llm/services/ModelResolver.test.js +0 -179
- package/dist/llm/services/PresetManager.test.d.ts +0 -1
- package/dist/llm/services/PresetManager.test.js +0 -210
- package/dist/llm/services/RequestValidator.test.d.ts +0 -1
- package/dist/llm/services/RequestValidator.test.js +0 -159
- package/dist/llm/services/SettingsManager.test.d.ts +0 -1
- package/dist/llm/services/SettingsManager.test.js +0 -266
- package/dist/prompting/builder.d.ts +0 -38
- package/dist/prompting/builder.js +0 -63
- package/dist/prompting/builder.test.d.ts +0 -4
- package/dist/prompting/builder.test.js +0 -109
- package/dist/prompting/content.test.d.ts +0 -4
- package/dist/prompting/content.test.js +0 -212
- package/dist/prompting/parser.test.d.ts +0 -4
- package/dist/prompting/parser.test.js +0 -464
- package/dist/prompting/template.test.d.ts +0 -1
- package/dist/prompting/template.test.js +0 -250
- package/dist/providers/fromEnvironment.test.d.ts +0 -1
- package/dist/providers/fromEnvironment.test.js +0 -59
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Tests for content preparation utilities
|
|
4
|
-
*/
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const content_1 = require("./content");
|
|
7
|
-
describe('Content Utilities', () => {
|
|
8
|
-
describe('countTokens', () => {
|
|
9
|
-
it('should return 0 for empty string', () => {
|
|
10
|
-
expect((0, content_1.countTokens)('')).toBe(0);
|
|
11
|
-
});
|
|
12
|
-
it('should count tokens for simple text', () => {
|
|
13
|
-
const text = 'Hello, world!';
|
|
14
|
-
const count = (0, content_1.countTokens)(text);
|
|
15
|
-
expect(count).toBeGreaterThan(0);
|
|
16
|
-
expect(count).toBeLessThan(text.length); // Tokens are typically fewer than characters
|
|
17
|
-
});
|
|
18
|
-
it('should count tokens with default gpt-4 model', () => {
|
|
19
|
-
const text = 'The quick brown fox jumps over the lazy dog';
|
|
20
|
-
const count = (0, content_1.countTokens)(text);
|
|
21
|
-
expect(count).toBeGreaterThan(0);
|
|
22
|
-
});
|
|
23
|
-
it('should count tokens with different models', () => {
|
|
24
|
-
const text = 'Testing different models';
|
|
25
|
-
const gpt4Count = (0, content_1.countTokens)(text, 'gpt-4');
|
|
26
|
-
const gpt35Count = (0, content_1.countTokens)(text, 'gpt-3.5-turbo');
|
|
27
|
-
expect(gpt4Count).toBeGreaterThan(0);
|
|
28
|
-
expect(gpt35Count).toBeGreaterThan(0);
|
|
29
|
-
});
|
|
30
|
-
it('should handle special characters and emojis', () => {
|
|
31
|
-
const text = '🚀 Special chars: @#$% and \n\t newlines';
|
|
32
|
-
const count = (0, content_1.countTokens)(text);
|
|
33
|
-
expect(count).toBeGreaterThan(0);
|
|
34
|
-
});
|
|
35
|
-
it('should fallback to estimate for invalid model', () => {
|
|
36
|
-
const text = 'Test fallback behavior';
|
|
37
|
-
const count = (0, content_1.countTokens)(text, 'invalid-model');
|
|
38
|
-
// Should fallback to length/4 estimate
|
|
39
|
-
expect(count).toBe(Math.ceil(text.length / 4));
|
|
40
|
-
});
|
|
41
|
-
it('should handle very long text', () => {
|
|
42
|
-
const longText = 'a'.repeat(10000);
|
|
43
|
-
const count = (0, content_1.countTokens)(longText);
|
|
44
|
-
expect(count).toBeGreaterThan(0);
|
|
45
|
-
expect(count).toBeLessThan(longText.length);
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
describe('getSmartPreview', () => {
|
|
49
|
-
const config = { minLines: 5, maxLines: 10 };
|
|
50
|
-
it('should return full content if shorter than maxLines', () => {
|
|
51
|
-
const content = 'Line 1\nLine 2\nLine 3';
|
|
52
|
-
const preview = (0, content_1.getSmartPreview)(content, config);
|
|
53
|
-
expect(preview).toBe(content);
|
|
54
|
-
});
|
|
55
|
-
it('should truncate at maxLines if no empty lines found', () => {
|
|
56
|
-
const lines = Array.from({ length: 20 }, (_, i) => `Line ${i + 1}`);
|
|
57
|
-
const content = lines.join('\n');
|
|
58
|
-
const preview = (0, content_1.getSmartPreview)(content, config);
|
|
59
|
-
const previewLines = preview.split('\n');
|
|
60
|
-
// Should extend up to maxLines when no empty lines are found
|
|
61
|
-
expect(previewLines.length).toBe(config.maxLines + 1); // +1 for truncation message
|
|
62
|
-
expect(preview).toContain('... (content truncated)');
|
|
63
|
-
});
|
|
64
|
-
it('should extend to next empty line within maxLines', () => {
|
|
65
|
-
const content = `Line 1
|
|
66
|
-
Line 2
|
|
67
|
-
Line 3
|
|
68
|
-
Line 4
|
|
69
|
-
Line 5
|
|
70
|
-
Line 6
|
|
71
|
-
|
|
72
|
-
Line 8
|
|
73
|
-
Line 9
|
|
74
|
-
Line 10
|
|
75
|
-
Line 11`;
|
|
76
|
-
const preview = (0, content_1.getSmartPreview)(content, config);
|
|
77
|
-
const previewLines = preview.split('\n');
|
|
78
|
-
// Should include up to line 7 (the empty line)
|
|
79
|
-
expect(previewLines[6]).toBe('');
|
|
80
|
-
expect(preview).toContain('... (content truncated)');
|
|
81
|
-
});
|
|
82
|
-
it('should handle content exactly at maxLines', () => {
|
|
83
|
-
const lines = Array.from({ length: config.maxLines }, (_, i) => `Line ${i + 1}`);
|
|
84
|
-
const content = lines.join('\n');
|
|
85
|
-
const preview = (0, content_1.getSmartPreview)(content, config);
|
|
86
|
-
expect(preview).toBe(content);
|
|
87
|
-
});
|
|
88
|
-
it('should handle empty content', () => {
|
|
89
|
-
const preview = (0, content_1.getSmartPreview)('', config);
|
|
90
|
-
expect(preview).toBe('');
|
|
91
|
-
});
|
|
92
|
-
it('should handle content with multiple consecutive empty lines', () => {
|
|
93
|
-
const content = `Line 1
|
|
94
|
-
Line 2
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
Line 5
|
|
98
|
-
Line 6
|
|
99
|
-
|
|
100
|
-
Line 8
|
|
101
|
-
Line 9
|
|
102
|
-
Line 10
|
|
103
|
-
Line 11`;
|
|
104
|
-
const preview = (0, content_1.getSmartPreview)(content, config);
|
|
105
|
-
const previewLines = preview.split('\n');
|
|
106
|
-
// Should stop at first empty line after minLines
|
|
107
|
-
expect(previewLines.length).toBeLessThanOrEqual(config.maxLines + 1);
|
|
108
|
-
expect(preview).toContain('... (content truncated)');
|
|
109
|
-
});
|
|
110
|
-
it('should respect maxLines limit even with empty lines', () => {
|
|
111
|
-
const lines = Array.from({ length: 15 }, (_, i) => i % 3 === 0 ? '' : `Line ${i + 1}`);
|
|
112
|
-
const content = lines.join('\n');
|
|
113
|
-
const preview = (0, content_1.getSmartPreview)(content, config);
|
|
114
|
-
const previewLines = preview.split('\n');
|
|
115
|
-
expect(previewLines.length).toBeLessThanOrEqual(config.maxLines + 1);
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
describe('extractRandomVariables', () => {
|
|
119
|
-
it('should extract and flatten random variables', () => {
|
|
120
|
-
const content = `
|
|
121
|
-
<RANDOM_GREETING>Hello</RANDOM_GREETING>
|
|
122
|
-
<RANDOM_GREETING>Hi</RANDOM_GREETING>
|
|
123
|
-
<RANDOM_GREETING>Hey</RANDOM_GREETING>
|
|
124
|
-
<RANDOM_FAREWELL>Goodbye</RANDOM_FAREWELL>
|
|
125
|
-
<RANDOM_FAREWELL>See you</RANDOM_FAREWELL>
|
|
126
|
-
`;
|
|
127
|
-
const result = (0, content_1.extractRandomVariables)(content, { maxPerTag: 3 });
|
|
128
|
-
// Check that we have the right keys
|
|
129
|
-
expect(Object.keys(result)).toContain('random_greeting_1');
|
|
130
|
-
expect(Object.keys(result)).toContain('random_greeting_2');
|
|
131
|
-
expect(Object.keys(result)).toContain('random_greeting_3');
|
|
132
|
-
expect(Object.keys(result)).toContain('random_farewell_1');
|
|
133
|
-
expect(Object.keys(result)).toContain('random_farewell_2');
|
|
134
|
-
expect(Object.keys(result)).toContain('random_farewell_3');
|
|
135
|
-
// Check that values are from the original set (order is random)
|
|
136
|
-
const greetings = [result.random_greeting_1, result.random_greeting_2, result.random_greeting_3];
|
|
137
|
-
expect(greetings).toContain('Hello');
|
|
138
|
-
expect(greetings).toContain('Hi');
|
|
139
|
-
expect(greetings).toContain('Hey');
|
|
140
|
-
const farewells = [result.random_farewell_1, result.random_farewell_2];
|
|
141
|
-
expect(farewells).toContain('Goodbye');
|
|
142
|
-
expect(farewells).toContain('See you');
|
|
143
|
-
// Third farewell should be empty
|
|
144
|
-
expect(result.random_farewell_3).toBe('');
|
|
145
|
-
});
|
|
146
|
-
it('should respect maxPerTag option', () => {
|
|
147
|
-
const content = `
|
|
148
|
-
<RANDOM_EXAMPLE>Example 1</RANDOM_EXAMPLE>
|
|
149
|
-
<RANDOM_EXAMPLE>Example 2</RANDOM_EXAMPLE>
|
|
150
|
-
<RANDOM_EXAMPLE>Example 3</RANDOM_EXAMPLE>
|
|
151
|
-
<RANDOM_EXAMPLE>Example 4</RANDOM_EXAMPLE>
|
|
152
|
-
<RANDOM_EXAMPLE>Example 5</RANDOM_EXAMPLE>
|
|
153
|
-
`;
|
|
154
|
-
const result = (0, content_1.extractRandomVariables)(content, { maxPerTag: 2 });
|
|
155
|
-
expect(Object.keys(result)).toHaveLength(2);
|
|
156
|
-
expect(result.random_example_1).toBeTruthy();
|
|
157
|
-
expect(result.random_example_2).toBeTruthy();
|
|
158
|
-
expect(result.random_example_3).toBeUndefined();
|
|
159
|
-
});
|
|
160
|
-
it('should handle content with no random tags', () => {
|
|
161
|
-
const content = 'This is just regular content without any random tags.';
|
|
162
|
-
const result = (0, content_1.extractRandomVariables)(content);
|
|
163
|
-
expect(result).toEqual({});
|
|
164
|
-
});
|
|
165
|
-
it('should handle multiple tag types', () => {
|
|
166
|
-
const content = `
|
|
167
|
-
<RANDOM_COLOR>red</RANDOM_COLOR>
|
|
168
|
-
<RANDOM_COLOR>blue</RANDOM_COLOR>
|
|
169
|
-
<RANDOM_ANIMAL>cat</RANDOM_ANIMAL>
|
|
170
|
-
<RANDOM_ANIMAL>dog</RANDOM_ANIMAL>
|
|
171
|
-
<RANDOM_FOOD>pizza</RANDOM_FOOD>
|
|
172
|
-
`;
|
|
173
|
-
const result = (0, content_1.extractRandomVariables)(content, { maxPerTag: 2 });
|
|
174
|
-
// Check we have entries for each type
|
|
175
|
-
expect(Object.keys(result)).toContain('random_color_1');
|
|
176
|
-
expect(Object.keys(result)).toContain('random_color_2');
|
|
177
|
-
expect(Object.keys(result)).toContain('random_animal_1');
|
|
178
|
-
expect(Object.keys(result)).toContain('random_animal_2');
|
|
179
|
-
expect(Object.keys(result)).toContain('random_food_1');
|
|
180
|
-
expect(Object.keys(result)).toContain('random_food_2');
|
|
181
|
-
// Check food has one value and one empty
|
|
182
|
-
expect(result.random_food_1).toBe('pizza');
|
|
183
|
-
expect(result.random_food_2).toBe('');
|
|
184
|
-
});
|
|
185
|
-
it('should use default maxPerTag of 30', () => {
|
|
186
|
-
const content = '<RANDOM_TEST>value</RANDOM_TEST>';
|
|
187
|
-
const result = (0, content_1.extractRandomVariables)(content);
|
|
188
|
-
// Should have 30 entries
|
|
189
|
-
expect(Object.keys(result)).toHaveLength(30);
|
|
190
|
-
expect(result.random_test_1).toBe('value');
|
|
191
|
-
expect(result.random_test_30).toBe('');
|
|
192
|
-
});
|
|
193
|
-
it('should handle multiline content in random tags', () => {
|
|
194
|
-
const content = `
|
|
195
|
-
<RANDOM_EXAMPLE>
|
|
196
|
-
This is a multiline
|
|
197
|
-
example with several
|
|
198
|
-
lines of text
|
|
199
|
-
</RANDOM_EXAMPLE>
|
|
200
|
-
<RANDOM_EXAMPLE>Single line</RANDOM_EXAMPLE>
|
|
201
|
-
`;
|
|
202
|
-
const result = (0, content_1.extractRandomVariables)(content, { maxPerTag: 2 });
|
|
203
|
-
const values = [result.random_example_1, result.random_example_2];
|
|
204
|
-
expect(values).toContain('Single line');
|
|
205
|
-
expect(values.some(v => v.includes('multiline'))).toBe(true);
|
|
206
|
-
});
|
|
207
|
-
it('should handle empty string input', () => {
|
|
208
|
-
const result = (0, content_1.extractRandomVariables)('');
|
|
209
|
-
expect(result).toEqual({});
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
});
|
|
@@ -1,464 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Tests for response parsing utilities
|
|
4
|
-
*/
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const parser_1 = require("./parser");
|
|
7
|
-
describe('parseStructuredContent', () => {
|
|
8
|
-
it('should parse properly closed tags', () => {
|
|
9
|
-
const content = `
|
|
10
|
-
<THOUGHT>I need to analyze this problem</THOUGHT>
|
|
11
|
-
<PLAN>First, I'll break it down into steps</PLAN>
|
|
12
|
-
<CODE>console.log('Hello, world!');</CODE>
|
|
13
|
-
`;
|
|
14
|
-
const result = (0, parser_1.parseStructuredContent)(content, ['THOUGHT', 'PLAN', 'CODE']);
|
|
15
|
-
expect(result.THOUGHT).toBe('I need to analyze this problem');
|
|
16
|
-
expect(result.PLAN).toBe("First, I'll break it down into steps");
|
|
17
|
-
expect(result.CODE).toBe("console.log('Hello, world!');");
|
|
18
|
-
});
|
|
19
|
-
it('should handle unclosed tags', () => {
|
|
20
|
-
const content = `
|
|
21
|
-
<FIRST>Content 1
|
|
22
|
-
<SECOND>Content 2
|
|
23
|
-
<THIRD>Content 3
|
|
24
|
-
`;
|
|
25
|
-
const result = (0, parser_1.parseStructuredContent)(content, ['FIRST', 'SECOND', 'THIRD']);
|
|
26
|
-
expect(result.FIRST).toBe('Content 1');
|
|
27
|
-
expect(result.SECOND).toBe('Content 2');
|
|
28
|
-
expect(result.THIRD).toBe('Content 3');
|
|
29
|
-
});
|
|
30
|
-
it('should handle missing tags', () => {
|
|
31
|
-
const content = `
|
|
32
|
-
<PRESENT>This tag exists</PRESENT>
|
|
33
|
-
Some other content here
|
|
34
|
-
`;
|
|
35
|
-
const result = (0, parser_1.parseStructuredContent)(content, ['PRESENT', 'MISSING', 'ALSOBISSING']);
|
|
36
|
-
expect(result.PRESENT).toBe('This tag exists');
|
|
37
|
-
expect(result.MISSING).toBe('');
|
|
38
|
-
expect(result.ALSOBISSING).toBe('');
|
|
39
|
-
});
|
|
40
|
-
it('should handle extra text outside tags', () => {
|
|
41
|
-
const content = `
|
|
42
|
-
Some preamble text
|
|
43
|
-
<TAG1>Content 1</TAG1>
|
|
44
|
-
Some middle text
|
|
45
|
-
<TAG2>Content 2</TAG2>
|
|
46
|
-
Some ending text
|
|
47
|
-
`;
|
|
48
|
-
const result = (0, parser_1.parseStructuredContent)(content, ['TAG1', 'TAG2']);
|
|
49
|
-
expect(result.TAG1).toBe('Content 1');
|
|
50
|
-
expect(result.TAG2).toBe('Content 2');
|
|
51
|
-
});
|
|
52
|
-
it('should extract content up to the next tag for unclosed tags', () => {
|
|
53
|
-
const content = `
|
|
54
|
-
<STEP1>First step content
|
|
55
|
-
<STEP2>Second step content
|
|
56
|
-
<STEP3>Third step content</STEP3>
|
|
57
|
-
`;
|
|
58
|
-
const result = (0, parser_1.parseStructuredContent)(content, ['STEP1', 'STEP2', 'STEP3']);
|
|
59
|
-
expect(result.STEP1).toBe('First step content');
|
|
60
|
-
expect(result.STEP2).toBe('Second step content');
|
|
61
|
-
expect(result.STEP3).toBe('Third step content');
|
|
62
|
-
});
|
|
63
|
-
it('should handle empty tag array', () => {
|
|
64
|
-
const content = '<TAG>Some content</TAG>';
|
|
65
|
-
const result = (0, parser_1.parseStructuredContent)(content, []);
|
|
66
|
-
expect(result).toEqual({});
|
|
67
|
-
});
|
|
68
|
-
it('should handle multiline content with code', () => {
|
|
69
|
-
const content = `
|
|
70
|
-
<ANALYSIS>
|
|
71
|
-
The code has several issues:
|
|
72
|
-
1. Memory leak
|
|
73
|
-
2. Performance problems
|
|
74
|
-
</ANALYSIS>
|
|
75
|
-
<SOLUTION>
|
|
76
|
-
function fixed() {
|
|
77
|
-
// Clean up resources
|
|
78
|
-
return result;
|
|
79
|
-
}
|
|
80
|
-
</SOLUTION>
|
|
81
|
-
`;
|
|
82
|
-
const result = (0, parser_1.parseStructuredContent)(content, ['ANALYSIS', 'SOLUTION']);
|
|
83
|
-
expect(result.ANALYSIS).toContain('Memory leak');
|
|
84
|
-
expect(result.ANALYSIS).toContain('Performance problems');
|
|
85
|
-
expect(result.SOLUTION).toContain('function fixed()');
|
|
86
|
-
expect(result.SOLUTION).toContain('Clean up resources');
|
|
87
|
-
});
|
|
88
|
-
it('should handle tags that appear multiple times (only first occurrence)', () => {
|
|
89
|
-
const content = `
|
|
90
|
-
<ITEM>First item</ITEM>
|
|
91
|
-
<ITEM>Second item</ITEM>
|
|
92
|
-
<ITEM>Third item</ITEM>
|
|
93
|
-
`;
|
|
94
|
-
const result = (0, parser_1.parseStructuredContent)(content, ['ITEM']);
|
|
95
|
-
expect(result.ITEM).toBe('First item');
|
|
96
|
-
});
|
|
97
|
-
it('should preserve order of extraction based on tags array', () => {
|
|
98
|
-
const content = `
|
|
99
|
-
<LAST>Last content</LAST>
|
|
100
|
-
<MIDDLE>Middle content</MIDDLE>
|
|
101
|
-
<FIRST>First content</FIRST>
|
|
102
|
-
`;
|
|
103
|
-
const result = (0, parser_1.parseStructuredContent)(content, ['FIRST', 'MIDDLE', 'LAST']);
|
|
104
|
-
// Even though LAST appears first in content, it should still extract correctly
|
|
105
|
-
expect(result.FIRST).toBe('First content');
|
|
106
|
-
expect(result.MIDDLE).toBe('Middle content');
|
|
107
|
-
expect(result.LAST).toBe('Last content');
|
|
108
|
-
});
|
|
109
|
-
it('should handle empty content', () => {
|
|
110
|
-
const result = (0, parser_1.parseStructuredContent)('', ['TAG1', 'TAG2']);
|
|
111
|
-
expect(result).toEqual({
|
|
112
|
-
TAG1: '',
|
|
113
|
-
TAG2: ''
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
describe('extractInitialTaggedContent', () => {
|
|
118
|
-
it('should extract content from a tag at the beginning', () => {
|
|
119
|
-
const content = '<thinking>I am thinking about this problem.</thinking>Here is the answer.';
|
|
120
|
-
const result = (0, parser_1.extractInitialTaggedContent)(content, 'thinking');
|
|
121
|
-
expect(result.extracted).toBe('I am thinking about this problem.');
|
|
122
|
-
expect(result.remaining).toBe('Here is the answer.');
|
|
123
|
-
});
|
|
124
|
-
it('should handle leading whitespace', () => {
|
|
125
|
-
const content = ' \n <thinking>My thoughts here</thinking>The actual response';
|
|
126
|
-
const result = (0, parser_1.extractInitialTaggedContent)(content, 'thinking');
|
|
127
|
-
expect(result.extracted).toBe('My thoughts here');
|
|
128
|
-
expect(result.remaining).toBe('The actual response');
|
|
129
|
-
});
|
|
130
|
-
it('should not extract when tag is in the middle', () => {
|
|
131
|
-
const content = 'Some preamble <thinking>This is in the middle</thinking> and more text';
|
|
132
|
-
const result = (0, parser_1.extractInitialTaggedContent)(content, 'thinking');
|
|
133
|
-
expect(result.extracted).toBeNull();
|
|
134
|
-
expect(result.remaining).toBe('Some preamble <thinking>This is in the middle</thinking> and more text');
|
|
135
|
-
});
|
|
136
|
-
it('should handle no tag present', () => {
|
|
137
|
-
const content = 'This is just regular text without any special tags.';
|
|
138
|
-
const result = (0, parser_1.extractInitialTaggedContent)(content, 'thinking');
|
|
139
|
-
expect(result.extracted).toBeNull();
|
|
140
|
-
expect(result.remaining).toBe('This is just regular text without any special tags.');
|
|
141
|
-
});
|
|
142
|
-
it('should work with different tag names', () => {
|
|
143
|
-
const content = '<scratchpad>Working through the logic...</scratchpad>Final answer is 42.';
|
|
144
|
-
const result = (0, parser_1.extractInitialTaggedContent)(content, 'scratchpad');
|
|
145
|
-
expect(result.extracted).toBe('Working through the logic...');
|
|
146
|
-
expect(result.remaining).toBe('Final answer is 42.');
|
|
147
|
-
});
|
|
148
|
-
it('should handle unclosed tag at start', () => {
|
|
149
|
-
const content = '<thinking>This tag is never closed and continues...';
|
|
150
|
-
const result = (0, parser_1.extractInitialTaggedContent)(content, 'thinking');
|
|
151
|
-
expect(result.extracted).toBeNull();
|
|
152
|
-
expect(result.remaining).toBe('<thinking>This tag is never closed and continues...');
|
|
153
|
-
});
|
|
154
|
-
it('should handle multiline content within tags', () => {
|
|
155
|
-
const content = `<thinking>
|
|
156
|
-
Step 1: Analyze the problem
|
|
157
|
-
Step 2: Break it down
|
|
158
|
-
Step 3: Solve each part
|
|
159
|
-
</thinking>
|
|
160
|
-
The solution is to approach it systematically.`;
|
|
161
|
-
const result = (0, parser_1.extractInitialTaggedContent)(content, 'thinking');
|
|
162
|
-
expect(result.extracted).toBe(`Step 1: Analyze the problem
|
|
163
|
-
Step 2: Break it down
|
|
164
|
-
Step 3: Solve each part`);
|
|
165
|
-
expect(result.remaining).toBe('The solution is to approach it systematically.');
|
|
166
|
-
});
|
|
167
|
-
it('should handle empty tag', () => {
|
|
168
|
-
const content = '<thinking></thinking>Here is the response.';
|
|
169
|
-
const result = (0, parser_1.extractInitialTaggedContent)(content, 'thinking');
|
|
170
|
-
expect(result.extracted).toBe('');
|
|
171
|
-
expect(result.remaining).toBe('Here is the response.');
|
|
172
|
-
});
|
|
173
|
-
it('should handle empty content', () => {
|
|
174
|
-
const result = (0, parser_1.extractInitialTaggedContent)('', 'thinking');
|
|
175
|
-
expect(result.extracted).toBeNull();
|
|
176
|
-
expect(result.remaining).toBe('');
|
|
177
|
-
});
|
|
178
|
-
it('should preserve whitespace after the closing tag', () => {
|
|
179
|
-
const content = '<thinking>Thoughts</thinking> \n\n Response here';
|
|
180
|
-
const result = (0, parser_1.extractInitialTaggedContent)(content, 'thinking');
|
|
181
|
-
expect(result.extracted).toBe('Thoughts');
|
|
182
|
-
expect(result.remaining).toBe('Response here');
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
describe('parseRoleTags', () => {
|
|
186
|
-
it('should parse basic role tags with variables', () => {
|
|
187
|
-
const template = `
|
|
188
|
-
<SYSTEM>You are a helpful {{expertise}} assistant.</SYSTEM>
|
|
189
|
-
<USER>Help me with {{task}}</USER>
|
|
190
|
-
<ASSISTANT>I'll help you with {{task}}.</ASSISTANT>
|
|
191
|
-
`;
|
|
192
|
-
const result = (0, parser_1.parseRoleTags)(template);
|
|
193
|
-
expect(result).toEqual([
|
|
194
|
-
{ role: 'system', content: 'You are a helpful {{expertise}} assistant.' },
|
|
195
|
-
{ role: 'user', content: 'Help me with {{task}}' },
|
|
196
|
-
{ role: 'assistant', content: "I'll help you with {{task}}." }
|
|
197
|
-
]);
|
|
198
|
-
});
|
|
199
|
-
it('should handle multiple user/assistant turns', () => {
|
|
200
|
-
const template = `
|
|
201
|
-
<USER>First question</USER>
|
|
202
|
-
<ASSISTANT>First answer</ASSISTANT>
|
|
203
|
-
<USER>Follow-up question</USER>
|
|
204
|
-
<ASSISTANT>Follow-up answer</ASSISTANT>
|
|
205
|
-
`;
|
|
206
|
-
const result = (0, parser_1.parseRoleTags)(template);
|
|
207
|
-
expect(result).toEqual([
|
|
208
|
-
{ role: 'user', content: 'First question' },
|
|
209
|
-
{ role: 'assistant', content: 'First answer' },
|
|
210
|
-
{ role: 'user', content: 'Follow-up question' },
|
|
211
|
-
{ role: 'assistant', content: 'Follow-up answer' }
|
|
212
|
-
]);
|
|
213
|
-
});
|
|
214
|
-
it('should handle template with only system message', () => {
|
|
215
|
-
const template = '<SYSTEM>You are a coding assistant.</SYSTEM>';
|
|
216
|
-
const result = (0, parser_1.parseRoleTags)(template);
|
|
217
|
-
expect(result).toEqual([
|
|
218
|
-
{ role: 'system', content: 'You are a coding assistant.' }
|
|
219
|
-
]);
|
|
220
|
-
});
|
|
221
|
-
it('should treat content without tags as user message', () => {
|
|
222
|
-
const template = 'Hello, can you help me with {{problem}}?';
|
|
223
|
-
const result = (0, parser_1.parseRoleTags)(template);
|
|
224
|
-
expect(result).toEqual([
|
|
225
|
-
{ role: 'user', content: 'Hello, can you help me with {{problem}}?' }
|
|
226
|
-
]);
|
|
227
|
-
});
|
|
228
|
-
it('should handle empty template', () => {
|
|
229
|
-
const result = (0, parser_1.parseRoleTags)('');
|
|
230
|
-
expect(result).toEqual([]);
|
|
231
|
-
});
|
|
232
|
-
it('should handle whitespace-only template', () => {
|
|
233
|
-
const result = (0, parser_1.parseRoleTags)(' \n\t ');
|
|
234
|
-
expect(result).toEqual([]);
|
|
235
|
-
});
|
|
236
|
-
it('should preserve multiline content', () => {
|
|
237
|
-
const template = `<USER>
|
|
238
|
-
Please help me with:
|
|
239
|
-
1. {{task1}}
|
|
240
|
-
2. {{task2}}
|
|
241
|
-
3. {{task3}}
|
|
242
|
-
</USER>`;
|
|
243
|
-
const result = (0, parser_1.parseRoleTags)(template);
|
|
244
|
-
expect(result).toEqual([
|
|
245
|
-
{ role: 'user', content: 'Please help me with:\n 1. {{task1}}\n 2. {{task2}}\n 3. {{task3}}' }
|
|
246
|
-
]);
|
|
247
|
-
});
|
|
248
|
-
it('should ignore empty role tags', () => {
|
|
249
|
-
const template = `
|
|
250
|
-
<SYSTEM>System prompt</SYSTEM>
|
|
251
|
-
<USER></USER>
|
|
252
|
-
<ASSISTANT> </ASSISTANT>
|
|
253
|
-
<USER>Real question</USER>
|
|
254
|
-
`;
|
|
255
|
-
const result = (0, parser_1.parseRoleTags)(template);
|
|
256
|
-
expect(result).toEqual([
|
|
257
|
-
{ role: 'system', content: 'System prompt' },
|
|
258
|
-
{ role: 'user', content: 'Real question' }
|
|
259
|
-
]);
|
|
260
|
-
});
|
|
261
|
-
it('should handle mixed case tags', () => {
|
|
262
|
-
const template = '<System>System</System><User>User</User><Assistant>Assistant</Assistant>';
|
|
263
|
-
const result = (0, parser_1.parseRoleTags)(template);
|
|
264
|
-
// Should only match uppercase tags as per the regex
|
|
265
|
-
expect(result).toEqual([
|
|
266
|
-
{ role: 'user', content: '<System>System</System><User>User</User><Assistant>Assistant</Assistant>' }
|
|
267
|
-
]);
|
|
268
|
-
});
|
|
269
|
-
it('should handle complex templates with conditionals', () => {
|
|
270
|
-
const template = `
|
|
271
|
-
<SYSTEM>{{ systemPrompt ? systemPrompt : 'Default system prompt' }}</SYSTEM>
|
|
272
|
-
<USER>{{ hasContext ? 'Context: {{context}}\n\n' : '' }}{{question}}</USER>
|
|
273
|
-
`;
|
|
274
|
-
const result = (0, parser_1.parseRoleTags)(template);
|
|
275
|
-
expect(result).toEqual([
|
|
276
|
-
{ role: 'system', content: "{{ systemPrompt ? systemPrompt : 'Default system prompt' }}" },
|
|
277
|
-
{ role: 'user', content: "{{ hasContext ? 'Context: {{context}}\n\n' : '' }}{{question}}" }
|
|
278
|
-
]);
|
|
279
|
-
});
|
|
280
|
-
it('should preserve special characters and formatting', () => {
|
|
281
|
-
const template = `
|
|
282
|
-
<USER>Code: \`{{code}}\`
|
|
283
|
-
|
|
284
|
-
Error: "{{error}}"</USER>
|
|
285
|
-
<ASSISTANT>Let me analyze that code...</ASSISTANT>
|
|
286
|
-
`;
|
|
287
|
-
const result = (0, parser_1.parseRoleTags)(template);
|
|
288
|
-
expect(result).toEqual([
|
|
289
|
-
{ role: 'user', content: 'Code: `{{code}}`\n \n Error: "{{error}}"' },
|
|
290
|
-
{ role: 'assistant', content: 'Let me analyze that code...' }
|
|
291
|
-
]);
|
|
292
|
-
});
|
|
293
|
-
it('should handle unclosed tags as regular content', () => {
|
|
294
|
-
const template = 'This has <USER>unclosed tag content';
|
|
295
|
-
const result = (0, parser_1.parseRoleTags)(template);
|
|
296
|
-
expect(result).toEqual([
|
|
297
|
-
{ role: 'user', content: 'This has <USER>unclosed tag content' }
|
|
298
|
-
]);
|
|
299
|
-
});
|
|
300
|
-
it('should handle nested-looking structures', () => {
|
|
301
|
-
const template = `<USER>Can you parse <XML>tags</XML> inside content?</USER>`;
|
|
302
|
-
const result = (0, parser_1.parseRoleTags)(template);
|
|
303
|
-
expect(result).toEqual([
|
|
304
|
-
{ role: 'user', content: 'Can you parse <XML>tags</XML> inside content?' }
|
|
305
|
-
]);
|
|
306
|
-
});
|
|
307
|
-
it('should throw error for non-string input', () => {
|
|
308
|
-
expect(() => (0, parser_1.parseRoleTags)(null)).toThrow('Template must be a string');
|
|
309
|
-
expect(() => (0, parser_1.parseRoleTags)(undefined)).toThrow('Template must be a string');
|
|
310
|
-
expect(() => (0, parser_1.parseRoleTags)(123)).toThrow('Template must be a string');
|
|
311
|
-
});
|
|
312
|
-
it('should handle text between role tags', () => {
|
|
313
|
-
const template = `
|
|
314
|
-
Some intro text
|
|
315
|
-
<SYSTEM>System message</SYSTEM>
|
|
316
|
-
Some middle text that should be ignored
|
|
317
|
-
<USER>User message</USER>
|
|
318
|
-
Some ending text
|
|
319
|
-
`;
|
|
320
|
-
const result = (0, parser_1.parseRoleTags)(template);
|
|
321
|
-
expect(result).toEqual([
|
|
322
|
-
{ role: 'system', content: 'System message' },
|
|
323
|
-
{ role: 'user', content: 'User message' }
|
|
324
|
-
]);
|
|
325
|
-
});
|
|
326
|
-
});
|
|
327
|
-
describe('parseTemplateWithMetadata', () => {
|
|
328
|
-
it('should parse template with valid META block', () => {
|
|
329
|
-
const template = `<META>
|
|
330
|
-
{
|
|
331
|
-
"settings": {
|
|
332
|
-
"temperature": 0.9,
|
|
333
|
-
"thinkingExtraction": { "enabled": true, "tag": "reasoning" }
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
</META>
|
|
337
|
-
<SYSTEM>You are a creative writer.</SYSTEM>
|
|
338
|
-
<USER>Write a story</USER>`;
|
|
339
|
-
const result = (0, parser_1.parseTemplateWithMetadata)(template);
|
|
340
|
-
expect(result.metadata).toEqual({
|
|
341
|
-
settings: {
|
|
342
|
-
temperature: 0.9,
|
|
343
|
-
thinkingExtraction: { enabled: true, tag: "reasoning" }
|
|
344
|
-
}
|
|
345
|
-
});
|
|
346
|
-
expect(result.content).toBe('<SYSTEM>You are a creative writer.</SYSTEM>\n<USER>Write a story</USER>');
|
|
347
|
-
});
|
|
348
|
-
it('should handle template without META block', () => {
|
|
349
|
-
const template = `<SYSTEM>You are a helpful assistant.</SYSTEM>
|
|
350
|
-
<USER>Help me understand TypeScript</USER>`;
|
|
351
|
-
const result = (0, parser_1.parseTemplateWithMetadata)(template);
|
|
352
|
-
expect(result.metadata).toEqual({ settings: {} });
|
|
353
|
-
expect(result.content).toBe(template);
|
|
354
|
-
});
|
|
355
|
-
it('should handle invalid JSON in META block', () => {
|
|
356
|
-
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
357
|
-
const template = `<META>
|
|
358
|
-
{
|
|
359
|
-
"settings": {
|
|
360
|
-
"temperature": 0.9,
|
|
361
|
-
invalid json here
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
</META>
|
|
365
|
-
<SYSTEM>Test</SYSTEM>`;
|
|
366
|
-
const result = (0, parser_1.parseTemplateWithMetadata)(template);
|
|
367
|
-
expect(result.metadata).toEqual({ settings: {} });
|
|
368
|
-
expect(result.content).toBe(template); // Original template returned on error
|
|
369
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith('Could not parse <META> block in template. Treating it as content.', expect.any(Error));
|
|
370
|
-
consoleWarnSpy.mockRestore();
|
|
371
|
-
});
|
|
372
|
-
it('should handle META block with missing settings', () => {
|
|
373
|
-
const template = `<META>
|
|
374
|
-
{
|
|
375
|
-
"name": "My Template",
|
|
376
|
-
"version": "1.0"
|
|
377
|
-
}
|
|
378
|
-
</META>
|
|
379
|
-
<USER>Hello</USER>`;
|
|
380
|
-
const result = (0, parser_1.parseTemplateWithMetadata)(template);
|
|
381
|
-
expect(result.metadata).toEqual({ settings: {} });
|
|
382
|
-
expect(result.content).toBe('<USER>Hello</USER>');
|
|
383
|
-
});
|
|
384
|
-
it('should handle META block with non-object settings', () => {
|
|
385
|
-
const template = `<META>
|
|
386
|
-
{
|
|
387
|
-
"settings": "not an object"
|
|
388
|
-
}
|
|
389
|
-
</META>
|
|
390
|
-
<USER>Test</USER>`;
|
|
391
|
-
const result = (0, parser_1.parseTemplateWithMetadata)(template);
|
|
392
|
-
expect(result.metadata).toEqual({ settings: {} });
|
|
393
|
-
expect(result.content).toBe('<USER>Test</USER>');
|
|
394
|
-
});
|
|
395
|
-
it('should handle empty META block', () => {
|
|
396
|
-
const template = `<META></META>
|
|
397
|
-
<USER>Test</USER>`;
|
|
398
|
-
const result = (0, parser_1.parseTemplateWithMetadata)(template);
|
|
399
|
-
expect(result.metadata).toEqual({ settings: {} });
|
|
400
|
-
expect(result.content).toBe('<USER>Test</USER>');
|
|
401
|
-
});
|
|
402
|
-
it('should handle META block with whitespace', () => {
|
|
403
|
-
const template = ` <META>
|
|
404
|
-
{
|
|
405
|
-
"settings": {
|
|
406
|
-
"temperature": 0.5
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
</META>
|
|
410
|
-
<SYSTEM>Assistant</SYSTEM>`;
|
|
411
|
-
const result = (0, parser_1.parseTemplateWithMetadata)(template);
|
|
412
|
-
expect(result.metadata).toEqual({
|
|
413
|
-
settings: { temperature: 0.5 }
|
|
414
|
-
});
|
|
415
|
-
expect(result.content).toBe('<SYSTEM>Assistant</SYSTEM>');
|
|
416
|
-
});
|
|
417
|
-
it('should handle complex settings in META block', () => {
|
|
418
|
-
const template = `<META>
|
|
419
|
-
{
|
|
420
|
-
"settings": {
|
|
421
|
-
"temperature": 0.8,
|
|
422
|
-
"maxTokens": 2000,
|
|
423
|
-
"stopSequences": ["\\n\\n", "END"],
|
|
424
|
-
"reasoning": {
|
|
425
|
-
"enabled": true,
|
|
426
|
-
"effort": "high",
|
|
427
|
-
"maxTokens": 5000
|
|
428
|
-
},
|
|
429
|
-
"thinkingExtraction": {
|
|
430
|
-
"enabled": false
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
</META>
|
|
435
|
-
<SYSTEM>Complex template</SYSTEM>`;
|
|
436
|
-
const result = (0, parser_1.parseTemplateWithMetadata)(template);
|
|
437
|
-
expect(result.metadata.settings).toEqual({
|
|
438
|
-
temperature: 0.8,
|
|
439
|
-
maxTokens: 2000,
|
|
440
|
-
stopSequences: ["\n\n", "END"],
|
|
441
|
-
reasoning: {
|
|
442
|
-
enabled: true,
|
|
443
|
-
effort: "high",
|
|
444
|
-
maxTokens: 5000
|
|
445
|
-
},
|
|
446
|
-
thinkingExtraction: {
|
|
447
|
-
enabled: false
|
|
448
|
-
}
|
|
449
|
-
});
|
|
450
|
-
});
|
|
451
|
-
it('should only parse META at the beginning of the template', () => {
|
|
452
|
-
const template = `<USER>Some content</USER>
|
|
453
|
-
<META>
|
|
454
|
-
{
|
|
455
|
-
"settings": { "temperature": 0.5 }
|
|
456
|
-
}
|
|
457
|
-
</META>
|
|
458
|
-
<USER>More content</USER>`;
|
|
459
|
-
const result = (0, parser_1.parseTemplateWithMetadata)(template);
|
|
460
|
-
// META block not at start, so it's not parsed
|
|
461
|
-
expect(result.metadata).toEqual({ settings: {} });
|
|
462
|
-
expect(result.content).toBe(template);
|
|
463
|
-
});
|
|
464
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|