genai-lite 0.2.1 → 0.3.1

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.
Files changed (49) hide show
  1. package/README.md +382 -49
  2. package/dist/index.d.ts +3 -3
  3. package/dist/index.js +4 -3
  4. package/dist/llm/LLMService.createMessages.test.d.ts +4 -0
  5. package/dist/llm/LLMService.createMessages.test.js +364 -0
  6. package/dist/llm/LLMService.d.ts +48 -83
  7. package/dist/llm/LLMService.js +176 -480
  8. package/dist/llm/LLMService.original.d.ts +147 -0
  9. package/dist/llm/LLMService.original.js +656 -0
  10. package/dist/llm/LLMService.test.js +187 -0
  11. package/dist/llm/clients/AnthropicClientAdapter.test.js +4 -0
  12. package/dist/llm/clients/GeminiClientAdapter.test.js +4 -0
  13. package/dist/llm/clients/MockClientAdapter.js +29 -13
  14. package/dist/llm/clients/MockClientAdapter.test.js +4 -0
  15. package/dist/llm/clients/OpenAIClientAdapter.test.js +4 -0
  16. package/dist/llm/config.js +5 -0
  17. package/dist/llm/services/AdapterRegistry.d.ts +59 -0
  18. package/dist/llm/services/AdapterRegistry.js +113 -0
  19. package/dist/llm/services/AdapterRegistry.test.d.ts +1 -0
  20. package/dist/llm/services/AdapterRegistry.test.js +239 -0
  21. package/dist/llm/services/ModelResolver.d.ts +35 -0
  22. package/dist/llm/services/ModelResolver.js +116 -0
  23. package/dist/llm/services/ModelResolver.test.d.ts +1 -0
  24. package/dist/llm/services/ModelResolver.test.js +158 -0
  25. package/dist/llm/services/PresetManager.d.ts +27 -0
  26. package/dist/llm/services/PresetManager.js +50 -0
  27. package/dist/llm/services/PresetManager.test.d.ts +1 -0
  28. package/dist/llm/services/PresetManager.test.js +210 -0
  29. package/dist/llm/services/RequestValidator.d.ts +31 -0
  30. package/dist/llm/services/RequestValidator.js +122 -0
  31. package/dist/llm/services/RequestValidator.test.d.ts +1 -0
  32. package/dist/llm/services/RequestValidator.test.js +159 -0
  33. package/dist/llm/services/SettingsManager.d.ts +32 -0
  34. package/dist/llm/services/SettingsManager.js +223 -0
  35. package/dist/llm/services/SettingsManager.test.d.ts +1 -0
  36. package/dist/llm/services/SettingsManager.test.js +266 -0
  37. package/dist/llm/types.d.ts +29 -28
  38. package/dist/prompting/builder.d.ts +4 -0
  39. package/dist/prompting/builder.js +12 -61
  40. package/dist/prompting/content.js +3 -9
  41. package/dist/prompting/index.d.ts +2 -3
  42. package/dist/prompting/index.js +4 -5
  43. package/dist/prompting/parser.d.ts +80 -0
  44. package/dist/prompting/parser.js +133 -0
  45. package/dist/prompting/parser.test.js +348 -0
  46. package/dist/prompting/template.d.ts +8 -0
  47. package/dist/prompting/template.js +89 -6
  48. package/dist/prompting/template.test.js +116 -0
  49. package/package.json +1 -1
@@ -15,9 +15,17 @@ exports.renderTemplate = renderTemplate;
15
15
  * - Simple variable substitution: {{ variableName }}
16
16
  * - Conditional rendering: {{ condition ? `true result` : `false result` }}
17
17
  * - Conditional with only true branch: {{ condition ? `true result` }}
