erosolar-cli 1.7.14 → 1.7.16
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/dist/core/responseVerifier.d.ts +79 -0
- package/dist/core/responseVerifier.d.ts.map +1 -0
- package/dist/core/responseVerifier.js +443 -0
- package/dist/core/responseVerifier.js.map +1 -0
- package/dist/shell/interactiveShell.d.ts +10 -0
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +80 -0
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/ui/ShellUIAdapter.d.ts +3 -0
- package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
- package/dist/ui/ShellUIAdapter.js +4 -10
- package/dist/ui/ShellUIAdapter.js.map +1 -1
- package/dist/ui/persistentPrompt.d.ts +4 -0
- package/dist/ui/persistentPrompt.d.ts.map +1 -1
- package/dist/ui/persistentPrompt.js +10 -11
- package/dist/ui/persistentPrompt.js.map +1 -1
- package/package.json +1 -1
- package/dist/bin/core/agent.js +0 -362
- package/dist/bin/core/agentProfileManifest.js +0 -187
- package/dist/bin/core/agentProfiles.js +0 -34
- package/dist/bin/core/agentRulebook.js +0 -135
- package/dist/bin/core/agentSchemaLoader.js +0 -233
- package/dist/bin/core/contextManager.js +0 -412
- package/dist/bin/core/contextWindow.js +0 -122
- package/dist/bin/core/customCommands.js +0 -80
- package/dist/bin/core/errors/apiKeyErrors.js +0 -114
- package/dist/bin/core/errors/errorTypes.js +0 -340
- package/dist/bin/core/errors/safetyValidator.js +0 -304
- package/dist/bin/core/errors.js +0 -32
- package/dist/bin/core/modelDiscovery.js +0 -755
- package/dist/bin/core/preferences.js +0 -224
- package/dist/bin/core/schemaValidator.js +0 -92
- package/dist/bin/core/secretStore.js +0 -199
- package/dist/bin/core/sessionStore.js +0 -187
- package/dist/bin/core/toolRuntime.js +0 -290
- package/dist/bin/core/types.js +0 -1
- package/dist/bin/shell/bracketedPasteManager.js +0 -350
- package/dist/bin/shell/fileChangeTracker.js +0 -65
- package/dist/bin/shell/interactiveShell.js +0 -2908
- package/dist/bin/shell/liveStatus.js +0 -78
- package/dist/bin/shell/shellApp.js +0 -290
- package/dist/bin/shell/systemPrompt.js +0 -60
- package/dist/bin/shell/updateManager.js +0 -108
- package/dist/bin/ui/ShellUIAdapter.js +0 -459
- package/dist/bin/ui/UnifiedUIController.js +0 -183
- package/dist/bin/ui/animation/AnimationScheduler.js +0 -430
- package/dist/bin/ui/codeHighlighter.js +0 -854
- package/dist/bin/ui/designSystem.js +0 -121
- package/dist/bin/ui/display.js +0 -1222
- package/dist/bin/ui/interrupts/InterruptManager.js +0 -437
- package/dist/bin/ui/layout.js +0 -139
- package/dist/bin/ui/orchestration/StatusOrchestrator.js +0 -403
- package/dist/bin/ui/outputMode.js +0 -38
- package/dist/bin/ui/persistentPrompt.js +0 -183
- package/dist/bin/ui/richText.js +0 -338
- package/dist/bin/ui/shortcutsHelp.js +0 -87
- package/dist/bin/ui/telemetry/UITelemetry.js +0 -443
- package/dist/bin/ui/textHighlighter.js +0 -210
- package/dist/bin/ui/theme.js +0 -116
- package/dist/bin/ui/toolDisplay.js +0 -423
- package/dist/bin/ui/toolDisplayAdapter.js +0 -357
package/dist/bin/core/agent.js
DELETED
|
@@ -1,362 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Maximum number of context overflow recovery attempts
|
|
3
|
-
*/
|
|
4
|
-
const MAX_CONTEXT_RECOVERY_ATTEMPTS = 3;
|
|
5
|
-
/**
|
|
6
|
-
* Check if an error is a context overflow error
|
|
7
|
-
*/
|
|
8
|
-
function isContextOverflowError(error) {
|
|
9
|
-
if (!(error instanceof Error))
|
|
10
|
-
return false;
|
|
11
|
-
const message = error.message.toLowerCase();
|
|
12
|
-
return (message.includes('context length') ||
|
|
13
|
-
message.includes('token') && (message.includes('limit') || message.includes('exceed') || message.includes('maximum')) ||
|
|
14
|
-
message.includes('too long') ||
|
|
15
|
-
message.includes('too many tokens') ||
|
|
16
|
-
message.includes('max_tokens') ||
|
|
17
|
-
message.includes('context window'));
|
|
18
|
-
}
|
|
19
|
-
export class AgentRuntime {
|
|
20
|
-
constructor(options) {
|
|
21
|
-
this.messages = [];
|
|
22
|
-
this.activeRun = null;
|
|
23
|
-
this.provider = options.provider;
|
|
24
|
-
this.toolRuntime = options.toolRuntime;
|
|
25
|
-
this.callbacks = options.callbacks ?? {};
|
|
26
|
-
this.contextManager = options.contextManager ?? null;
|
|
27
|
-
const trimmedPrompt = options.systemPrompt.trim();
|
|
28
|
-
this.baseSystemPrompt = trimmedPrompt || null;
|
|
29
|
-
if (trimmedPrompt) {
|
|
30
|
-
this.messages.push({ role: 'system', content: trimmedPrompt });
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
async send(text, useStreaming = false) {
|
|
34
|
-
const prompt = text.trim();
|
|
35
|
-
if (!prompt) {
|
|
36
|
-
return '';
|
|
37
|
-
}
|
|
38
|
-
this.messages.push({ role: 'user', content: prompt });
|
|
39
|
-
const run = { startedAt: Date.now() };
|
|
40
|
-
this.activeRun = run;
|
|
41
|
-
try {
|
|
42
|
-
if (useStreaming && this.provider.generateStream) {
|
|
43
|
-
return await this.processConversationStreaming();
|
|
44
|
-
}
|
|
45
|
-
return await this.processConversation();
|
|
46
|
-
}
|
|
47
|
-
finally {
|
|
48
|
-
if (this.activeRun === run) {
|
|
49
|
-
this.activeRun = null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
async processConversation() {
|
|
54
|
-
let contextRecoveryAttempts = 0;
|
|
55
|
-
while (true) {
|
|
56
|
-
// Prune messages if approaching context limit (BEFORE generation)
|
|
57
|
-
await this.pruneMessagesIfNeeded();
|
|
58
|
-
try {
|
|
59
|
-
const response = await this.provider.generate(this.messages, this.providerTools);
|
|
60
|
-
const usage = response.usage ?? null;
|
|
61
|
-
const contextStats = this.getContextStats();
|
|
62
|
-
// Reset recovery attempts on successful generation
|
|
63
|
-
contextRecoveryAttempts = 0;
|
|
64
|
-
if (response.type === 'tool_calls') {
|
|
65
|
-
const narration = response.content?.trim();
|
|
66
|
-
if (narration) {
|
|
67
|
-
this.emitAssistantMessage(narration, { isFinal: false, usage, contextStats });
|
|
68
|
-
}
|
|
69
|
-
const assistantMessage = {
|
|
70
|
-
role: 'assistant',
|
|
71
|
-
content: response.content ?? '',
|
|
72
|
-
};
|
|
73
|
-
if (response.toolCalls?.length) {
|
|
74
|
-
assistantMessage.toolCalls = response.toolCalls;
|
|
75
|
-
}
|
|
76
|
-
this.messages.push(assistantMessage);
|
|
77
|
-
await this.resolveToolCalls(response.toolCalls);
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
const reply = response.content?.trim() ?? '';
|
|
81
|
-
if (reply) {
|
|
82
|
-
this.emitAssistantMessage(reply, { isFinal: true, usage, contextStats });
|
|
83
|
-
}
|
|
84
|
-
this.messages.push({ role: 'assistant', content: reply });
|
|
85
|
-
return reply;
|
|
86
|
-
}
|
|
87
|
-
catch (error) {
|
|
88
|
-
// Auto-recover from context overflow errors
|
|
89
|
-
if (isContextOverflowError(error) && contextRecoveryAttempts < MAX_CONTEXT_RECOVERY_ATTEMPTS) {
|
|
90
|
-
contextRecoveryAttempts++;
|
|
91
|
-
const recovered = await this.recoverFromContextOverflow(contextRecoveryAttempts);
|
|
92
|
-
if (recovered) {
|
|
93
|
-
// Retry the generation with reduced context
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
// Re-throw if not recoverable or recovery failed
|
|
98
|
-
throw error;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
async processConversationStreaming() {
|
|
103
|
-
if (!this.provider.generateStream) {
|
|
104
|
-
return this.processConversation();
|
|
105
|
-
}
|
|
106
|
-
let contextRecoveryAttempts = 0;
|
|
107
|
-
while (true) {
|
|
108
|
-
// Prune messages if approaching context limit (BEFORE generation)
|
|
109
|
-
await this.pruneMessagesIfNeeded();
|
|
110
|
-
try {
|
|
111
|
-
let fullContent = '';
|
|
112
|
-
const toolCalls = [];
|
|
113
|
-
let usage = null;
|
|
114
|
-
const stream = this.provider.generateStream(this.messages, this.providerTools);
|
|
115
|
-
for await (const chunk of stream) {
|
|
116
|
-
if (chunk.type === 'content' && chunk.content) {
|
|
117
|
-
fullContent += chunk.content;
|
|
118
|
-
this.callbacks.onStreamChunk?.(chunk.content);
|
|
119
|
-
}
|
|
120
|
-
else if (chunk.type === 'tool_call' && chunk.toolCall) {
|
|
121
|
-
toolCalls.push(chunk.toolCall);
|
|
122
|
-
}
|
|
123
|
-
else if (chunk.type === 'usage' && chunk.usage) {
|
|
124
|
-
usage = chunk.usage;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
// Reset recovery attempts on successful generation
|
|
128
|
-
contextRecoveryAttempts = 0;
|
|
129
|
-
const contextStats = this.getContextStats();
|
|
130
|
-
// Check if we got tool calls
|
|
131
|
-
if (toolCalls.length > 0) {
|
|
132
|
-
const narration = fullContent.trim();
|
|
133
|
-
if (narration) {
|
|
134
|
-
this.emitAssistantMessage(narration, { isFinal: false, usage, contextStats });
|
|
135
|
-
}
|
|
136
|
-
const assistantMessage = {
|
|
137
|
-
role: 'assistant',
|
|
138
|
-
content: fullContent,
|
|
139
|
-
toolCalls,
|
|
140
|
-
};
|
|
141
|
-
this.messages.push(assistantMessage);
|
|
142
|
-
await this.resolveToolCalls(toolCalls);
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
// Final message
|
|
146
|
-
const reply = fullContent.trim();
|
|
147
|
-
if (reply) {
|
|
148
|
-
this.emitAssistantMessage(reply, { isFinal: true, usage, contextStats });
|
|
149
|
-
}
|
|
150
|
-
this.messages.push({ role: 'assistant', content: reply });
|
|
151
|
-
return reply;
|
|
152
|
-
}
|
|
153
|
-
catch (error) {
|
|
154
|
-
// Auto-recover from context overflow errors
|
|
155
|
-
if (isContextOverflowError(error) && contextRecoveryAttempts < MAX_CONTEXT_RECOVERY_ATTEMPTS) {
|
|
156
|
-
contextRecoveryAttempts++;
|
|
157
|
-
const recovered = await this.recoverFromContextOverflow(contextRecoveryAttempts);
|
|
158
|
-
if (recovered) {
|
|
159
|
-
// Retry the generation with reduced context
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
// Re-throw if not recoverable or recovery failed
|
|
164
|
-
throw error;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
async resolveToolCalls(toolCalls) {
|
|
169
|
-
// Execute all tool calls in parallel for better performance
|
|
170
|
-
const results = await Promise.all(toolCalls.map(async (call) => ({
|
|
171
|
-
call,
|
|
172
|
-
output: await this.toolRuntime.execute(call),
|
|
173
|
-
})));
|
|
174
|
-
// Add results to messages in the same order as tool calls
|
|
175
|
-
for (const { call, output } of results) {
|
|
176
|
-
this.messages.push({
|
|
177
|
-
role: 'tool',
|
|
178
|
-
name: call.name,
|
|
179
|
-
toolCallId: call.id,
|
|
180
|
-
content: output,
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
get providerTools() {
|
|
185
|
-
return this.toolRuntime.listProviderTools();
|
|
186
|
-
}
|
|
187
|
-
emitAssistantMessage(content, metadata) {
|
|
188
|
-
if (!content) {
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
const elapsedMs = this.activeRun ? Date.now() - this.activeRun.startedAt : undefined;
|
|
192
|
-
const payload = { ...metadata };
|
|
193
|
-
if (typeof elapsedMs === 'number') {
|
|
194
|
-
payload.elapsedMs = elapsedMs;
|
|
195
|
-
}
|
|
196
|
-
this.callbacks.onAssistantMessage?.(content, payload);
|
|
197
|
-
}
|
|
198
|
-
getHistory() {
|
|
199
|
-
return this.messages.map(cloneMessage);
|
|
200
|
-
}
|
|
201
|
-
loadHistory(history) {
|
|
202
|
-
this.messages.length = 0;
|
|
203
|
-
if (history.length === 0) {
|
|
204
|
-
if (this.baseSystemPrompt) {
|
|
205
|
-
this.messages.push({ role: 'system', content: this.baseSystemPrompt });
|
|
206
|
-
}
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
for (const message of history) {
|
|
210
|
-
this.messages.push(cloneMessage(message));
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
clearHistory() {
|
|
214
|
-
this.messages.length = 0;
|
|
215
|
-
if (this.baseSystemPrompt) {
|
|
216
|
-
this.messages.push({ role: 'system', content: this.baseSystemPrompt });
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* Prune messages if approaching context limit
|
|
221
|
-
*
|
|
222
|
-
* This runs BEFORE each generation to ensure we stay within budget.
|
|
223
|
-
* If LLM summarization is available, it will create intelligent summaries
|
|
224
|
-
* instead of just removing old messages.
|
|
225
|
-
*/
|
|
226
|
-
async pruneMessagesIfNeeded() {
|
|
227
|
-
if (!this.contextManager) {
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
if (this.contextManager.isApproachingLimit(this.messages)) {
|
|
231
|
-
// Try LLM-based summarization first (preserves context better)
|
|
232
|
-
const result = await this.contextManager.pruneMessagesWithSummary(this.messages);
|
|
233
|
-
if (result.removed > 0) {
|
|
234
|
-
// Replace messages with pruned/summarized version
|
|
235
|
-
this.messages.length = 0;
|
|
236
|
-
this.messages.push(...result.pruned);
|
|
237
|
-
// Notify callback with enriched stats
|
|
238
|
-
const stats = this.contextManager.getStats(this.messages);
|
|
239
|
-
const enrichedStats = {
|
|
240
|
-
...stats,
|
|
241
|
-
summarized: result.summarized,
|
|
242
|
-
method: result.summarized ? 'llm-summary' : 'simple-prune',
|
|
243
|
-
};
|
|
244
|
-
this.callbacks.onContextPruned?.(result.removed, enrichedStats);
|
|
245
|
-
if (process.env['DEBUG_CONTEXT']) {
|
|
246
|
-
console.warn(`[Context Manager] ${result.summarized ? 'Summarized' : 'Pruned'} ${result.removed} messages. ` +
|
|
247
|
-
`Tokens: ${stats.totalTokens} (${stats.percentage}%)`);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
/**
|
|
253
|
-
* Get current context statistics
|
|
254
|
-
*/
|
|
255
|
-
getContextStats() {
|
|
256
|
-
if (!this.contextManager) {
|
|
257
|
-
return null;
|
|
258
|
-
}
|
|
259
|
-
return this.contextManager.getStats(this.messages);
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Get context manager instance
|
|
263
|
-
*/
|
|
264
|
-
getContextManager() {
|
|
265
|
-
return this.contextManager;
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Auto-recover from context overflow errors by aggressively pruning messages.
|
|
269
|
-
*
|
|
270
|
-
* This is called when an API call fails due to context length exceeding limits.
|
|
271
|
-
* It performs increasingly aggressive pruning on each attempt:
|
|
272
|
-
* - Attempt 1: Remove 30% of oldest messages
|
|
273
|
-
* - Attempt 2: Remove 50% of oldest messages
|
|
274
|
-
* - Attempt 3: Remove 70% of oldest messages (keep only recent)
|
|
275
|
-
*
|
|
276
|
-
* @returns true if recovery was successful (context was reduced)
|
|
277
|
-
*/
|
|
278
|
-
async recoverFromContextOverflow(attempt) {
|
|
279
|
-
// Calculate reduction percentage based on attempt
|
|
280
|
-
const reductionPercentages = [0.3, 0.5, 0.7];
|
|
281
|
-
const reductionPercent = reductionPercentages[attempt - 1] ?? 0.7;
|
|
282
|
-
// Notify UI about recovery attempt
|
|
283
|
-
const message = `Context overflow detected. Auto-squishing context (attempt ${attempt}/${MAX_CONTEXT_RECOVERY_ATTEMPTS}, removing ${Math.round(reductionPercent * 100)}% of history)...`;
|
|
284
|
-
this.callbacks.onContextRecovery?.(attempt, MAX_CONTEXT_RECOVERY_ATTEMPTS, message);
|
|
285
|
-
this.callbacks.onContextSquishing?.(message);
|
|
286
|
-
// Separate system messages from conversation
|
|
287
|
-
const systemMessages = [];
|
|
288
|
-
const conversationMessages = [];
|
|
289
|
-
for (const msg of this.messages) {
|
|
290
|
-
if (msg.role === 'system') {
|
|
291
|
-
systemMessages.push(msg);
|
|
292
|
-
}
|
|
293
|
-
else {
|
|
294
|
-
conversationMessages.push(msg);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
// Calculate how many messages to remove
|
|
298
|
-
const removeCount = Math.floor(conversationMessages.length * reductionPercent);
|
|
299
|
-
if (removeCount === 0 || conversationMessages.length <= 2) {
|
|
300
|
-
// Nothing to remove or too few messages - can't recover
|
|
301
|
-
return false;
|
|
302
|
-
}
|
|
303
|
-
// Keep recent messages
|
|
304
|
-
const keepMessages = conversationMessages.slice(removeCount);
|
|
305
|
-
// Rebuild message array
|
|
306
|
-
this.messages.length = 0;
|
|
307
|
-
// Add system messages
|
|
308
|
-
for (const sys of systemMessages) {
|
|
309
|
-
this.messages.push(sys);
|
|
310
|
-
}
|
|
311
|
-
// Add summary notice
|
|
312
|
-
this.messages.push({
|
|
313
|
-
role: 'system',
|
|
314
|
-
content: `[Auto Context Recovery] Removed ${removeCount} earlier messages to stay within token limits. Conversation context has been compressed.`,
|
|
315
|
-
});
|
|
316
|
-
// Add remaining conversation
|
|
317
|
-
for (const msg of keepMessages) {
|
|
318
|
-
this.messages.push(msg);
|
|
319
|
-
}
|
|
320
|
-
// Notify about pruning
|
|
321
|
-
const stats = this.contextManager?.getStats(this.messages) ?? {};
|
|
322
|
-
this.callbacks.onContextPruned?.(removeCount, {
|
|
323
|
-
...stats,
|
|
324
|
-
method: 'emergency-recovery',
|
|
325
|
-
attempt,
|
|
326
|
-
removedPercent: reductionPercent * 100,
|
|
327
|
-
});
|
|
328
|
-
return true;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
function cloneMessage(message) {
|
|
332
|
-
switch (message.role) {
|
|
333
|
-
case 'assistant':
|
|
334
|
-
const clone = {
|
|
335
|
-
role: 'assistant',
|
|
336
|
-
content: message.content,
|
|
337
|
-
};
|
|
338
|
-
if (message.toolCalls) {
|
|
339
|
-
clone.toolCalls = message.toolCalls.map(cloneToolCall);
|
|
340
|
-
}
|
|
341
|
-
return clone;
|
|
342
|
-
case 'tool':
|
|
343
|
-
return {
|
|
344
|
-
role: 'tool',
|
|
345
|
-
name: message.name,
|
|
346
|
-
content: message.content,
|
|
347
|
-
toolCallId: message.toolCallId,
|
|
348
|
-
};
|
|
349
|
-
case 'system':
|
|
350
|
-
return { role: 'system', content: message.content };
|
|
351
|
-
case 'user':
|
|
352
|
-
default:
|
|
353
|
-
return { role: 'user', content: message.content };
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
function cloneToolCall(call) {
|
|
357
|
-
return {
|
|
358
|
-
id: call.id,
|
|
359
|
-
name: call.name,
|
|
360
|
-
arguments: { ...(call.arguments ?? {}) },
|
|
361
|
-
};
|
|
362
|
-
}
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import { getAgentSchemas } from './agentSchemaLoader.js';
|
|
2
|
-
// Load from centralized schema
|
|
3
|
-
const manifest = (() => {
|
|
4
|
-
const schemas = getAgentSchemas();
|
|
5
|
-
const rawManifest = {
|
|
6
|
-
contractVersion: schemas.contractVersion,
|
|
7
|
-
version: schemas.version,
|
|
8
|
-
label: schemas.label,
|
|
9
|
-
description: schemas.description,
|
|
10
|
-
profiles: schemas.profiles,
|
|
11
|
-
metadata: schemas.metadata,
|
|
12
|
-
};
|
|
13
|
-
return normalizeManifest(rawManifest);
|
|
14
|
-
})();
|
|
15
|
-
export function getAgentProfileManifest() {
|
|
16
|
-
return manifest;
|
|
17
|
-
}
|
|
18
|
-
function normalizeManifest(raw) {
|
|
19
|
-
if (!isRecord(raw)) {
|
|
20
|
-
throw new Error('Agent profile manifest is malformed: expected an object.');
|
|
21
|
-
}
|
|
22
|
-
const profiles = Array.isArray(raw.profiles) ? raw.profiles.map(normalizeProfileEntry) : null;
|
|
23
|
-
if (!profiles?.length) {
|
|
24
|
-
throw new Error('Agent profile manifest must include at least one profile entry.');
|
|
25
|
-
}
|
|
26
|
-
const seen = new Set();
|
|
27
|
-
for (const entry of profiles) {
|
|
28
|
-
if (seen.has(entry.name)) {
|
|
29
|
-
throw new Error(`Agent profile manifest contains duplicate profile id "${entry.name}".`);
|
|
30
|
-
}
|
|
31
|
-
seen.add(entry.name);
|
|
32
|
-
}
|
|
33
|
-
const manifest = {
|
|
34
|
-
contractVersion: requireString(raw.contractVersion, 'contractVersion'),
|
|
35
|
-
version: requireString(raw.version, 'version'),
|
|
36
|
-
profiles,
|
|
37
|
-
};
|
|
38
|
-
const label = optionalString(raw.label);
|
|
39
|
-
if (label) {
|
|
40
|
-
manifest.label = label;
|
|
41
|
-
}
|
|
42
|
-
const description = optionalString(raw.description);
|
|
43
|
-
if (description) {
|
|
44
|
-
manifest.description = description;
|
|
45
|
-
}
|
|
46
|
-
const metadata = normalizeRecord(raw.metadata);
|
|
47
|
-
if (metadata) {
|
|
48
|
-
manifest.metadata = metadata;
|
|
49
|
-
}
|
|
50
|
-
return manifest;
|
|
51
|
-
}
|
|
52
|
-
function normalizeProfileEntry(raw) {
|
|
53
|
-
const name = requireString(raw.name, 'profile.name');
|
|
54
|
-
const profile = {
|
|
55
|
-
name,
|
|
56
|
-
label: requireString(raw.label, `profiles["${name}"].label`),
|
|
57
|
-
defaultProvider: requireProvider(raw.defaultProvider, name),
|
|
58
|
-
defaultModel: requireString(raw.defaultModel, `profiles["${name}"].defaultModel`),
|
|
59
|
-
systemPrompt: normalizePrompt(raw.systemPrompt, name),
|
|
60
|
-
rulebook: normalizeRulebook(raw.rulebook, name),
|
|
61
|
-
};
|
|
62
|
-
const description = optionalString(raw.description);
|
|
63
|
-
if (description) {
|
|
64
|
-
profile.description = description;
|
|
65
|
-
}
|
|
66
|
-
const temperature = optionalNumber(raw.temperature, `profiles["${name}"].temperature`);
|
|
67
|
-
if (typeof temperature === 'number') {
|
|
68
|
-
profile.temperature = temperature;
|
|
69
|
-
}
|
|
70
|
-
const maxTokens = optionalInteger(raw.maxTokens, `profiles["${name}"].maxTokens`);
|
|
71
|
-
if (typeof maxTokens === 'number') {
|
|
72
|
-
profile.maxTokens = maxTokens;
|
|
73
|
-
}
|
|
74
|
-
const metadata = normalizeRecord(raw.metadata);
|
|
75
|
-
if (metadata) {
|
|
76
|
-
profile.metadata = metadata;
|
|
77
|
-
}
|
|
78
|
-
return profile;
|
|
79
|
-
}
|
|
80
|
-
function normalizePrompt(raw, profile) {
|
|
81
|
-
if (!isRecord(raw)) {
|
|
82
|
-
throw new Error(`Profile "${profile}" is missing a valid systemPrompt definition.`);
|
|
83
|
-
}
|
|
84
|
-
if (raw.type === 'literal') {
|
|
85
|
-
return normalizeLiteralPrompt(raw, profile);
|
|
86
|
-
}
|
|
87
|
-
if (raw.type === 'rulebook') {
|
|
88
|
-
return normalizeRulebookPrompt(raw, profile);
|
|
89
|
-
}
|
|
90
|
-
throw new Error(`Profile "${profile}" has an unsupported systemPrompt type.`);
|
|
91
|
-
}
|
|
92
|
-
function normalizeLiteralPrompt(raw, profile) {
|
|
93
|
-
const prompt = {
|
|
94
|
-
type: 'literal',
|
|
95
|
-
content: requireString(raw.content, `profiles["${profile}"].systemPrompt.content`),
|
|
96
|
-
};
|
|
97
|
-
const metadata = normalizeRecord(raw.metadata);
|
|
98
|
-
if (metadata) {
|
|
99
|
-
prompt.metadata = metadata;
|
|
100
|
-
}
|
|
101
|
-
return prompt;
|
|
102
|
-
}
|
|
103
|
-
function normalizeRulebookPrompt(raw, _profile) {
|
|
104
|
-
const prompt = { type: 'rulebook' };
|
|
105
|
-
const template = optionalString(raw.template);
|
|
106
|
-
if (template) {
|
|
107
|
-
prompt.template = template;
|
|
108
|
-
}
|
|
109
|
-
const metadata = normalizeRecord(raw.metadata);
|
|
110
|
-
if (metadata) {
|
|
111
|
-
prompt.metadata = metadata;
|
|
112
|
-
}
|
|
113
|
-
return prompt;
|
|
114
|
-
}
|
|
115
|
-
function normalizeRulebook(raw, profile) {
|
|
116
|
-
if (!isRecord(raw)) {
|
|
117
|
-
throw new Error(`Profile "${profile}" is missing a valid rulebook reference.`);
|
|
118
|
-
}
|
|
119
|
-
const reference = {
|
|
120
|
-
file: requireString(raw.file, `profiles["${profile}"].rulebook.file`),
|
|
121
|
-
};
|
|
122
|
-
const version = optionalString(raw.version);
|
|
123
|
-
if (version) {
|
|
124
|
-
reference.version = version;
|
|
125
|
-
}
|
|
126
|
-
const contractVersion = optionalString(raw.contractVersion);
|
|
127
|
-
if (contractVersion) {
|
|
128
|
-
reference.contractVersion = contractVersion;
|
|
129
|
-
}
|
|
130
|
-
const description = optionalString(raw.description);
|
|
131
|
-
if (description) {
|
|
132
|
-
reference.description = description;
|
|
133
|
-
}
|
|
134
|
-
const metadata = normalizeRecord(raw.metadata);
|
|
135
|
-
if (metadata) {
|
|
136
|
-
reference.metadata = metadata;
|
|
137
|
-
}
|
|
138
|
-
return reference;
|
|
139
|
-
}
|
|
140
|
-
function requireProvider(value, profile) {
|
|
141
|
-
const resolved = requireString(value, `profiles["${profile}"].defaultProvider`);
|
|
142
|
-
return resolved;
|
|
143
|
-
}
|
|
144
|
-
function requireString(value, field) {
|
|
145
|
-
if (typeof value !== 'string') {
|
|
146
|
-
throw new Error(`Agent profile manifest is missing required field "${field}".`);
|
|
147
|
-
}
|
|
148
|
-
const trimmed = value.trim();
|
|
149
|
-
if (!trimmed) {
|
|
150
|
-
throw new Error(`Agent profile manifest field "${field}" cannot be blank.`);
|
|
151
|
-
}
|
|
152
|
-
return trimmed;
|
|
153
|
-
}
|
|
154
|
-
function optionalString(value) {
|
|
155
|
-
if (typeof value !== 'string') {
|
|
156
|
-
return undefined;
|
|
157
|
-
}
|
|
158
|
-
const trimmed = value.trim();
|
|
159
|
-
return trimmed.length ? trimmed : undefined;
|
|
160
|
-
}
|
|
161
|
-
function optionalNumber(value, field) {
|
|
162
|
-
if (value === undefined) {
|
|
163
|
-
return undefined;
|
|
164
|
-
}
|
|
165
|
-
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
166
|
-
throw new Error(`Agent profile manifest field "${field}" must be a finite number when provided.`);
|
|
167
|
-
}
|
|
168
|
-
return value;
|
|
169
|
-
}
|
|
170
|
-
function optionalInteger(value, field) {
|
|
171
|
-
if (value === undefined) {
|
|
172
|
-
return undefined;
|
|
173
|
-
}
|
|
174
|
-
if (typeof value !== 'number' || !Number.isInteger(value) || value <= 0) {
|
|
175
|
-
throw new Error(`Agent profile manifest field "${field}" must be a positive integer when provided.`);
|
|
176
|
-
}
|
|
177
|
-
return value;
|
|
178
|
-
}
|
|
179
|
-
function normalizeRecord(record) {
|
|
180
|
-
if (!isRecord(record)) {
|
|
181
|
-
return undefined;
|
|
182
|
-
}
|
|
183
|
-
return { ...record };
|
|
184
|
-
}
|
|
185
|
-
function isRecord(value) {
|
|
186
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
187
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
const registry = new Map();
|
|
2
|
-
export function registerAgentProfile(blueprint) {
|
|
3
|
-
if (!blueprint?.name) {
|
|
4
|
-
throw new Error('Agent profile name is required.');
|
|
5
|
-
}
|
|
6
|
-
const trimmedName = blueprint.name.trim();
|
|
7
|
-
if (!trimmedName) {
|
|
8
|
-
throw new Error('Agent profile name cannot be blank.');
|
|
9
|
-
}
|
|
10
|
-
const payload = Object.freeze({
|
|
11
|
-
...blueprint,
|
|
12
|
-
name: trimmedName,
|
|
13
|
-
label: blueprint.label.trim() || trimmedName,
|
|
14
|
-
frozen: true,
|
|
15
|
-
});
|
|
16
|
-
registry.set(trimmedName, payload);
|
|
17
|
-
}
|
|
18
|
-
export function hasAgentProfile(name) {
|
|
19
|
-
return registry.has(name.trim());
|
|
20
|
-
}
|
|
21
|
-
export function getAgentProfile(name) {
|
|
22
|
-
const profile = registry.get(name.trim());
|
|
23
|
-
if (!profile) {
|
|
24
|
-
const known = listAgentProfiles()
|
|
25
|
-
.map((entry) => entry.name)
|
|
26
|
-
.sort()
|
|
27
|
-
.join(', ');
|
|
28
|
-
throw new Error(`Unknown profile "${name}". Registered profiles: ${known || 'none'}.`);
|
|
29
|
-
}
|
|
30
|
-
return profile;
|
|
31
|
-
}
|
|
32
|
-
export function listAgentProfiles() {
|
|
33
|
-
return Array.from(registry.values());
|
|
34
|
-
}
|