@cogitator-ai/self-modifying 17.0.17 → 18.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 (114) hide show
  1. package/README.md +112 -48
  2. package/dist/architecture-evolution/capability-analyzer.d.ts.map +1 -1
  3. package/dist/architecture-evolution/capability-analyzer.js +44 -40
  4. package/dist/architecture-evolution/capability-analyzer.js.map +1 -1
  5. package/dist/architecture-evolution/evolution-strategy.d.ts.map +1 -1
  6. package/dist/architecture-evolution/evolution-strategy.js +4 -2
  7. package/dist/architecture-evolution/evolution-strategy.js.map +1 -1
  8. package/dist/architecture-evolution/index.d.ts +2 -2
  9. package/dist/architecture-evolution/index.d.ts.map +1 -1
  10. package/dist/architecture-evolution/index.js +1 -1
  11. package/dist/architecture-evolution/index.js.map +1 -1
  12. package/dist/architecture-evolution/parameter-optimizer.d.ts +5 -4
  13. package/dist/architecture-evolution/parameter-optimizer.d.ts.map +1 -1
  14. package/dist/architecture-evolution/parameter-optimizer.js +62 -34
  15. package/dist/architecture-evolution/parameter-optimizer.js.map +1 -1
  16. package/dist/architecture-evolution/prompts.d.ts +21 -2
  17. package/dist/architecture-evolution/prompts.d.ts.map +1 -1
  18. package/dist/architecture-evolution/prompts.js +189 -37
  19. package/dist/architecture-evolution/prompts.js.map +1 -1
  20. package/dist/constraints/modification-validator.d.ts +1 -0
  21. package/dist/constraints/modification-validator.d.ts.map +1 -1
  22. package/dist/constraints/modification-validator.js +45 -18
  23. package/dist/constraints/modification-validator.js.map +1 -1
  24. package/dist/constraints/rollback-manager.d.ts +1 -0
  25. package/dist/constraints/rollback-manager.d.ts.map +1 -1
  26. package/dist/constraints/rollback-manager.js +25 -1
  27. package/dist/constraints/rollback-manager.js.map +1 -1
  28. package/dist/events/event-emitter.d.ts +0 -1
  29. package/dist/events/event-emitter.d.ts.map +1 -1
  30. package/dist/events/event-emitter.js +6 -10
  31. package/dist/events/event-emitter.js.map +1 -1
  32. package/dist/index.d.ts +4 -4
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +3 -3
  35. package/dist/index.js.map +1 -1
  36. package/dist/meta-reasoning/meta-reasoner.d.ts +4 -1
  37. package/dist/meta-reasoning/meta-reasoner.d.ts.map +1 -1
  38. package/dist/meta-reasoning/meta-reasoner.js +120 -43
  39. package/dist/meta-reasoning/meta-reasoner.js.map +1 -1
  40. package/dist/meta-reasoning/observation-collector.d.ts.map +1 -1
  41. package/dist/meta-reasoning/observation-collector.js +6 -2
  42. package/dist/meta-reasoning/observation-collector.js.map +1 -1
  43. package/dist/meta-reasoning/prompts.d.ts +10 -14
  44. package/dist/meta-reasoning/prompts.d.ts.map +1 -1
  45. package/dist/meta-reasoning/prompts.js +108 -33
  46. package/dist/meta-reasoning/prompts.js.map +1 -1
  47. package/dist/meta-reasoning/strategy-selector.d.ts.map +1 -1
  48. package/dist/meta-reasoning/strategy-selector.js +6 -0
  49. package/dist/meta-reasoning/strategy-selector.js.map +1 -1
  50. package/dist/self-modifying-agent.d.ts +88 -15
  51. package/dist/self-modifying-agent.d.ts.map +1 -1
  52. package/dist/self-modifying-agent.js +583 -376
  53. package/dist/self-modifying-agent.js.map +1 -1
  54. package/dist/tool-generation/gap-analyzer.d.ts +0 -1
  55. package/dist/tool-generation/gap-analyzer.d.ts.map +1 -1
  56. package/dist/tool-generation/gap-analyzer.js +16 -15
  57. package/dist/tool-generation/gap-analyzer.js.map +1 -1
  58. package/dist/tool-generation/generated-tool-store.d.ts.map +1 -1
  59. package/dist/tool-generation/generated-tool-store.js +90 -10
  60. package/dist/tool-generation/generated-tool-store.js.map +1 -1
  61. package/dist/tool-generation/index.d.ts +3 -3
  62. package/dist/tool-generation/index.d.ts.map +1 -1
  63. package/dist/tool-generation/index.js +2 -2
  64. package/dist/tool-generation/index.js.map +1 -1
  65. package/dist/tool-generation/prompts.d.ts +4 -3
  66. package/dist/tool-generation/prompts.d.ts.map +1 -1
  67. package/dist/tool-generation/prompts.js +96 -18
  68. package/dist/tool-generation/prompts.js.map +1 -1
  69. package/dist/tool-generation/tool-generator.d.ts +6 -6
  70. package/dist/tool-generation/tool-generator.d.ts.map +1 -1
  71. package/dist/tool-generation/tool-generator.js +88 -23
  72. package/dist/tool-generation/tool-generator.js.map +1 -1
  73. package/dist/tool-generation/tool-sandbox.d.ts +10 -11
  74. package/dist/tool-generation/tool-sandbox.d.ts.map +1 -1
  75. package/dist/tool-generation/tool-sandbox.js +346 -156
  76. package/dist/tool-generation/tool-sandbox.js.map +1 -1
  77. package/dist/tool-generation/tool-validator.d.ts +4 -8
  78. package/dist/tool-generation/tool-validator.d.ts.map +1 -1
  79. package/dist/tool-generation/tool-validator.js +74 -28
  80. package/dist/tool-generation/tool-validator.js.map +1 -1
  81. package/dist/utils/index.d.ts.map +1 -1
  82. package/dist/utils/index.js +30 -5
  83. package/dist/utils/index.js.map +1 -1
  84. package/dist/utils/llm-helper.d.ts.map +1 -1
  85. package/dist/utils/llm-helper.js +1 -0
  86. package/dist/utils/llm-helper.js.map +1 -1
  87. package/package.json +3 -12
  88. package/src/__tests__/component-regressions.test.ts +599 -0
  89. package/src/__tests__/meta-reasoning.test.ts +5 -1
  90. package/src/__tests__/sandbox-security.test.ts +186 -0
  91. package/src/__tests__/self-modifying-agent.test.ts +405 -11
  92. package/src/architecture-evolution/capability-analyzer.ts +63 -44
  93. package/src/architecture-evolution/evolution-strategy.ts +5 -2
  94. package/src/architecture-evolution/index.ts +2 -0
  95. package/src/architecture-evolution/parameter-optimizer.ts +82 -40
  96. package/src/architecture-evolution/prompts.ts +224 -42
  97. package/src/constraints/modification-validator.ts +48 -21
  98. package/src/constraints/rollback-manager.ts +20 -1
  99. package/src/events/event-emitter.ts +6 -15
  100. package/src/index.ts +14 -0
  101. package/src/meta-reasoning/meta-reasoner.ts +124 -52
  102. package/src/meta-reasoning/observation-collector.ts +6 -2
  103. package/src/meta-reasoning/prompts.ts +131 -56
  104. package/src/meta-reasoning/strategy-selector.ts +6 -0
  105. package/src/self-modifying-agent.ts +797 -413
  106. package/src/tool-generation/gap-analyzer.ts +21 -19
  107. package/src/tool-generation/generated-tool-store.ts +97 -10
  108. package/src/tool-generation/index.ts +13 -3
  109. package/src/tool-generation/prompts.ts +120 -18
  110. package/src/tool-generation/tool-generator.ts +120 -26
  111. package/src/tool-generation/tool-sandbox.ts +408 -206
  112. package/src/tool-generation/tool-validator.ts +90 -37
  113. package/src/utils/index.ts +29 -5
  114. package/src/utils/llm-helper.ts +1 -0
