@fastino-ai/pioneer-cli 0.1.0 → 0.2.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.
- package/README.md +161 -22
- package/bun.lock +82 -0
- package/cache/cache.db +0 -0
- package/cache/cache.db-shm +0 -0
- package/cache/cache.db-wal +0 -0
- package/fastino-ai-pioneer-cli-0.2.0.tgz +0 -0
- package/package.json +6 -3
- package/src/agent/Agent.ts +342 -0
- package/src/agent/BudgetManager.ts +167 -0
- package/src/agent/FileResolver.ts +321 -0
- package/src/agent/LLMClient.ts +435 -0
- package/src/agent/ToolRegistry.ts +97 -0
- package/src/agent/index.ts +15 -0
- package/src/agent/types.ts +84 -0
- package/src/chat/ChatApp.tsx +701 -0
- package/src/chat/index.ts +7 -0
- package/src/config.ts +185 -3
- package/src/evolution/EvalRunner.ts +301 -0
- package/src/evolution/EvolutionEngine.ts +319 -0
- package/src/evolution/FeedbackCollector.ts +197 -0
- package/src/evolution/ModelTrainer.ts +371 -0
- package/src/evolution/index.ts +18 -0
- package/src/evolution/types.ts +110 -0
- package/src/index.tsx +101 -2
- package/src/tools/bash.ts +184 -0
- package/src/tools/filesystem.ts +444 -0
- package/src/tools/index.ts +29 -0
- package/src/tools/modal.ts +269 -0
- package/src/tools/sandbox.ts +310 -0
- package/src/tools/training.ts +443 -0
- package/src/tools/wandb.ts +348 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent - Main orchestrator that coordinates tools and LLM calls
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
Message,
|
|
7
|
+
Tool,
|
|
8
|
+
ToolCall,
|
|
9
|
+
ToolResult,
|
|
10
|
+
AgentConfig,
|
|
11
|
+
ConversationContext,
|
|
12
|
+
LLMResponse,
|
|
13
|
+
Budget,
|
|
14
|
+
} from "./types.js";
|
|
15
|
+
import { LLMClient } from "./LLMClient.js";
|
|
16
|
+
import { ToolRegistry } from "./ToolRegistry.js";
|
|
17
|
+
import { BudgetManager } from "./BudgetManager.js";
|
|
18
|
+
import { FileResolver, type FileReference } from "./FileResolver.js";
|
|
19
|
+
|
|
20
|
+
const DEFAULT_SYSTEM_PROMPT = `You are an intelligent AI assistant with access to powerful tools for coding, file manipulation, and system operations.
|
|
21
|
+
|
|
22
|
+
Available capabilities:
|
|
23
|
+
- Execute bash commands to interact with the system
|
|
24
|
+
- Read, write, and edit files
|
|
25
|
+
- List and search directories
|
|
26
|
+
- Execute code in isolated sandboxes (Python, JavaScript, TypeScript, Bash, Ruby, Go)
|
|
27
|
+
|
|
28
|
+
Guidelines:
|
|
29
|
+
1. Be concise but thorough in your responses
|
|
30
|
+
2. When given a task, break it down into steps and execute them
|
|
31
|
+
3. Use tools to verify your work when appropriate
|
|
32
|
+
4. If something fails, analyze the error and try alternative approaches
|
|
33
|
+
5. Ask clarifying questions only when truly necessary
|
|
34
|
+
6. Report your progress and results clearly
|
|
35
|
+
|
|
36
|
+
You are operating in the user's local environment. Be careful with destructive operations.`;
|
|
37
|
+
|
|
38
|
+
export interface AgentEvents {
|
|
39
|
+
onMessage?: (message: Message) => void;
|
|
40
|
+
onToolCall?: (toolCall: ToolCall) => void;
|
|
41
|
+
onToolResult?: (result: ToolResult) => void;
|
|
42
|
+
onStream?: (chunk: string) => void;
|
|
43
|
+
onBudgetWarning?: (warnings: string[]) => void;
|
|
44
|
+
onFileReference?: (refs: FileReference[]) => void;
|
|
45
|
+
onError?: (error: Error) => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class Agent {
|
|
49
|
+
private llm: LLMClient;
|
|
50
|
+
private tools: ToolRegistry;
|
|
51
|
+
private budget: BudgetManager;
|
|
52
|
+
private config: AgentConfig;
|
|
53
|
+
private context: ConversationContext;
|
|
54
|
+
private events: AgentEvents;
|
|
55
|
+
private fileResolver: FileResolver;
|
|
56
|
+
private abortController: AbortController | null = null;
|
|
57
|
+
|
|
58
|
+
constructor(config: AgentConfig, events: AgentEvents = {}) {
|
|
59
|
+
this.config = config;
|
|
60
|
+
this.events = events;
|
|
61
|
+
|
|
62
|
+
this.llm = new LLMClient({
|
|
63
|
+
provider: config.provider,
|
|
64
|
+
model: config.model,
|
|
65
|
+
apiKey: config.apiKey,
|
|
66
|
+
baseUrl: config.baseUrl,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
this.tools = new ToolRegistry();
|
|
70
|
+
this.budget = new BudgetManager(
|
|
71
|
+
config.budget,
|
|
72
|
+
this.llm.getCostPerMillion()
|
|
73
|
+
);
|
|
74
|
+
this.fileResolver = new FileResolver(process.cwd());
|
|
75
|
+
|
|
76
|
+
this.context = {
|
|
77
|
+
messages: [],
|
|
78
|
+
budgetUsage: this.budget.getUsage(),
|
|
79
|
+
startTime: new Date(),
|
|
80
|
+
workingDirectory: process.cwd(),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
registerTool(tool: Tool): void {
|
|
85
|
+
this.tools.register(tool);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
registerTools(tools: Tool[]): void {
|
|
89
|
+
for (const tool of tools) {
|
|
90
|
+
this.tools.register(tool);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getTools(): Tool[] {
|
|
95
|
+
return this.tools.getAll();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async chat(userMessage: string, stream = true): Promise<string> {
|
|
99
|
+
// Create new abort controller for this chat session
|
|
100
|
+
this.abortController = new AbortController();
|
|
101
|
+
|
|
102
|
+
// Check budget before starting
|
|
103
|
+
const budgetCheck = this.budget.checkBudget();
|
|
104
|
+
if (!budgetCheck.withinBudget) {
|
|
105
|
+
const error = `Budget exhausted: ${budgetCheck.warnings.join(", ")}`;
|
|
106
|
+
this.events.onError?.(new Error(error));
|
|
107
|
+
return error;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (budgetCheck.warnings.length > 0) {
|
|
111
|
+
this.events.onBudgetWarning?.(budgetCheck.warnings);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Resolve @file references in the message
|
|
115
|
+
const resolved = this.fileResolver.resolve(userMessage);
|
|
116
|
+
|
|
117
|
+
// Notify about file references
|
|
118
|
+
if (resolved.references.length > 0) {
|
|
119
|
+
this.events.onFileReference?.(resolved.references);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Build the actual message content with file context
|
|
123
|
+
let messageContent = userMessage;
|
|
124
|
+
if (resolved.contextBlock) {
|
|
125
|
+
messageContent = resolved.contextBlock + "\n" + userMessage;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Add user message to context
|
|
129
|
+
const userMsg: Message = {
|
|
130
|
+
role: "user",
|
|
131
|
+
content: messageContent,
|
|
132
|
+
timestamp: new Date(),
|
|
133
|
+
};
|
|
134
|
+
this.context.messages.push(userMsg);
|
|
135
|
+
this.events.onMessage?.(userMsg);
|
|
136
|
+
|
|
137
|
+
// Run the agent loop
|
|
138
|
+
try {
|
|
139
|
+
return await this.runAgentLoop(stream);
|
|
140
|
+
} finally {
|
|
141
|
+
this.abortController = null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Stop the current generation/tool execution
|
|
146
|
+
stop(): void {
|
|
147
|
+
if (this.abortController) {
|
|
148
|
+
this.abortController.abort();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Check if agent is currently processing
|
|
153
|
+
isProcessing(): boolean {
|
|
154
|
+
return this.abortController !== null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private async runAgentLoop(stream: boolean): Promise<string> {
|
|
158
|
+
const maxToolCalls = this.config.maxToolCalls || 25;
|
|
159
|
+
let toolCallCount = 0;
|
|
160
|
+
let finalResponse = "";
|
|
161
|
+
|
|
162
|
+
while (toolCallCount < maxToolCalls) {
|
|
163
|
+
// Check if aborted
|
|
164
|
+
if (this.abortController?.signal.aborted) {
|
|
165
|
+
finalResponse += "\n\n[Stopped by user]";
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Check budget
|
|
170
|
+
const budgetCheck = this.budget.checkBudget();
|
|
171
|
+
if (!budgetCheck.withinBudget) {
|
|
172
|
+
finalResponse += `\n\n[Budget exhausted: ${budgetCheck.warnings.join(", ")}]`;
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (budgetCheck.warnings.length > 0) {
|
|
177
|
+
this.events.onBudgetWarning?.(budgetCheck.warnings);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Get LLM response
|
|
181
|
+
const systemPrompt = this.config.systemPrompt || DEFAULT_SYSTEM_PROMPT;
|
|
182
|
+
|
|
183
|
+
let response: LLMResponse;
|
|
184
|
+
try {
|
|
185
|
+
response = await this.llm.chat(
|
|
186
|
+
this.context.messages,
|
|
187
|
+
this.tools.getAll(),
|
|
188
|
+
systemPrompt,
|
|
189
|
+
stream ? this.events.onStream : undefined,
|
|
190
|
+
this.abortController?.signal
|
|
191
|
+
);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
194
|
+
// Don't report abort as an error
|
|
195
|
+
if (err.message === "Aborted") {
|
|
196
|
+
return finalResponse + "\n\n[Stopped by user]";
|
|
197
|
+
}
|
|
198
|
+
this.events.onError?.(err);
|
|
199
|
+
return `Error communicating with LLM: ${err.message}`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Record token usage
|
|
203
|
+
this.budget.recordUsage(response.usage);
|
|
204
|
+
this.budget.recordIteration();
|
|
205
|
+
|
|
206
|
+
// Add assistant message to context
|
|
207
|
+
const assistantMsg: Message = {
|
|
208
|
+
role: "assistant",
|
|
209
|
+
content: response.content,
|
|
210
|
+
toolCalls: response.toolCalls,
|
|
211
|
+
timestamp: new Date(),
|
|
212
|
+
};
|
|
213
|
+
this.context.messages.push(assistantMsg);
|
|
214
|
+
this.events.onMessage?.(assistantMsg);
|
|
215
|
+
|
|
216
|
+
// If no tool calls, we're done
|
|
217
|
+
if (!response.toolCalls || response.toolCalls.length === 0) {
|
|
218
|
+
finalResponse = response.content;
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Execute tool calls
|
|
223
|
+
for (const toolCall of response.toolCalls) {
|
|
224
|
+
// Check if aborted before each tool call
|
|
225
|
+
if (this.abortController?.signal.aborted) {
|
|
226
|
+
return finalResponse + "\n\n[Stopped by user]";
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
this.events.onToolCall?.(toolCall);
|
|
230
|
+
toolCallCount++;
|
|
231
|
+
|
|
232
|
+
const result = await this.executeToolCall(toolCall);
|
|
233
|
+
this.events.onToolResult?.(result);
|
|
234
|
+
|
|
235
|
+
// Add tool result to context
|
|
236
|
+
const toolMsg: Message = {
|
|
237
|
+
role: "tool",
|
|
238
|
+
content: result.output || result.error || "(no output)",
|
|
239
|
+
toolCallId: toolCall.id,
|
|
240
|
+
timestamp: new Date(),
|
|
241
|
+
};
|
|
242
|
+
this.context.messages.push(toolMsg);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// If we hit max tool calls, break
|
|
246
|
+
if (toolCallCount >= maxToolCalls) {
|
|
247
|
+
finalResponse += `\n\n[Reached maximum tool call limit: ${maxToolCalls}]`;
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return finalResponse;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private async executeToolCall(toolCall: ToolCall): Promise<ToolResult> {
|
|
256
|
+
const result = await this.tools.execute(toolCall.name, toolCall.arguments);
|
|
257
|
+
result.toolCallId = toolCall.id;
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Get current conversation
|
|
262
|
+
getMessages(): Message[] {
|
|
263
|
+
return [...this.context.messages];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Clear conversation history
|
|
267
|
+
clearHistory(): void {
|
|
268
|
+
this.context.messages = [];
|
|
269
|
+
this.budget.reset();
|
|
270
|
+
this.context.startTime = new Date();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Get budget status
|
|
274
|
+
getBudgetStatus(): {
|
|
275
|
+
usage: ReturnType<BudgetManager["getUsage"]>;
|
|
276
|
+
check: ReturnType<BudgetManager["checkBudget"]>;
|
|
277
|
+
summary: string;
|
|
278
|
+
} {
|
|
279
|
+
return {
|
|
280
|
+
usage: this.budget.getUsage(),
|
|
281
|
+
check: this.budget.checkBudget(),
|
|
282
|
+
summary: this.budget.formatUsageSummary(),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Update budget
|
|
287
|
+
updateBudget(budget: Partial<Budget>): void {
|
|
288
|
+
this.budget.updateBudget(budget);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Update max tool calls
|
|
292
|
+
setMaxToolCalls(limit: number): void {
|
|
293
|
+
this.config.maxToolCalls = limit;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Get max tool calls
|
|
297
|
+
getMaxToolCalls(): number {
|
|
298
|
+
return this.config.maxToolCalls || 50;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Get current model info
|
|
302
|
+
getModelInfo(): { provider: string; model: string } {
|
|
303
|
+
return {
|
|
304
|
+
provider: this.config.provider,
|
|
305
|
+
model: this.llm.getModel(),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Set model
|
|
310
|
+
setModel(model: string): void {
|
|
311
|
+
this.config.model = model;
|
|
312
|
+
this.llm.setModel(model);
|
|
313
|
+
// Update cost estimation for new model
|
|
314
|
+
this.budget.setCostPerMillion(this.llm.getCostPerMillion());
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Set working directory
|
|
318
|
+
setWorkingDirectory(dir: string): void {
|
|
319
|
+
this.context.workingDirectory = dir;
|
|
320
|
+
this.fileResolver.setBasePath(dir);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Get working directory
|
|
324
|
+
getWorkingDirectory(): string {
|
|
325
|
+
return this.context.workingDirectory;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Add a system message
|
|
329
|
+
addSystemMessage(content: string): void {
|
|
330
|
+
this.context.messages.push({
|
|
331
|
+
role: "system",
|
|
332
|
+
content,
|
|
333
|
+
timestamp: new Date(),
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Get file suggestions for autocomplete
|
|
338
|
+
getFileSuggestions(partial: string): string[] {
|
|
339
|
+
return this.fileResolver.getSuggestions(partial);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Budget Manager - Track and enforce token/time/cost budgets
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Budget, BudgetUsage, TokenUsage } from "./types.js";
|
|
6
|
+
|
|
7
|
+
export interface BudgetCheckResult {
|
|
8
|
+
withinBudget: boolean;
|
|
9
|
+
warnings: string[];
|
|
10
|
+
tokensRemaining?: number;
|
|
11
|
+
costRemaining?: number;
|
|
12
|
+
timeRemaining?: number;
|
|
13
|
+
iterationsRemaining?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class BudgetManager {
|
|
17
|
+
private budget: Budget;
|
|
18
|
+
private usage: BudgetUsage;
|
|
19
|
+
private startTime: number;
|
|
20
|
+
private costPerMillion: { input: number; output: number };
|
|
21
|
+
|
|
22
|
+
constructor(budget: Budget = {}, costPerMillion = { input: 3, output: 15 }) {
|
|
23
|
+
this.budget = budget;
|
|
24
|
+
this.costPerMillion = costPerMillion;
|
|
25
|
+
this.usage = {
|
|
26
|
+
tokensUsed: 0,
|
|
27
|
+
costUsed: 0,
|
|
28
|
+
timeUsed: 0,
|
|
29
|
+
iterationsUsed: 0,
|
|
30
|
+
};
|
|
31
|
+
this.startTime = Date.now();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
recordUsage(tokenUsage: TokenUsage): void {
|
|
35
|
+
this.usage.tokensUsed += tokenUsage.totalTokens;
|
|
36
|
+
this.usage.costUsed +=
|
|
37
|
+
(tokenUsage.inputTokens * this.costPerMillion.input +
|
|
38
|
+
tokenUsage.outputTokens * this.costPerMillion.output) /
|
|
39
|
+
1_000_000;
|
|
40
|
+
this.usage.timeUsed = (Date.now() - this.startTime) / 1000;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
recordIteration(): void {
|
|
44
|
+
this.usage.iterationsUsed += 1;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
checkBudget(): BudgetCheckResult {
|
|
48
|
+
const warnings: string[] = [];
|
|
49
|
+
let withinBudget = true;
|
|
50
|
+
|
|
51
|
+
// Update time
|
|
52
|
+
this.usage.timeUsed = (Date.now() - this.startTime) / 1000;
|
|
53
|
+
|
|
54
|
+
// Check tokens
|
|
55
|
+
if (this.budget.maxTokens !== undefined) {
|
|
56
|
+
const tokensRemaining = this.budget.maxTokens - this.usage.tokensUsed;
|
|
57
|
+
if (tokensRemaining <= 0) {
|
|
58
|
+
withinBudget = false;
|
|
59
|
+
warnings.push(`Token budget exhausted (${this.usage.tokensUsed}/${this.budget.maxTokens})`);
|
|
60
|
+
} else if (tokensRemaining < this.budget.maxTokens * 0.1) {
|
|
61
|
+
warnings.push(`Low token budget: ${tokensRemaining} remaining`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check cost
|
|
66
|
+
if (this.budget.maxCost !== undefined) {
|
|
67
|
+
const costRemaining = this.budget.maxCost - this.usage.costUsed;
|
|
68
|
+
if (costRemaining <= 0) {
|
|
69
|
+
withinBudget = false;
|
|
70
|
+
warnings.push(`Cost budget exhausted ($${this.usage.costUsed.toFixed(4)}/$${this.budget.maxCost})`);
|
|
71
|
+
} else if (costRemaining < this.budget.maxCost * 0.1) {
|
|
72
|
+
warnings.push(`Low cost budget: $${costRemaining.toFixed(4)} remaining`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check time
|
|
77
|
+
if (this.budget.maxTime !== undefined) {
|
|
78
|
+
const timeRemaining = this.budget.maxTime - this.usage.timeUsed;
|
|
79
|
+
if (timeRemaining <= 0) {
|
|
80
|
+
withinBudget = false;
|
|
81
|
+
warnings.push(`Time budget exhausted (${this.usage.timeUsed.toFixed(0)}s/${this.budget.maxTime}s)`);
|
|
82
|
+
} else if (timeRemaining < this.budget.maxTime * 0.1) {
|
|
83
|
+
warnings.push(`Low time budget: ${timeRemaining.toFixed(0)}s remaining`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check iterations
|
|
88
|
+
if (this.budget.maxIterations !== undefined) {
|
|
89
|
+
const iterationsRemaining = this.budget.maxIterations - this.usage.iterationsUsed;
|
|
90
|
+
if (iterationsRemaining <= 0) {
|
|
91
|
+
withinBudget = false;
|
|
92
|
+
warnings.push(`Iteration limit reached (${this.usage.iterationsUsed}/${this.budget.maxIterations})`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
withinBudget,
|
|
98
|
+
warnings,
|
|
99
|
+
tokensRemaining: this.budget.maxTokens
|
|
100
|
+
? Math.max(0, this.budget.maxTokens - this.usage.tokensUsed)
|
|
101
|
+
: undefined,
|
|
102
|
+
costRemaining: this.budget.maxCost
|
|
103
|
+
? Math.max(0, this.budget.maxCost - this.usage.costUsed)
|
|
104
|
+
: undefined,
|
|
105
|
+
timeRemaining: this.budget.maxTime
|
|
106
|
+
? Math.max(0, this.budget.maxTime - this.usage.timeUsed)
|
|
107
|
+
: undefined,
|
|
108
|
+
iterationsRemaining: this.budget.maxIterations
|
|
109
|
+
? Math.max(0, this.budget.maxIterations - this.usage.iterationsUsed)
|
|
110
|
+
: undefined,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
getUsage(): BudgetUsage {
|
|
115
|
+
this.usage.timeUsed = (Date.now() - this.startTime) / 1000;
|
|
116
|
+
return { ...this.usage };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
getBudget(): Budget {
|
|
120
|
+
return { ...this.budget };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
formatUsageSummary(): string {
|
|
124
|
+
const usage = this.getUsage();
|
|
125
|
+
const parts: string[] = [];
|
|
126
|
+
|
|
127
|
+
parts.push(`Tokens: ${usage.tokensUsed.toLocaleString()}`);
|
|
128
|
+
if (this.budget.maxTokens) {
|
|
129
|
+
parts[parts.length - 1] += `/${this.budget.maxTokens.toLocaleString()}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
parts.push(`Cost: $${usage.costUsed.toFixed(4)}`);
|
|
133
|
+
if (this.budget.maxCost) {
|
|
134
|
+
parts[parts.length - 1] += `/$${this.budget.maxCost.toFixed(2)}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
parts.push(`Time: ${usage.timeUsed.toFixed(1)}s`);
|
|
138
|
+
if (this.budget.maxTime) {
|
|
139
|
+
parts[parts.length - 1] += `/${this.budget.maxTime}s`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (this.budget.maxIterations) {
|
|
143
|
+
parts.push(`Iterations: ${usage.iterationsUsed}/${this.budget.maxIterations}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return parts.join(" | ");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
reset(): void {
|
|
150
|
+
this.usage = {
|
|
151
|
+
tokensUsed: 0,
|
|
152
|
+
costUsed: 0,
|
|
153
|
+
timeUsed: 0,
|
|
154
|
+
iterationsUsed: 0,
|
|
155
|
+
};
|
|
156
|
+
this.startTime = Date.now();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
updateBudget(budget: Partial<Budget>): void {
|
|
160
|
+
this.budget = { ...this.budget, ...budget };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
setCostPerMillion(costs: { input: number; output: number }): void {
|
|
164
|
+
this.costPerMillion = costs;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|