lazlo-ai 1.0.0

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 (126) hide show
  1. package/.env.example +9 -0
  2. package/README.md +278 -0
  3. package/dist/cache/semantic.d.ts +39 -0
  4. package/dist/cache/semantic.d.ts.map +1 -0
  5. package/dist/cache/semantic.js +134 -0
  6. package/dist/cache/semantic.js.map +1 -0
  7. package/dist/chains/llmchain.d.ts +65 -0
  8. package/dist/chains/llmchain.d.ts.map +1 -0
  9. package/dist/chains/llmchain.js +137 -0
  10. package/dist/chains/llmchain.js.map +1 -0
  11. package/dist/chains/rag.d.ts +23 -0
  12. package/dist/chains/rag.d.ts.map +1 -0
  13. package/dist/chains/rag.js +47 -0
  14. package/dist/chains/rag.js.map +1 -0
  15. package/dist/core/types.d.ts +130 -0
  16. package/dist/core/types.d.ts.map +1 -0
  17. package/dist/core/types.js +8 -0
  18. package/dist/core/types.js.map +1 -0
  19. package/dist/document_loaders/index.d.ts +61 -0
  20. package/dist/document_loaders/index.d.ts.map +1 -0
  21. package/dist/document_loaders/index.js +183 -0
  22. package/dist/document_loaders/index.js.map +1 -0
  23. package/dist/embeddings/google.d.ts +43 -0
  24. package/dist/embeddings/google.d.ts.map +1 -0
  25. package/dist/embeddings/google.js +90 -0
  26. package/dist/embeddings/google.js.map +1 -0
  27. package/dist/embeddings/local.d.ts +64 -0
  28. package/dist/embeddings/local.d.ts.map +1 -0
  29. package/dist/embeddings/local.js +95 -0
  30. package/dist/embeddings/local.js.map +1 -0
  31. package/dist/evals/judge.d.ts +22 -0
  32. package/dist/evals/judge.d.ts.map +1 -0
  33. package/dist/evals/judge.js +77 -0
  34. package/dist/evals/judge.js.map +1 -0
  35. package/dist/index.d.ts +28 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +84 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/memory/buffer.d.ts +64 -0
  40. package/dist/memory/buffer.d.ts.map +1 -0
  41. package/dist/memory/buffer.js +168 -0
  42. package/dist/memory/buffer.js.map +1 -0
  43. package/dist/parsers/output.d.ts +64 -0
  44. package/dist/parsers/output.d.ts.map +1 -0
  45. package/dist/parsers/output.js +148 -0
  46. package/dist/parsers/output.js.map +1 -0
  47. package/dist/prompts/registry.d.ts +65 -0
  48. package/dist/prompts/registry.d.ts.map +1 -0
  49. package/dist/prompts/registry.js +170 -0
  50. package/dist/prompts/registry.js.map +1 -0
  51. package/dist/providers/ollama.d.ts +30 -0
  52. package/dist/providers/ollama.d.ts.map +1 -0
  53. package/dist/providers/ollama.js +104 -0
  54. package/dist/providers/ollama.js.map +1 -0
  55. package/dist/providers/openai.d.ts +46 -0
  56. package/dist/providers/openai.d.ts.map +1 -0
  57. package/dist/providers/openai.js +228 -0
  58. package/dist/providers/openai.js.map +1 -0
  59. package/dist/retrievers/index.d.ts +71 -0
  60. package/dist/retrievers/index.d.ts.map +1 -0
  61. package/dist/retrievers/index.js +130 -0
  62. package/dist/retrievers/index.js.map +1 -0
  63. package/dist/router/smartrouter.d.ts +36 -0
  64. package/dist/router/smartrouter.d.ts.map +1 -0
  65. package/dist/router/smartrouter.js +132 -0
  66. package/dist/router/smartrouter.js.map +1 -0
  67. package/dist/text_splitters/index.d.ts +28 -0
  68. package/dist/text_splitters/index.d.ts.map +1 -0
  69. package/dist/text_splitters/index.js +109 -0
  70. package/dist/text_splitters/index.js.map +1 -0
  71. package/dist/tools/decorator.d.ts +26 -0
  72. package/dist/tools/decorator.d.ts.map +1 -0
  73. package/dist/tools/decorator.js +102 -0
  74. package/dist/tools/decorator.js.map +1 -0
  75. package/dist/tools/index.d.ts +7 -0
  76. package/dist/tools/index.d.ts.map +1 -0
  77. package/dist/tools/index.js +6 -0
  78. package/dist/tools/index.js.map +1 -0
  79. package/dist/tools/keiro.d.ts +20 -0
  80. package/dist/tools/keiro.d.ts.map +1 -0
  81. package/dist/tools/keiro.js +67 -0
  82. package/dist/tools/keiro.js.map +1 -0
  83. package/dist/tracing/tracer.d.ts +56 -0
  84. package/dist/tracing/tracer.d.ts.map +1 -0
  85. package/dist/tracing/tracer.js +125 -0
  86. package/dist/tracing/tracer.js.map +1 -0
  87. package/dist/utils/logger.d.ts +25 -0
  88. package/dist/utils/logger.d.ts.map +1 -0
  89. package/dist/utils/logger.js +50 -0
  90. package/dist/utils/logger.js.map +1 -0
  91. package/dist/utils/pricing.d.ts +31 -0
  92. package/dist/utils/pricing.d.ts.map +1 -0
  93. package/dist/utils/pricing.js +108 -0
  94. package/dist/utils/pricing.js.map +1 -0
  95. package/dist/vectorstores/index.d.ts +62 -0
  96. package/dist/vectorstores/index.d.ts.map +1 -0
  97. package/dist/vectorstores/index.js +244 -0
  98. package/dist/vectorstores/index.js.map +1 -0
  99. package/package.json +48 -0
  100. package/src/cache/semantic.ts +175 -0
  101. package/src/chains/llmchain.ts +194 -0
  102. package/src/chains/rag.ts +65 -0
  103. package/src/core/types.ts +178 -0
  104. package/src/document_loaders/index.ts +223 -0
  105. package/src/embeddings/google.ts +119 -0
  106. package/src/embeddings/local.ts +118 -0
  107. package/src/evals/judge.ts +99 -0
  108. package/src/index.ts +121 -0
  109. package/src/memory/buffer.ts +222 -0
  110. package/src/parsers/output.ts +195 -0
  111. package/src/prompts/registry.ts +205 -0
  112. package/src/providers/ollama.ts +151 -0
  113. package/src/providers/openai.ts +320 -0
  114. package/src/retrievers/index.ts +182 -0
  115. package/src/router/smartrouter.ts +172 -0
  116. package/src/text_splitters/index.ts +145 -0
  117. package/src/tools/decorator.ts +145 -0
  118. package/src/tools/index.ts +7 -0
  119. package/src/tools/keiro.ts +92 -0
  120. package/src/tracing/tracer.ts +178 -0
  121. package/src/utils/logger.ts +62 -0
  122. package/src/utils/pricing.ts +133 -0
  123. package/src/vectorstores/index.ts +338 -0
  124. package/test-full.mjs +552 -0
  125. package/test.mjs +74 -0
  126. package/tsconfig.json +30 -0
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Buffer Memory - Simple conversation history
3
+ */
4
+
5
+ import { Message } from '../core/types.js';
6
+
7
+ // ============================================================================
8
+ // Buffer Memory
9
+ // ============================================================================
10
+
11
+ export interface BufferMemoryOptions {
12
+ memoryKey?: string;
13
+ returnMessages?: boolean;
14
+ maxTokenLimit?: number;
15
+ }
16
+
17
+ export class BufferMemory {
18
+ private messages: Message[] = [];
19
+ private memoryKey: string;
20
+ private returnMessages: boolean;
21
+ private maxTokenLimit?: number;
22
+
23
+ constructor(options: BufferMemoryOptions = {}) {
24
+ this.memoryKey = options.memoryKey ?? 'history';
25
+ this.returnMessages = options.returnMessages ?? false;
26
+ this.maxTokenLimit = options.maxTokenLimit;
27
+ }
28
+
29
+ /**
30
+ * Estimate tokens (rough approximation)
31
+ */
32
+ private estimateTokens(text: string): number {
33
+ return Math.ceil(text.length / 4);
34
+ }
35
+
36
+ /**
37
+ * Load memory variables for the chain
38
+ */
39
+ async loadMemoryVariables(_inputs: Record<string, unknown> = {}): Promise<Record<string, unknown>> {
40
+ // Trim if over token limit
41
+ if (this.maxTokenLimit) {
42
+ let tokenCount = 0;
43
+ const trimmedMessages: Message[] = [];
44
+
45
+ for (let i = this.messages.length - 1; i >= 0; i--) {
46
+ const msg = this.messages[i];
47
+ const msgTokens = this.estimateTokens(msg.content);
48
+
49
+ if (tokenCount + msgTokens <= this.maxTokenLimit) {
50
+ trimmedMessages.unshift(msg);
51
+ tokenCount += msgTokens;
52
+ } else {
53
+ break;
54
+ }
55
+ }
56
+
57
+ this.messages = trimmedMessages;
58
+ }
59
+
60
+ if (this.returnMessages) {
61
+ return { [this.memoryKey]: [...this.messages] };
62
+ }
63
+
64
+ // Return as formatted string
65
+ const history = this.messages
66
+ .map(m => `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content}`)
67
+ .join('\n');
68
+
69
+ return { [this.memoryKey]: history };
70
+ }
71
+
72
+ /**
73
+ * Save context to memory
74
+ */
75
+ async saveContext(inputs: Record<string, unknown>, outputs: Record<string, string>): Promise<void> {
76
+ // Extract input and output content
77
+ const inputContent = Object.values(inputs)[0]?.toString() ?? '';
78
+ const outputContent = Object.values(outputs)[0]?.toString() ?? '';
79
+
80
+ if (inputContent) {
81
+ this.messages.push({ role: 'user', content: inputContent });
82
+ }
83
+ if (outputContent) {
84
+ this.messages.push({ role: 'assistant', content: outputContent });
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Clear memory
90
+ */
91
+ async clear(): Promise<void> {
92
+ this.messages = [];
93
+ }
94
+
95
+ /**
96
+ * Get messages
97
+ */
98
+ getMessages(): Message[] {
99
+ return [...this.messages];
100
+ }
101
+ }
102
+
103
+ // ============================================================================
104
+ // Token Budget Memory
105
+ // ============================================================================
106
+
107
+ export interface TokenBudgetMemoryOptions {
108
+ summarizerLLM?: any;
109
+ maxTokens?: number;
110
+ summarizeThreshold?: number;
111
+ memoryKey?: string;
112
+ returnMessages?: boolean;
113
+ }
114
+
115
+ const SUMMARIZE_PROMPT = `Summarize the following conversation history into 2-3 concise sentences. Preserve key facts, decisions, and context. Be extremely terse.
116
+
117
+ Conversation:
118
+ {history}
119
+
120
+ Summary:`;
121
+
122
+ export class TokenBudgetMemory {
123
+ private messages: Message[] = [];
124
+ private summarizerLLM?: any;
125
+ private maxTokens: number;
126
+ private summarizeThreshold: number;
127
+ private memoryKey: string;
128
+ private returnMessages: boolean;
129
+ private summaryPrefix = '';
130
+
131
+ constructor(options: TokenBudgetMemoryOptions = {}) {
132
+ this.summarizerLLM = options.summarizerLLM;
133
+ this.maxTokens = options.maxTokens ?? 4000;
134
+ this.summarizeThreshold = options.summarizeThreshold ?? 0.8;
135
+ this.memoryKey = options.memoryKey ?? 'history';
136
+ this.returnMessages = options.returnMessages ?? false;
137
+ }
138
+
139
+ private estimateTokens(): number {
140
+ let text = this.messages.map(m => m.content).join('\n');
141
+ if (this.summaryPrefix) {
142
+ text += '\n' + this.summaryPrefix;
143
+ }
144
+ return Math.ceil(text.length / 4);
145
+ }
146
+
147
+ private async compress(): Promise<void> {
148
+ if (!this.messages.length || !this.summarizerLLM) return;
149
+
150
+ // Keep the second half of messages
151
+ const half = Math.floor(this.messages.length / 2);
152
+ const oldMessages = this.messages.slice(0, half);
153
+ this.messages = this.messages.slice(half);
154
+
155
+ const historyStr = oldMessages
156
+ .map(m => `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content}`)
157
+ .join('\n');
158
+
159
+ const prompt = SUMMARIZE_PROMPT.replace('{history}',
160
+ this.summaryPrefix ? `Previous summary: ${this.summaryPrefix}\n\n${historyStr}` : historyStr
161
+ );
162
+
163
+ try {
164
+ const response = await this.summarizerLLM.invoke(prompt);
165
+ this.summaryPrefix = typeof response === 'string' ? response : response.content ?? '';
166
+ } catch (error) {
167
+ console.error('[TokenBudgetMemory] Compression failed:', error);
168
+ }
169
+ }
170
+
171
+ async loadMemoryVariables(_inputs: Record<string, unknown> = {}): Promise<Record<string, unknown>> {
172
+ const tokensUsed = this.estimateTokens();
173
+
174
+ if (tokensUsed >= this.maxTokens * this.summarizeThreshold) {
175
+ await this.compress();
176
+ }
177
+
178
+ if (this.returnMessages) {
179
+ const content: Message[] = [];
180
+ if (this.summaryPrefix) {
181
+ content.push({ role: 'system', content: `[Earlier context]: ${this.summaryPrefix}` });
182
+ }
183
+ content.push(...this.messages);
184
+ return { [this.memoryKey]: content };
185
+ }
186
+
187
+ const parts: string[] = [];
188
+ if (this.summaryPrefix) {
189
+ parts.push(`[Earlier context]: ${this.summaryPrefix}`);
190
+ }
191
+ for (const m of this.messages) {
192
+ parts.push(`${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content}`);
193
+ }
194
+
195
+ return { [this.memoryKey]: parts.join('\n') };
196
+ }
197
+
198
+ async saveContext(inputs: Record<string, unknown>, outputs: Record<string, string>): Promise<void> {
199
+ const inputContent = Object.values(inputs)[0]?.toString() ?? '';
200
+ const outputContent = Object.values(outputs)[0]?.toString() ?? '';
201
+
202
+ if (inputContent) {
203
+ this.messages.push({ role: 'user', content: inputContent });
204
+ }
205
+ if (outputContent) {
206
+ this.messages.push({ role: 'assistant', content: outputContent });
207
+ }
208
+ }
209
+
210
+ async clear(): Promise<void> {
211
+ this.messages = [];
212
+ this.summaryPrefix = '';
213
+ }
214
+
215
+ get tokenUsage(): { estimatedTokens: number; budget: number; messageCount: number } {
216
+ return {
217
+ estimatedTokens: this.estimateTokens(),
218
+ budget: this.maxTokens,
219
+ messageCount: this.messages.length,
220
+ };
221
+ }
222
+ }
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Output Parsers
3
+ *
4
+ * Parse LLM outputs into structured formats
5
+ */
6
+
7
+ import { z } from 'zod';
8
+
9
+ /**
10
+ * Base Output Parser Interface
11
+ */
12
+ export interface BaseOutputParser<T> {
13
+ parse(text: string): Promise<T>;
14
+ format?(text: string): string;
15
+ }
16
+
17
+ /**
18
+ * JSON Output Parser
19
+ */
20
+ export class JSONOutputParser<T extends z.ZodTypeAny = z.ZodTypeAny> {
21
+ private schema?: T;
22
+ private schemaName?: string;
23
+
24
+ constructor(schema?: T, schemaName?: string) {
25
+ this.schema = schema;
26
+ this.schemaName = schemaName;
27
+ }
28
+
29
+ async parse(text: string): Promise<any> {
30
+ // Try to extract JSON from text
31
+ const jsonMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/) ||
32
+ text.match(/\{[\s\S]*\}/);
33
+
34
+ if (!jsonMatch) {
35
+ throw new Error('No JSON found in output');
36
+ }
37
+
38
+ const jsonStr = jsonMatch[1] || jsonMatch[0];
39
+
40
+ try {
41
+ const parsed = JSON.parse(jsonStr);
42
+
43
+ if (this.schema) {
44
+ return this.schema.parse(parsed);
45
+ }
46
+
47
+ return parsed;
48
+ } catch (error: any) {
49
+ throw new Error(`Failed to parse JSON: ${error.message}`);
50
+ }
51
+ }
52
+
53
+ format(text: string): string {
54
+ return `Output the following as valid JSON:\n${text}`;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * XML Output Parser
60
+ */
61
+ export class XMLOutputParser {
62
+ private tags: string[];
63
+
64
+ constructor(tags: string[] = ['response']) {
65
+ this.tags = tags;
66
+ }
67
+
68
+ async parse(text: string): Promise<Record<string, string>> {
69
+ const result: Record<string, string> = {};
70
+
71
+ for (const tag of this.tags) {
72
+ const regex = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)</${tag}>`, 'i');
73
+ const match = text.match(regex);
74
+
75
+ if (match) {
76
+ result[tag] = match[1].trim();
77
+ }
78
+ }
79
+
80
+ return result;
81
+ }
82
+
83
+ format(text: string): string {
84
+ const tagsStr = this.tags.map(t => `<${t}>...</${t}>`).join('\n');
85
+ return `Output the following as XML:\n${tagsStr}\n\nContent:\n${text}`;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Comma-Separated List Parser
91
+ */
92
+ export class CommaSeparatedListParser {
93
+ async parse(text: string): Promise<string[]> {
94
+ return text
95
+ .split(',')
96
+ .map(item => item.trim())
97
+ .filter(item => item.length > 0);
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Markdown Code Block Parser
103
+ */
104
+ export class MarkdownCodeBlockParser {
105
+ private language?: string;
106
+
107
+ constructor(language?: string) {
108
+ this.language = language;
109
+ }
110
+
111
+ async parse(text: string): Promise<string> {
112
+ const regex = this.language
113
+ ? new RegExp(`\`\`\`${this.language}\\s*([\\s\\S]*?)\`\`\``)
114
+ : /```(?:\w+)?\s*([\s\S]*?)```/;
115
+
116
+ const match = text.match(regex);
117
+
118
+ if (match) {
119
+ return match[1].trim();
120
+ }
121
+
122
+ // Return original if no code block found
123
+ return text.trim();
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Parser for tool calls
129
+ */
130
+ export interface ToolCall {
131
+ name: string;
132
+ arguments: Record<string, any>;
133
+ }
134
+
135
+ export class ToolCallParser {
136
+ async parse(text: string): Promise<ToolCall[]> {
137
+ const toolCalls: ToolCall[] = [];
138
+
139
+ // Try JSON format
140
+ try {
141
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
142
+ if (jsonMatch) {
143
+ const parsed = JSON.parse(jsonMatch[0]);
144
+ if (parsed.name && parsed.arguments) {
145
+ return [parsed as ToolCall];
146
+ }
147
+ }
148
+ } catch {
149
+ // Not JSON, try other formats
150
+ }
151
+
152
+ // Try function call format
153
+ const functionMatch = text.match(/(\w+)\(([\s\S]*?)\)/);
154
+ if (functionMatch) {
155
+ const name = functionMatch[1];
156
+ let args: Record<string, any> = {};
157
+
158
+ try {
159
+ args = JSON.parse(`{${functionMatch[2]}}`);
160
+ } catch {
161
+ // Simple key=value parsing
162
+ const argPairs = functionMatch[2].split(',');
163
+ for (const pair of argPairs) {
164
+ const [key, value] = pair.split('=').map(s => s.trim());
165
+ if (key) {
166
+ args[key] = value?.replace(/["']/g, '') || '';
167
+ }
168
+ }
169
+ }
170
+
171
+ toolCalls.push({ name, arguments: args });
172
+ }
173
+
174
+ return toolCalls;
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Factory functions
180
+ */
181
+ export function createJSONParser<T extends z.ZodTypeAny>(schema?: T, name?: string): JSONOutputParser<T> {
182
+ return new JSONOutputParser(schema, name);
183
+ }
184
+
185
+ export function createXMLParser(tags?: string[]): XMLOutputParser {
186
+ return new XMLOutputParser(tags);
187
+ }
188
+
189
+ export function createListParser(): CommaSeparatedListParser {
190
+ return new CommaSeparatedListParser();
191
+ }
192
+
193
+ export function createMarkdownParser(language?: string): MarkdownCodeBlockParser {
194
+ return new MarkdownCodeBlockParser(language);
195
+ }
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Prompt Registry
3
+ *
4
+ * Store, version, and load prompts from YAML files
5
+ */
6
+
7
+ import { readFileSync, readdirSync, existsSync } from 'fs';
8
+ import { join, basename } from 'path';
9
+ import { logger } from '../utils/logger.js';
10
+
11
+ export interface PromptConfig {
12
+ template: string;
13
+ description?: string;
14
+ version?: string;
15
+ variables?: string[];
16
+ }
17
+
18
+ /**
19
+ * Prompt Registry
20
+ *
21
+ * Load and manage prompts from YAML files
22
+ */
23
+ export class PromptRegistry {
24
+ private prompts: Map<string, PromptConfig> = new Map();
25
+ private directory?: string;
26
+
27
+ constructor(directory?: string) {
28
+ if (directory) {
29
+ this.directory = directory;
30
+ this.loadPrompts(directory);
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Load prompts from a directory
36
+ */
37
+ private loadPrompts(directory: string): void {
38
+ if (!existsSync(directory)) {
39
+ logger.warn(`[PromptRegistry] Directory not found: ${directory}`);
40
+ return;
41
+ }
42
+
43
+ try {
44
+ const files = readdirSync(directory).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
45
+
46
+ for (const file of files) {
47
+ const content = readFileSync(join(directory, file), 'utf-8');
48
+ const config = this.parseYAML(content);
49
+ const name = file.replace(/\.(yaml|yml)$/, '');
50
+
51
+ this.prompts.set(name, config as PromptConfig);
52
+ }
53
+
54
+ logger.info(`[PromptRegistry] Loaded ${this.prompts.size} prompts from ${directory}`);
55
+ } catch (error: any) {
56
+ logger.error(`[PromptRegistry] Error loading prompts: ${error.message}`);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Simple YAML parser
62
+ */
63
+ private parseYAML(content: string): any {
64
+ const result: any = {};
65
+ const lines = content.split('\n');
66
+ let currentKey = '';
67
+ let currentValue: string[] = [];
68
+ let inArray = false;
69
+
70
+ for (const line of lines) {
71
+ const trimmed = line.trim();
72
+
73
+ if (!trimmed || trimmed.startsWith('#')) continue;
74
+
75
+ // Check for key: value
76
+ const keyMatch = trimmed.match(/^(\w+):\s*(.*)$/);
77
+
78
+ if (keyMatch) {
79
+ if (currentKey) {
80
+ result[currentKey] = currentValue.join('\n').trim();
81
+ }
82
+
83
+ currentKey = keyMatch[1];
84
+ const value = keyMatch[2];
85
+
86
+ if (value.startsWith('[') && value.endsWith(']')) {
87
+ // Array
88
+ result[currentKey] = value.slice(1, -1).split(',').map((s: string) => s.trim());
89
+ currentKey = '';
90
+ } else if (value) {
91
+ currentValue = [value];
92
+ } else {
93
+ currentValue = [];
94
+ }
95
+ } else if (trimmed.startsWith('- ')) {
96
+ // Array item
97
+ if (!Array.isArray(result[currentKey])) {
98
+ result[currentKey] = [];
99
+ }
100
+ (result[currentKey] as any[]).push(trimmed.slice(2).trim());
101
+ } else {
102
+ // Continuation
103
+ currentValue.push(trimmed);
104
+ }
105
+ }
106
+
107
+ if (currentKey) {
108
+ result[currentKey] = currentValue.join('\n').trim();
109
+ }
110
+
111
+ return result;
112
+ }
113
+
114
+ /**
115
+ * Register a prompt
116
+ */
117
+ register(name: string, config: PromptConfig): void {
118
+ this.prompts.set(name, config);
119
+ logger.debug(`[PromptRegistry] Registered prompt: ${name}`);
120
+ }
121
+
122
+ /**
123
+ * Load a prompt by name (supports @version syntax)
124
+ */
125
+ load(name: string): string {
126
+ const [baseName, version] = name.split('@');
127
+ const prompt = this.prompts.get(baseName);
128
+
129
+ if (!prompt) {
130
+ throw new Error(`Prompt not found: ${baseName}`);
131
+ }
132
+
133
+ if (version && prompt.version && prompt.version !== version) {
134
+ throw new Error(`Version ${version} not found for prompt: ${baseName}`);
135
+ }
136
+
137
+ return prompt.template;
138
+ }
139
+
140
+ /**
141
+ * Get prompt config
142
+ */
143
+ get(name: string): PromptConfig | undefined {
144
+ return this.prompts.get(name);
145
+ }
146
+
147
+ /**
148
+ * List all prompts
149
+ */
150
+ list(): string[] {
151
+ return Array.from(this.prompts.keys());
152
+ }
153
+
154
+ /**
155
+ * Format a prompt with variables
156
+ */
157
+ format(template: string, variables: Record<string, string>): string {
158
+ let result = template;
159
+
160
+ for (const [key, value] of Object.entries(variables)) {
161
+ result = result.replace(new RegExp(`\\{${key}\\}`, 'g'), value);
162
+ }
163
+
164
+ return result;
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Factory function
170
+ */
171
+ export function createPromptRegistry(directory?: string): PromptRegistry {
172
+ return new PromptRegistry(directory);
173
+ }
174
+
175
+ /**
176
+ * Simple Prompt Template (standalone)
177
+ */
178
+ export class SimplePromptTemplate {
179
+ private template: string;
180
+ private inputVariables: string[];
181
+
182
+ constructor(template: string) {
183
+ this.template = template;
184
+ this.inputVariables = this.extractVariables(template);
185
+ }
186
+
187
+ private extractVariables(template: string): string[] {
188
+ const matches = template.match(/\{(\w+)\}/g) || [];
189
+ return [...new Set(matches.map(m => m.slice(1, -1)))];
190
+ }
191
+
192
+ format(variables: Record<string, string>): string {
193
+ let result = this.template;
194
+
195
+ for (const [key, value] of Object.entries(variables)) {
196
+ result = result.replace(new RegExp(`\\{${key}\\}`, 'g'), value);
197
+ }
198
+
199
+ return result;
200
+ }
201
+
202
+ get inputVars(): string[] {
203
+ return this.inputVariables;
204
+ }
205
+ }