18
+ * - Logical operators in conditions:
19
+ * - NOT: {{ !isDisabled ? `enabled` : `disabled` }}
20
+ * - AND: {{ hasPermission && isActive ? `show` : `hide` }}
21
+ * - OR: {{ isAdmin || isOwner ? `allow` : `deny` }}
22
+ * - Combined: {{ !isDraft && isPublished ? `public` : `private` }}
18
23
  * - Multi-line strings in backticks
19
24
  * - Intelligent newline handling (removes empty lines when result is empty)
20
25
  *
26
+ * Note: Logical operators support up to 2 operands. Complex expressions
27
+ * (parentheses, mixing && and ||, or 3+ operands) are not supported.
28
+ *
21
29
  * @param template The template string containing placeholders
22
30
  * @param variables Object containing variable values
23
31
  * @returns The rendered template string
@@ -87,6 +95,53 @@ function renderTemplate(template, variables) {
87
95
  }
88
96
  return result;
89
97
  }
98
+ /**
99
+ * Evaluates a condition string that may contain logical operators (&&, ||, !)
100
+ * Supports:
101
+ * - Simple variable: varName
102
+ * - Negation: !varName
103
+ * - AND: varName1 && varName2, !varName1 && varName2
104
+ * - OR: varName1 || varName2, !varName1 || varName2
105
+ *
106
+ * Does NOT support:
107
+ * - Parentheses for grouping
108
+ * - Mixing && and || in same expression
109
+ * - More than 2 operands
110
+ */
111
+ function evaluateCondition(condition, variables) {
112
+ condition = condition.trim();
113
+ // Check for AND operator
114
+ if (condition.includes('&&')) {
115
+ const parts = condition.split('&&').map(p => p.trim());
116
+ if (parts.length !== 2) {
117
+ // Fallback to simple variable lookup for complex expressions
118
+ return !!variables[condition];
119
+ }
120
+ return evaluateSimpleCondition(parts[0], variables) && evaluateSimpleCondition(parts[1], variables);
121
+ }
122
+ // Check for OR operator
123
+ if (condition.includes('||')) {
124
+ const parts = condition.split('||').map(p => p.trim());
125
+ if (parts.length !== 2) {
126
+ // Fallback to simple variable lookup for complex expressions
127
+ return !!variables[condition];
128
+ }
129
+ return evaluateSimpleCondition(parts[0], variables) || evaluateSimpleCondition(parts[1], variables);
130
+ }
131
+ // Simple condition (possibly with negation)
132
+ return evaluateSimpleCondition(condition, variables);
133
+ }
134
+ /**
135
+ * Evaluates a simple condition that may have a ! prefix
136
+ */
137
+ function evaluateSimpleCondition(condition, variables) {
138
+ condition = condition.trim();
139
+ if (condition.startsWith('!')) {
140
+ const varName = condition.substring(1).trim();
141
+ return !variables[varName];
142
+ }
143
+ return !!variables[condition];
144
+ }
90
145
  function processExpression(expression, variables, leadingNewline, trailingNewline) {
91
146
  const conditionalMarkerIndex = expression.indexOf('?');
92
147
  let result;
@@ -104,7 +159,7 @@ function processExpression(expression, variables, leadingNewline, trailingNewlin
104
159
  }
105
160
  else {
106
161
  // --- Conditional 'ternary' substitution ---
107
- const conditionKey = expression.substring(0, conditionalMarkerIndex).trim();
162
+ const conditionStr = expression.substring(0, conditionalMarkerIndex).trim();
108
163
  const rest = expression.substring(conditionalMarkerIndex + 1).trim();
109
164
  // Parse ternary expression with backtick-delimited strings
110
165
  // We need to handle nested {{ }} within backticks
@@ -165,8 +220,31 @@ function processExpression(expression, variables, leadingNewline, trailingNewlin
165
220
  }
166
221
  }
167
222
  else {
168
- // Fallback to old quote-based parsing for backward compatibility
169
- const elseMarkerIndex = rest.indexOf(':');
223
+ // Fallback to quote-based parsing for backward compatibility
224
+ // Need to find the ':' that separates true/false parts, not one inside quotes
225
+ let elseMarkerIndex = -1;
226
+ let inSingleQuote = false;
227
+ let inDoubleQuote = false;
228
+ for (let i = 0; i < rest.length; i++) {
229
+ const char = rest[i];
230
+ const prevChar = i > 0 ? rest[i - 1] : '';
231
+ // Track quote state (ignoring escaped quotes)
232
+ if (char === "'" && prevChar !== '\\') {
233
+ if (!inDoubleQuote) {
234
+ inSingleQuote = !inSingleQuote;
235
+ }
236
+ }
237
+ else if (char === '"' && prevChar !== '\\') {
238
+ if (!inSingleQuote) {
239
+ inDoubleQuote = !inDoubleQuote;
240
+ }
241
+ }
242
+ else if (char === ':' && !inSingleQuote && !inDoubleQuote) {
243
+ // Found the separator outside of quotes
244
+ elseMarkerIndex = i;
245
+ break;
246
+ }
247
+ }
170
248
  if (elseMarkerIndex === -1) {
171
249
  trueText = rest;
172
250
  }
@@ -176,15 +254,20 @@ function processExpression(expression, variables, leadingNewline, trailingNewlin
176
254
  }
177
255
  // Remove quotes from the start and end of the text parts
178
256
  const unquote = (text) => {
179
- if ((text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'"))) {
180
- return text.slice(1, -1);
257
+ if (text.startsWith('"') && text.endsWith('"')) {
258
+ // Remove the outer double quotes and unescape inner quotes
259
+ return text.slice(1, -1).replace(/\\"/g, '"');
260
+ }
261
+ else if (text.startsWith("'") && text.endsWith("'")) {
262
+ // Remove the outer single quotes and unescape inner quotes
263
+ return text.slice(1, -1).replace(/\\'/g, "'");
181
264
  }
182
265
  return text;
183
266
  };
184
267
  trueText = unquote(trueText);
185
268
  falseText = unquote(falseText);
186
269
  }
187
- const conditionValue = !!variables[conditionKey]; // Evaluate truthiness
270
+ const conditionValue = evaluateCondition(conditionStr, variables);
188
271
  result = conditionValue ? trueText : falseText;
189
272
  // Recursively process the result to handle nested variables
190
273
  if (result.includes('{{')) {
@@ -131,4 +131,120 @@ End
131
131
  const result = (0, template_1.renderTemplate)(template, { any: 'value' });
132
132
  expect(result).toBe('Just plain text');
133
133
  });
134
+ describe('colon handling in quoted strings', () => {
135
+ it('should handle colon in double-quoted true branch', () => {
136
+ const template = '{{ isDraft ? "Note: This is a draft" : "Published" }}';
137
+ expect((0, template_1.renderTemplate)(template, { isDraft: true })).toBe('Note: This is a draft');
138
+ expect((0, template_1.renderTemplate)(template, { isDraft: false })).toBe('Published');
139
+ });
140
+ it('should handle colon in single-quoted true branch', () => {
141
+ const template = "{{ isDraft ? 'Note: This is a draft' : 'Published' }}";
142
+ expect((0, template_1.renderTemplate)(template, { isDraft: true })).toBe('Note: This is a draft');
143
+ expect((0, template_1.renderTemplate)(template, { isDraft: false })).toBe('Published');
144
+ });
145
+ it('should handle colon in both branches', () => {
146
+ const template = '{{ isDraft ? "Status: Draft" : "Status: Published" }}';
147
+ expect((0, template_1.renderTemplate)(template, { isDraft: true })).toBe('Status: Draft');
148
+ expect((0, template_1.renderTemplate)(template, { isDraft: false })).toBe('Status: Published');
149
+ });
150
+ it('should handle multiple colons in quoted strings', () => {
151
+ const template = '{{ showTime ? "Time: 10:30:45" : "Time: Not available" }}';
152
+ expect((0, template_1.renderTemplate)(template, { showTime: true })).toBe('Time: 10:30:45');
153
+ expect((0, template_1.renderTemplate)(template, { showTime: false })).toBe('Time: Not available');
154
+ });
155
+ it('should handle escaped quotes with colons', () => {
156
+ const template = '{{ useAdvanced ? "He said: \\"Hello\\"" : "Simple greeting" }}';
157
+ expect((0, template_1.renderTemplate)(template, { useAdvanced: true })).toBe('He said: "Hello"');
158
+ });
159
+ it('should handle mixed quote types', () => {
160
+ const template = `{{ showBoth ? "Bob's message: 'Hi there'" : 'Alice said: "Hello"' }}`;
161
+ expect((0, template_1.renderTemplate)(template, { showBoth: true })).toBe("Bob's message: 'Hi there'");
162
+ expect((0, template_1.renderTemplate)(template, { showBoth: false })).toBe('Alice said: "Hello"');
163
+ });
164
+ it('should handle URL-like strings with colons', () => {
165
+ const template = '{{ useHttps ? "https://example.com" : "http://example.com" }}';
166
+ expect((0, template_1.renderTemplate)(template, { useHttps: true })).toBe('https://example.com');
167
+ expect((0, template_1.renderTemplate)(template, { useHttps: false })).toBe('http://example.com');
168
+ });
169
+ it('should work with backtick syntax (regression test)', () => {
170
+ const template = '{{ isDraft ? `Note: This is a draft` : `Status: Published` }}';
171
+ expect((0, template_1.renderTemplate)(template, { isDraft: true })).toBe('Note: This is a draft');
172
+ expect((0, template_1.renderTemplate)(template, { isDraft: false })).toBe('Status: Published');
173
+ });
174
+ it('should handle no false branch with colon in true branch', () => {
175
+ const template = '{{ showNote ? "Note: Important information" }}';
176
+ expect((0, template_1.renderTemplate)(template, { showNote: true })).toBe('Note: Important information');
177
+ expect((0, template_1.renderTemplate)(template, { showNote: false })).toBe('');
178
+ });
179
+ it('should handle colons with logical operators', () => {
180
+ const template = '{{ isActive && hasAccess ? "Status: Active" : "Status: Inactive" }}';
181
+ expect((0, template_1.renderTemplate)(template, { isActive: true, hasAccess: true })).toBe('Status: Active');
182
+ expect((0, template_1.renderTemplate)(template, { isActive: false, hasAccess: true })).toBe('Status: Inactive');
183
+ });
184
+ });
185
+ describe('logical operators in conditions', () => {
186
+ it('should handle NOT operator (!)', () => {
187
+ const template = '{{ !isDisabled ? `Enabled` : `Disabled` }}';
188
+ expect((0, template_1.renderTemplate)(template, { isDisabled: false })).toBe('Enabled');
189
+ expect((0, template_1.renderTemplate)(template, { isDisabled: true })).toBe('Disabled');
190
+ });
191
+ it('should handle AND operator (&&)', () => {
192
+ const template = '{{ hasPermission && isActive ? `Show button` : `Hide button` }}';
193
+ expect((0, template_1.renderTemplate)(template, { hasPermission: true, isActive: true })).toBe('Show button');
194
+ expect((0, template_1.renderTemplate)(template, { hasPermission: true, isActive: false })).toBe('Hide button');
195
+ expect((0, template_1.renderTemplate)(template, { hasPermission: false, isActive: true })).toBe('Hide button');
196
+ expect((0, template_1.renderTemplate)(template, { hasPermission: false, isActive: false })).toBe('Hide button');
197
+ });
198
+ it('should handle OR operator (||)', () => {
199
+ const template = '{{ isAdmin || isOwner ? `Has access` : `No access` }}';
200
+ expect((0, template_1.renderTemplate)(template, { isAdmin: true, isOwner: false })).toBe('Has access');
201
+ expect((0, template_1.renderTemplate)(template, { isAdmin: false, isOwner: true })).toBe('Has access');
202
+ expect((0, template_1.renderTemplate)(template, { isAdmin: true, isOwner: true })).toBe('Has access');
203
+ expect((0, template_1.renderTemplate)(template, { isAdmin: false, isOwner: false })).toBe('No access');
204
+ });
205
+ it('should handle NOT with AND', () => {
206
+ const template = '{{ !isDraft && isPublished ? `Show public` : `Hide` }}';
207
+ expect((0, template_1.renderTemplate)(template, { isDraft: false, isPublished: true })).toBe('Show public');
208
+ expect((0, template_1.renderTemplate)(template, { isDraft: true, isPublished: true })).toBe('Hide');
209
+ });
210
+ it('should handle NOT with OR', () => {
211
+ const template = '{{ !isBlocked || isAdmin ? `Allow` : `Deny` }}';
212
+ expect((0, template_1.renderTemplate)(template, { isBlocked: false, isAdmin: false })).toBe('Allow');
213
+ expect((0, template_1.renderTemplate)(template, { isBlocked: true, isAdmin: true })).toBe('Allow');
214
+ expect((0, template_1.renderTemplate)(template, { isBlocked: true, isAdmin: false })).toBe('Deny');
215
+ });
216
+ it('should handle NOT on both sides of AND', () => {
217
+ const template = '{{ !isLoading && !hasError ? `Ready` : `Not ready` }}';
218
+ expect((0, template_1.renderTemplate)(template, { isLoading: false, hasError: false })).toBe('Ready');
219
+ expect((0, template_1.renderTemplate)(template, { isLoading: true, hasError: false })).toBe('Not ready');
220
+ expect((0, template_1.renderTemplate)(template, { isLoading: false, hasError: true })).toBe('Not ready');
221
+ });
222
+ it('should handle whitespace around operators', () => {
223
+ const template = '{{ !isDisabled && isVisible ? `Show` : `Hide` }}';
224
+ expect((0, template_1.renderTemplate)(template, { isDisabled: false, isVisible: true })).toBe('Show');
225
+ });
226
+ it('should handle undefined variables in logical expressions', () => {
227
+ const template = '{{ undefinedVar && definedVar ? `Both true` : `At least one false` }}';
228
+ expect((0, template_1.renderTemplate)(template, { definedVar: true })).toBe('At least one false');
229
+ });
230
+ it('should handle falsy values in logical expressions', () => {
231
+ const template = '{{ nullVar || zero ? `Has truthy` : `All falsy` }}';
232
+ expect((0, template_1.renderTemplate)(template, { nullVar: null, zero: 0 })).toBe('All falsy');
233
+ const template2 = '{{ emptyString || text ? `Has truthy` : `All falsy` }}';
234
+ expect((0, template_1.renderTemplate)(template2, { emptyString: '', text: 'hello' })).toBe('Has truthy');
235
+ });
236
+ it('should fallback to simple lookup for complex expressions', () => {
237
+ // More than 2 operands should fallback
238
+ const template = '{{ a && b && c ? `True` : `False` }}';
239
+ const result = (0, template_1.renderTemplate)(template, { 'a && b && c': true });
240
+ expect(result).toBe('True');
241
+ });
242
+ it('should handle nested templates with logical operators', () => {
243
+ const template = '{{ showDetails && hasData ? `Details: {{data}}` : `No details` }}';
244
+ expect((0, template_1.renderTemplate)(template, { showDetails: true, hasData: true, data: 'Important info' }))
245
+ .toBe('Details: Important info');
246
+ expect((0, template_1.renderTemplate)(template, { showDetails: false, hasData: true, data: 'Important info' }))
247
+ .toBe('No details');
248
+ });
249
+ });
134
250
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genai-lite",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "A lightweight, portable toolkit for interacting with various Generative AI APIs.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",