@@ -1,3 +1,4 @@
1
+ import { createHash } from 'node:crypto';
1
2
  import type {
2
3
  Tool,
3
4
  LLMBackend,
@@ -6,6 +7,7 @@ import type {
6
7
  ToolSelfGenerationConfig,
7
8
  } from '@cogitator-ai/types';
8
9
  import { buildGapAnalysisPrompt, parseGapAnalysisResponse } from './prompts';
10
+ import { llmChat } from '../utils/llm-helper';
9
11
 
10
12
  export interface GapAnalyzerOptions {
11
13
  llm: LLMBackend;
@@ -33,7 +35,7 @@ export class GapAnalyzer {
33
35
  previousGaps?: CapabilityGap[];
34
36
  }
35
37
  ): Promise<GapAnalysisResult> {
36
- const cacheKey = this.buildCacheKey(userIntent, availableTools);
38
+ const cacheKey = this.buildCacheKey(userIntent, availableTools, context);
37
39
  const cached = this.analysisCache.get(cacheKey);
38
40
  if (cached && Date.now() - cached.timestamp.getTime() < 60000) {
39
41
  return cached;
@@ -46,7 +48,8 @@ export class GapAnalyzer {
46
48
 
47
49
  const prompt = buildGapAnalysisPrompt(userIntent, toolSummaries, context?.failedAttempts);
48
50
 
49
- const response = await this.callLLM(
51
+ const content = await llmChat(
52
+ this.llm,
50
53
  [
51
54
  {
52
55
  role: 'system',
@@ -57,10 +60,10 @@ Consider tool composition before suggesting new tools.`,
57
60
  },
58
61
  { role: 'user', content: prompt },
59
62
  ],
60
- 0.3
63
+ { model: this.model, temperature: 0.3 }
61
64
  );
62
65
 
63
- const parsed = parseGapAnalysisResponse(response.content);
66
+ const parsed = parseGapAnalysisResponse(content);
64
67
  const filteredGaps = this.filterAndPrioritizeGaps(
65
68
  parsed.gaps,
66
69
  availableTools,
@@ -151,7 +154,6 @@ Consider tool composition before suggesting new tools.`,
151
154
  return 1.0;
152
155
  }
153
156
 
154
- const _intentWords = userIntent.toLowerCase().split(/\s+/).length;
155
157
  const toolDescriptionWords = availableTools
156
158
  .map((t) => t.description.toLowerCase().split(/\s+/))
157
159
  .flat();
@@ -217,25 +219,25 @@ Consider tool composition before suggesting new tools.`,
217
219
  return `Identified ${gaps.length} capability gap(s):\n${gapDescriptions}`;
218
220
  }
219
221
 
220
- private buildCacheKey(userIntent: string, tools: Tool[]): string {
222
+ private buildCacheKey(
223
+ userIntent: string,
224
+ tools: Tool[],
225
+ context?: { failedAttempts?: string[]; previousGaps?: CapabilityGap[] }
226
+ ): string {
221
227
  const toolSignature = tools
222
- .map((t) => t.name)
228
+ .map((t) => `${t.name}:${t.description}`)
223
229
  .sort()
224
- .join(',');
225
- return `${userIntent.slice(0, 100)}|${toolSignature}`;
230
+ .join('\n');
231
+ const raw = JSON.stringify([
232
+ userIntent,
233
+ toolSignature,
234
+ context?.failedAttempts ?? [],
235
+ (context?.previousGaps ?? []).map((g) => g.id).sort(),
236
+ ]);
237
+ return createHash('sha256').update(raw).digest('hex');
226
238
  }
227
239
 
228
240
  clearCache(): void {
229
241
  this.analysisCache.clear();
230
242
  }
231
-
232
- private async callLLM(
233
- messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>,
234
- temperature: number
235
- ) {
236
- if (this.llm.complete) {
237
- return this.llm.complete({ messages, temperature });
238
- }
239
- return this.llm.chat({ model: this.model, messages, temperature });
240
- }
241
243
  }
@@ -1,5 +1,78 @@
1
1
  import type { GeneratedTool, GeneratedToolStore as IGeneratedToolStore } from '@cogitator-ai/types';
2
2
 
3
+ const STOP_WORDS = new Set([
4
+ 'the',
5
+ 'and',
6
+ 'for',
7
+ 'not',
8
+ 'are',
9
+ 'was',
10
+ 'were',
11
+ 'been',
12
+ 'being',
13
+ 'have',
14
+ 'has',
15
+ 'had',
16
+ 'does',
17
+ 'did',
18
+ 'will',
19
+ 'would',
20
+ 'could',
21
+ 'should',
22
+ 'may',
23
+ 'might',
24
+ 'shall',
25
+ 'can',
26
+ 'this',
27
+ 'that',
28
+ 'these',
29
+ 'those',
30
+ 'with',
31
+ 'from',
32
+ 'into',
33
+ 'through',
34
+ 'during',
35
+ 'before',
36
+ 'after',
37
+ 'above',
38
+ 'below',
39
+ 'between',
40
+ 'under',
41
+ 'over',
42
+ 'again',
43
+ 'further',
44
+ 'then',
45
+ 'once',
46
+ 'here',
47
+ 'there',
48
+ 'when',
49
+ 'where',
50
+ 'why',
51
+ 'how',
52
+ 'all',
53
+ 'each',
54
+ 'every',
55
+ 'both',
56
+ 'few',
57
+ 'more',
58
+ 'most',
59
+ 'other',
60
+ 'some',
61
+ 'such',
62
+ 'only',
63
+ 'own',
64
+ 'same',
65
+ 'than',
66
+ 'too',
67
+ 'very',
68
+ 'just',
69
+ 'because',
70
+ 'but',
71
+ 'nor',
72
+ 'yet',
73
+ 'about',
74
+ ]);
75
+
3
76
  export interface ToolUsageRecord {
4
77
  toolId: string;
5
78
  timestamp: Date;
@@ -25,7 +98,7 @@ export class InMemoryGeneratedToolStore implements IGeneratedToolStore {
25
98
 
26
99
  async save(tool: GeneratedTool): Promise<void> {
27
100
  const existing = this.tools.get(tool.id);
28
- const version = existing ? existing.version + 1 : tool.version;
101
+ const version = existing ? existing.version + 1 : 1;
29
102
  this.tools.set(tool.id, { ...tool, version, updatedAt: new Date() });
30
103
  }
31
104
 
@@ -140,17 +213,27 @@ export class InMemoryGeneratedToolStore implements IGeneratedToolStore {
140
213
  }
141
214
 
142
215
  async findSimilar(description: string, limit: number = 5): Promise<GeneratedTool[]> {
143
- const descWords = new Set(description.toLowerCase().split(/\s+/));
216
+ const descWords = new Set(
217
+ description
218
+ .toLowerCase()
219
+ .split(/\s+/)
220
+ .filter((w) => w.length >= 3 && !STOP_WORDS.has(w))
221
+ );
144
222
  const scored: Array<{ tool: GeneratedTool; score: number }> = [];
145
223
 
146
224
  for (const tool of this.tools.values()) {
147
225
  if (tool.status === 'deprecated') continue;
148
226
 
149
- const toolWords = new Set(`${tool.name} ${tool.description}`.toLowerCase().split(/\s+/));
227
+ const toolWords = new Set(
228
+ `${tool.name} ${tool.description}`
229
+ .toLowerCase()
230
+ .split(/\s+/)
231
+ .filter((w) => w.length >= 3 && !STOP_WORDS.has(w))
232
+ );
150
233
 
151
234
  let matchCount = 0;
152
235
  for (const word of descWords) {
153
- if (word.length >= 3 && toolWords.has(word)) {
236
+ if (toolWords.has(word)) {
154
237
  matchCount++;
155
238
  }
156
239
  }
@@ -204,13 +287,13 @@ export class InMemoryGeneratedToolStore implements IGeneratedToolStore {
204
287
  let removed = 0;
205
288
  const now = Date.now();
206
289
 
290
+ const keysToRemove: string[] = [];
291
+
207
292
  for (const [id, tool] of this.tools) {
208
293
  if (options.maxAge && tool.createdAt) {
209
294
  const age = now - tool.createdAt.getTime();
210
295
  if (age > options.maxAge && tool.status !== 'active') {
211
- this.tools.delete(id);
212
- this.usageRecords.delete(id);
213
- removed++;
296
+ keysToRemove.push(id);
214
297
  continue;
215
298
  }
216
299
  }
@@ -218,13 +301,17 @@ export class InMemoryGeneratedToolStore implements IGeneratedToolStore {
218
301
  if (options.minUsage !== undefined) {
219
302
  const usage = tool.usageCount || 0;
220
303
  if (usage < options.minUsage && tool.status === 'deprecated') {
221
- this.tools.delete(id);
222
- this.usageRecords.delete(id);
223
- removed++;
304
+ keysToRemove.push(id);
224
305
  }
225
306
  }
226
307
  }
227
308
 
309
+ for (const id of keysToRemove) {
310
+ this.tools.delete(id);
311
+ this.usageRecords.delete(id);
312
+ removed++;
313
+ }
314
+
228
315
  if (options.maxDeprecated !== undefined) {
229
316
  const deprecated = Array.from(this.tools.values())
230
317
  .filter((t) => t.status === 'deprecated')
@@ -1,7 +1,17 @@
1
1
  export { GapAnalyzer, type GapAnalyzerOptions } from './gap-analyzer';
2
- export { ToolGenerator, type ToolGeneratorOptions, type GenerationResult } from './tool-generator';
3
- export { ToolValidator, type ToolValidatorOptions } from './tool-validator';
4
- export { ToolSandbox, DEFAULT_SANDBOX_CONFIG } from './tool-sandbox';
2
+ export {
3
+ ToolGenerator,
4
+ type ToolGeneratorOptions,
5
+ type GenerateOptions,
6
+ type GenerationResult,
7
+ } from './tool-generator';
8
+ export { ToolValidator, type ToolValidatorOptions, type ValidationRule } from './tool-validator';
9
+ export {
10
+ ToolSandbox,
11
+ DEFAULT_SANDBOX_CONFIG,
12
+ deepEqual,
13
+ type SandboxTestCase,
14
+ } from './tool-sandbox';
5
15
  export {
6
16
  InMemoryGeneratedToolStore,
7
17
  type ToolUsageRecord,
@@ -7,9 +7,30 @@ Rules:
7
7
  - Plain JavaScript only, no TypeScript
8
8
  - No require/import/eval/process/fetch
9
9
  - Must have an async function called "execute"
10
+ - Always propagate errors with throw, never silently swallow them
10
11
 
11
12
  Example response:
12
- {"name":"add","description":"Add two numbers","implementation":"async function execute(params) { try { return params.a + params.b; } catch(e) { return 0; } }","parameters":{"type":"object","properties":{"a":{"type":"number"},"b":{"type":"number"}},"required":["a","b"]},"reasoning":"Simple addition"}`;
13
+ {"name":"add","description":"Add two numbers","implementation":"async function execute(params) { if (typeof params.a !== 'number' || typeof params.b !== 'number') { throw new Error('Parameters a and b must be numbers'); } return params.a + params.b; }","parameters":{"type":"object","properties":{"a":{"type":"number"},"b":{"type":"number"}},"required":["a","b"]},"reasoning":"Simple addition"}`;
14
+
15
+ const TOOL_NAME_REGEX = /^[a-z_][a-z0-9_]*$/;
16
+
17
+ function sanitizeForPrompt(value: string): string {
18
+ return value
19
+ .replace(/\\/g, '\\\\')
20
+ .replace(/"/g, '\\"')
21
+ .replace(/\n/g, '\\n')
22
+ .replace(/\r/g, '\\r')
23
+ .replace(/\t/g, '\\t');
24
+ }
25
+
26
+ function sanitizeToolName(name: string): string {
27
+ const cleaned = name
28
+ .toLowerCase()
29
+ .replace(/[^a-z0-9_]/g, '_')
30
+ .replace(/^[0-9]/, '_$&')
31
+ .slice(0, 64);
32
+ return TOOL_NAME_REGEX.test(cleaned) ? cleaned : `generated_tool_${Date.now()}`;
33
+ }
13
34
 
14
35
  export function buildGapAnalysisPrompt(
15
36
  userIntent: string,
@@ -52,19 +73,59 @@ Respond with a JSON object:
52
73
 
53
74
  export function buildToolGenerationPrompt(
54
75
  gap: CapabilityGap,
55
- _existingTools: Array<{ name: string; description: string }>,
56
- _constraints?: {
76
+ existingTools: Array<{ name: string; description: string }>,
77
+ constraints?: {
57
78
  maxLines?: number;
58
79
  allowedModules?: string[];
59
80
  securityLevel?: 'strict' | 'moderate' | 'permissive';
81
+ parameters?: Record<string, unknown>;
60
82
  }
61
83
  ): string {
62
- return `Create a tool named "${gap.suggestedToolName}".
63
- Task: ${gap.description}
64
- Formula/Logic: ${gap.requiredCapability}
84
+ const toolName = sanitizeToolName(gap.suggestedToolName);
85
+ const description = sanitizeForPrompt(gap.description);
86
+ const capability = sanitizeForPrompt(gap.requiredCapability);
87
+
88
+ const existingToolsSection =
89
+ existingTools.length > 0
90
+ ? `\nEXISTING TOOLS (avoid duplication, consider composing with these):\n${existingTools.map((t) => `- ${t.name}: ${t.description}`).join('\n')}\n`
91
+ : '';
92
+
93
+ const constraintsSection: string[] = [];
94
+ if (constraints?.maxLines) {
95
+ constraintsSection.push(`- Maximum ${constraints.maxLines} lines of code`);
96
+ }
97
+ if (constraints?.securityLevel) {
98
+ constraintsSection.push(`- Security level: ${constraints.securityLevel}`);
99
+ }
100
+ if (constraints?.allowedModules && constraints.allowedModules.length > 0) {
101
+ constraintsSection.push(
102
+ `- Only these modules may be referenced: ${constraints.allowedModules.join(', ')}`
103
+ );
104
+ }
105
+ if (constraints?.securityLevel === 'strict') {
106
+ constraintsSection.push(
107
+ '- No eval, Function constructor, process, global, globalThis, fetch, setTimeout, setInterval',
108
+ '- No bracket notation access to global objects',
109
+ '- No prototype chain manipulation'
110
+ );
111
+ }
65
112
 
113
+ const constraintsText =
114
+ constraintsSection.length > 0 ? `\nCONSTRAINTS:\n${constraintsSection.join('\n')}\n` : '';
115
+
116
+ const parametersJson = constraints?.parameters
117
+ ? JSON.stringify(constraints.parameters)
118
+ : '{"type":"object","properties":{/* DEFINE PARAMS */},"required":[/* REQUIRED PARAMS */]}';
119
+ const parametersText = constraints?.parameters
120
+ ? `\nThe tool MUST accept exactly this parameters JSON Schema: ${parametersJson}\n`
121
+ : '';
122
+
123
+ return `Create a tool named "${toolName}".
124
+ Task: ${description}
125
+ Formula/Logic: ${capability}
126
+ ${existingToolsSection}${constraintsText}${parametersText}
66
127
  Respond with ONLY this JSON (no other text):
67
- {"name":"${gap.suggestedToolName}","description":"${gap.description}","implementation":"async function execute(params) { try { /* YOUR CODE HERE */ } catch(e) { throw e; } }","parameters":{"type":"object","properties":{/* DEFINE PARAMS */},"required":[/* REQUIRED PARAMS */]},"reasoning":"explanation"}`;
128
+ {"name":"${toolName}","description":"${description}","implementation":"async function execute(params) { /* YOUR CODE HERE — throw on invalid input */ }","parameters":${parametersJson},"reasoning":"explanation"}`;
68
129
  }
69
130
 
70
131
  export function buildToolValidationPrompt(
@@ -78,7 +139,7 @@ Name: ${tool.name}
78
139
  Description: ${tool.description}
79
140
 
80
141
  IMPLEMENTATION:
81
- \`\`\`typescript
142
+ \`\`\`javascript
82
143
  ${tool.implementation}
83
144
  \`\`\`
84
145
 
@@ -123,7 +184,7 @@ Name: ${tool.name}
123
184
  Description: ${tool.description}
124
185
 
125
186
  CURRENT IMPLEMENTATION:
126
- \`\`\`typescript
187
+ \`\`\`javascript
127
188
  ${tool.implementation}
128
189
  \`\`\`
129
190
 
@@ -165,11 +226,16 @@ export function parseGapAnalysisResponse(response: string): {
165
226
  id: String(g.id || `gap_${idx}`),
166
227
  description: String(g.description || ''),
167
228
  requiredCapability: String(g.requiredCapability || ''),
168
- suggestedToolName: String(g.suggestedToolName || `generated_tool_${idx}`),
229
+ suggestedToolName: sanitizeToolName(
230
+ String(g.suggestedToolName || `generated_tool_${idx}`)
231
+ ),
169
232
  complexity: (['simple', 'moderate', 'complex'].includes(String(g.complexity))
170
233
  ? g.complexity
171
234
  : 'moderate') as 'simple' | 'moderate' | 'complex',
172
- confidence: typeof g.confidence === 'number' ? g.confidence : 0.5,
235
+ confidence:
236
+ typeof g.confidence === 'number' && Number.isFinite(g.confidence)
237
+ ? Math.min(1, Math.max(0, g.confidence))
238
+ : 0.5,
173
239
  reasoning: String(g.reasoning || ''),
174
240
  }))
175
241
  : [],
@@ -194,15 +260,29 @@ export function parseToolGenerationResponse(response: string): GeneratedTool | n
194
260
  } catch {}
195
261
  }
196
262
 
197
- const fnRegex = /(?:async\s+)?function\s+execute\s*\([^)]*\)\s*\{[\s\S]*?\n\}/;
263
+ const fnRegex = /(?:async\s+)?function\s+execute\s*\([^)]*\)\s*\{[\s\S]*\}/;
198
264
  const fnMatch = fnRegex.exec(response);
199
265
  if (fnMatch) {
266
+ let impl = fnMatch[0];
267
+ let braceDepth = 0;
268
+ let endIdx = impl.length;
269
+ const firstBrace = impl.indexOf('{');
270
+ for (let i = firstBrace; i < impl.length; i++) {
271
+ if (impl[i] === '{') braceDepth++;
272
+ if (impl[i] === '}') braceDepth--;
273
+ if (braceDepth === 0) {
274
+ endIdx = i + 1;
275
+ break;
276
+ }
277
+ }
278
+ impl = impl.slice(0, endIdx);
279
+
200
280
  const nameRegex = /["']?name["']?\s*[:=]\s*["']([^"']+)["']/;
201
281
  const nameMatch = nameRegex.exec(response);
202
282
  return buildToolFromParsed({
203
283
  name: nameMatch?.[1] ?? 'generated_tool',
204
284
  description: '',
205
- implementation: fnMatch[0],
285
+ implementation: impl,
206
286
  parameters: { type: 'object', properties: {} },
207
287
  });
208
288
  }
@@ -210,16 +290,38 @@ export function parseToolGenerationResponse(response: string): GeneratedTool | n
210
290
  return null;
211
291
  }
212
292
 
293
+ function normalizeParameters(value: unknown): Record<string, unknown> {
294
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
295
+ return { type: 'object', properties: {} };
296
+ }
297
+
298
+ const schema = value as Record<string, unknown>;
299
+ const properties =
300
+ schema.properties && typeof schema.properties === 'object' && !Array.isArray(schema.properties)
301
+ ? (schema.properties as Record<string, unknown>)
302
+ : {};
303
+ const required = Array.isArray(schema.required)
304
+ ? schema.required.filter(
305
+ (key): key is string => typeof key === 'string' && Object.hasOwn(properties, key)
306
+ )
307
+ : [];
308
+
309
+ const normalized: Record<string, unknown> = { ...schema, type: 'object', properties };
310
+ if (required.length > 0) {
311
+ normalized.required = required;
312
+ } else {
313
+ delete normalized.required;
314
+ }
315
+ return normalized;
316
+ }
317
+
213
318
  function buildToolFromParsed(parsed: Record<string, unknown>): GeneratedTool {
214
319
  return {
215
320
  id: `gen_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
216
- name: String(parsed.name),
321
+ name: sanitizeToolName(String(parsed.name)),
217
322
  description: String(parsed.description || ''),
218
323
  implementation: String(parsed.implementation),
219
- parameters: (parsed.parameters as GeneratedTool['parameters']) || {
220
- type: 'object',
221
- properties: {},
222
- },
324
+ parameters: normalizeParameters(parsed.parameters),
223
325
  createdAt: new Date(),
224
326
  version: 1,
225
327
  status: 'pending_validation',