@console-agent/agent 1.1.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/dist/index.js ADDED
@@ -0,0 +1,850 @@
1
+ import { createGoogleGenerativeAI } from '@ai-sdk/google';
2
+ import { jsonSchema, Output, ToolLoopAgent } from 'ai';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+
6
+ // src/personas/debugger.ts
7
+ var debuggerPersona = {
8
+ name: "debugger",
9
+ icon: "\u{1F41B}",
10
+ label: "Debugging",
11
+ systemPrompt: `You are a senior debugging expert and performance engineer.
12
+
13
+ Your role:
14
+ - Analyze errors, stack traces, exceptions, and performance issues
15
+ - Identify root causes with high confidence
16
+ - Provide concrete fixes with code examples
17
+ - Suggest preventive measures
18
+
19
+ Output format:
20
+ - Start with a one-line summary of the issue
21
+ - Explain the root cause clearly
22
+ - Provide a concrete fix (with code if applicable)
23
+ - Rate severity: LOW / MEDIUM / HIGH / CRITICAL
24
+ - Include confidence score (0-1)
25
+
26
+ Always be concise, technical, and actionable. No fluff.`,
27
+ defaultTools: ["code_execution", "google_search"],
28
+ keywords: [
29
+ "slow",
30
+ "perf",
31
+ "performance",
32
+ "optimize",
33
+ "optimization",
34
+ "debug",
35
+ "error",
36
+ "bug",
37
+ "crash",
38
+ "exception",
39
+ "stack",
40
+ "trace",
41
+ "memory",
42
+ "leak",
43
+ "timeout",
44
+ "latency",
45
+ "bottleneck",
46
+ "hang",
47
+ "freeze",
48
+ "deadlock",
49
+ "race condition"
50
+ ]
51
+ };
52
+
53
+ // src/personas/security.ts
54
+ var securityPersona = {
55
+ name: "security",
56
+ icon: "\u{1F6E1}\uFE0F",
57
+ label: "Security audit",
58
+ systemPrompt: `You are an OWASP security expert and penetration testing specialist.
59
+
60
+ Your role:
61
+ - Audit code and inputs for vulnerabilities (SQL injection, XSS, CSRF, SSRF, etc.)
62
+ - Flag security risks immediately with severity ratings
63
+ - Check for known CVEs in dependencies
64
+ - Recommend secure coding practices
65
+
66
+ Output format:
67
+ - Start with overall risk level: SAFE / LOW RISK / MEDIUM RISK / HIGH RISK / CRITICAL
68
+ - List each vulnerability found with:
69
+ - Type (e.g., SQL Injection, XSS)
70
+ - Location (where in the code/input)
71
+ - Impact (what an attacker could do)
72
+ - Fix (concrete remediation)
73
+ - Include confidence score (0-1)
74
+
75
+ Be thorough, explicit about risks, and always err on the side of caution.`,
76
+ defaultTools: ["google_search"],
77
+ keywords: [
78
+ "security",
79
+ "vuln",
80
+ "vulnerability",
81
+ "exploit",
82
+ "injection",
83
+ "xss",
84
+ "csrf",
85
+ "ssrf",
86
+ "sql injection",
87
+ "auth",
88
+ "authentication",
89
+ "authorization",
90
+ "permission",
91
+ "privilege",
92
+ "escalation",
93
+ "sanitize",
94
+ "escape",
95
+ "encrypt",
96
+ "decrypt",
97
+ "hash",
98
+ "token",
99
+ "secret",
100
+ "api key",
101
+ "password",
102
+ "credential",
103
+ "owasp",
104
+ "cve"
105
+ ]
106
+ };
107
+
108
+ // src/personas/architect.ts
109
+ var architectPersona = {
110
+ name: "architect",
111
+ icon: "\u{1F3D7}\uFE0F",
112
+ label: "Architecture review",
113
+ systemPrompt: `You are a principal software engineer and system architect.
114
+
115
+ Your role:
116
+ - Review system design, API design, and code architecture
117
+ - Evaluate scalability, maintainability, and performance characteristics
118
+ - Identify design pattern opportunities and anti-patterns
119
+ - Suggest architectural improvements with trade-off analysis
120
+
121
+ Output format:
122
+ - Start with an overall assessment: SOLID / NEEDS IMPROVEMENT / SIGNIFICANT CONCERNS
123
+ - List strengths of the current design
124
+ - List concerns with severity and impact
125
+ - Provide concrete recommendations with:
126
+ - What to change
127
+ - Why (trade-offs)
128
+ - How (implementation guidance)
129
+ - Include confidence score (0-1)
130
+
131
+ Think like a senior architect reviewing a design doc. Be constructive, not pedantic.`,
132
+ defaultTools: ["google_search", "file_analysis"],
133
+ keywords: [
134
+ "design",
135
+ "architecture",
136
+ "architect",
137
+ "pattern",
138
+ "scalab",
139
+ "microservice",
140
+ "monolith",
141
+ "api design",
142
+ "schema",
143
+ "database",
144
+ "system design",
145
+ "infrastructure",
146
+ "deploy",
147
+ "ci/cd",
148
+ "pipeline",
149
+ "refactor",
150
+ "modular",
151
+ "coupling",
152
+ "cohesion",
153
+ "solid",
154
+ "clean architecture",
155
+ "domain driven",
156
+ "event driven"
157
+ ]
158
+ };
159
+
160
+ // src/personas/general.ts
161
+ var generalPersona = {
162
+ name: "general",
163
+ icon: "\u{1F50D}",
164
+ label: "Analyzing",
165
+ systemPrompt: `You are a helpful senior full-stack engineer with broad expertise.
166
+
167
+ Your role:
168
+ - Provide actionable advice on any technical topic
169
+ - Analyze code, data, configurations, and systems
170
+ - Validate inputs, schemas, and data integrity
171
+ - Answer questions with practical, real-world guidance
172
+
173
+ Output format:
174
+ - Start with a clear, one-line answer or summary
175
+ - Provide supporting details and reasoning
176
+ - Include code examples when relevant
177
+ - List any caveats or edge cases
178
+ - Include confidence score (0-1)
179
+
180
+ Be balanced, practical, and concise. Prioritize actionable insights over theory.`,
181
+ defaultTools: ["code_execution", "google_search", "file_analysis"],
182
+ keywords: []
183
+ // General catches everything not matched by specific personas
184
+ };
185
+
186
+ // src/personas/index.ts
187
+ var personas = {
188
+ debugger: debuggerPersona,
189
+ security: securityPersona,
190
+ architect: architectPersona,
191
+ general: generalPersona
192
+ };
193
+ function detectPersona(prompt, defaultPersona) {
194
+ const lower = prompt.toLowerCase();
195
+ for (const name of ["security", "debugger", "architect"]) {
196
+ const persona = personas[name];
197
+ if (persona.keywords.some((kw) => lower.includes(kw))) {
198
+ return persona;
199
+ }
200
+ }
201
+ return personas[defaultPersona];
202
+ }
203
+ function getPersona(name) {
204
+ return personas[name];
205
+ }
206
+ var currentLogLevel = "info";
207
+ function setLogLevel(level) {
208
+ currentLogLevel = level;
209
+ }
210
+ function shouldLog(level) {
211
+ const levels = ["silent", "errors", "info", "debug"];
212
+ return levels.indexOf(currentLogLevel) >= levels.indexOf(level);
213
+ }
214
+ function startSpinner(persona, prompt) {
215
+ if (!shouldLog("info")) return null;
216
+ const truncated = prompt.length > 60 ? prompt.substring(0, 57) + "..." : prompt;
217
+ const spinner = ora({
218
+ text: chalk.cyan(`${persona.icon} ${persona.label}... `) + chalk.dim(truncated),
219
+ prefixText: chalk.gray("[AGENT]")
220
+ }).start();
221
+ return spinner;
222
+ }
223
+ function stopSpinner(spinner, success) {
224
+ if (!spinner) return;
225
+ if (success) {
226
+ spinner.succeed();
227
+ } else {
228
+ spinner.fail();
229
+ }
230
+ }
231
+ function formatResult(result, persona) {
232
+ if (!shouldLog("info")) return;
233
+ const prefix = chalk.gray("[AGENT]");
234
+ const confidenceColor = result.confidence >= 0.8 ? chalk.green : result.confidence >= 0.5 ? chalk.yellow : chalk.red;
235
+ const statusIcon = result.success ? chalk.green("\u2713") : chalk.red("\u2717");
236
+ console.log("");
237
+ console.log(`${prefix} ${persona.icon} ${chalk.bold(persona.label)} Complete`);
238
+ console.log(`${prefix} \u251C\u2500 ${statusIcon} ${chalk.white(result.summary)}`);
239
+ if (result.actions.length > 0) {
240
+ for (let i = 0; i < result.actions.length; i++) {
241
+ const connector = i < result.actions.length - 1 ? "\u251C\u2500" : "\u251C\u2500";
242
+ console.log(`${prefix} ${connector} ${chalk.dim("Tool:")} ${chalk.cyan(result.actions[i])}`);
243
+ }
244
+ }
245
+ const dataEntries = Object.entries(result.data);
246
+ if (dataEntries.length > 0) {
247
+ for (const [key, value] of dataEntries) {
248
+ const displayValue = typeof value === "string" ? value : JSON.stringify(value);
249
+ console.log(`${prefix} \u251C\u2500 ${chalk.dim(key + ":")} ${chalk.white(displayValue)}`);
250
+ }
251
+ }
252
+ if (result.reasoning) {
253
+ const reasoningLines = result.reasoning.split("\n").slice(0, 3);
254
+ console.log(`${prefix} \u251C\u2500 ${chalk.dim("Reasoning:")}`);
255
+ for (const line of reasoningLines) {
256
+ console.log(`${prefix} \u2502 ${chalk.dim(line.trim())}`);
257
+ }
258
+ }
259
+ const confidence = confidenceColor(`confidence: ${result.confidence.toFixed(2)}`);
260
+ const latency = chalk.dim(`${result.metadata.latencyMs}ms`);
261
+ const tokens = chalk.dim(`${result.metadata.tokensUsed} tokens`);
262
+ const cached = result.metadata.cached ? chalk.green(" (cached)") : "";
263
+ console.log(`${prefix} \u2514\u2500 ${confidence} | ${latency} | ${tokens}${cached}`);
264
+ console.log("");
265
+ }
266
+ function formatError(error, persona) {
267
+ if (!shouldLog("errors")) return;
268
+ const prefix = chalk.gray("[AGENT]");
269
+ console.log("");
270
+ console.log(`${prefix} ${persona.icon} ${chalk.red("Error:")} ${error.message}`);
271
+ if (shouldLog("debug") && error.stack) {
272
+ console.log(`${prefix} ${chalk.dim(error.stack)}`);
273
+ }
274
+ console.log("");
275
+ }
276
+ function formatBudgetWarning(reason) {
277
+ if (!shouldLog("errors")) return;
278
+ const prefix = chalk.gray("[AGENT]");
279
+ console.log(`${prefix} ${chalk.yellow("\u26A0 Budget limit:")} ${reason}`);
280
+ }
281
+ function formatRateLimitWarning() {
282
+ if (!shouldLog("errors")) return;
283
+ const prefix = chalk.gray("[AGENT]");
284
+ console.log(`${prefix} ${chalk.yellow("\u26A0 Rate limited:")} Too many calls. Try again later.`);
285
+ }
286
+ function formatDryRun(prompt, persona, context) {
287
+ if (!shouldLog("info")) return;
288
+ const prefix = chalk.gray("[AGENT]");
289
+ console.log("");
290
+ console.log(`${prefix} ${chalk.magenta("DRY RUN")} ${persona.icon} ${persona.label}`);
291
+ console.log(`${prefix} \u251C\u2500 ${chalk.dim("Persona:")} ${persona.name}`);
292
+ console.log(`${prefix} \u251C\u2500 ${chalk.dim("Prompt:")} ${prompt}`);
293
+ if (context !== void 0) {
294
+ const contextStr = typeof context === "string" ? context : JSON.stringify(context, null, 2);
295
+ const lines = contextStr.split("\n").slice(0, 5);
296
+ console.log(`${prefix} \u251C\u2500 ${chalk.dim("Context:")}`);
297
+ for (const line of lines) {
298
+ console.log(`${prefix} \u2502 ${chalk.dim(line)}`);
299
+ }
300
+ }
301
+ console.log(`${prefix} \u2514\u2500 ${chalk.dim("(No API call made)")}`);
302
+ console.log("");
303
+ }
304
+ function logDebug(message) {
305
+ if (!shouldLog("debug")) return;
306
+ console.log(`${chalk.gray("[AGENT DEBUG]")} ${chalk.dim(message)}`);
307
+ }
308
+
309
+ // src/providers/google.ts
310
+ var agentOutputSchema = jsonSchema({
311
+ type: "object",
312
+ properties: {
313
+ success: { type: "boolean", description: "Whether the task was completed successfully" },
314
+ summary: { type: "string", description: "One-line human-readable conclusion" },
315
+ reasoning: { type: "string", description: "Your thought process" },
316
+ data: {
317
+ type: "object",
318
+ description: "Structured findings as key-value pairs",
319
+ properties: {
320
+ result: { type: "string", description: "Primary result or finding" }
321
+ },
322
+ additionalProperties: true
323
+ },
324
+ actions: { type: "array", items: { type: "string" }, description: "List of tools/steps you used" },
325
+ confidence: { type: "number", minimum: 0, maximum: 1, description: "0-1 confidence score" }
326
+ },
327
+ required: ["success", "summary", "data", "actions", "confidence"],
328
+ additionalProperties: false
329
+ });
330
+ async function callGoogle(prompt, context, persona, config2, options) {
331
+ const startTime = Date.now();
332
+ const modelName = options?.model ?? config2.model;
333
+ logDebug(`Using model: ${modelName}`);
334
+ logDebug(`Persona: ${persona.name}`);
335
+ const google = createGoogleGenerativeAI({
336
+ apiKey: config2.apiKey ?? process.env.GEMINI_API_KEY ?? process.env.GOOGLE_GENERATIVE_AI_API_KEY
337
+ });
338
+ const providerOptions = {};
339
+ const googleOpts = {};
340
+ if (options?.thinking) {
341
+ const thinking = options.thinking;
342
+ if (thinking.budget !== void 0) {
343
+ googleOpts["thinkingConfig"] = { thinkingBudget: thinking.budget };
344
+ } else if (thinking.level) {
345
+ googleOpts["thinkingConfig"] = { thinkingLevel: thinking.level };
346
+ }
347
+ }
348
+ if (Object.keys(googleOpts).length > 0) {
349
+ providerOptions["google"] = googleOpts;
350
+ }
351
+ if (!config2.localOnly) {
352
+ const toolNames = options?.tools ?? persona.defaultTools;
353
+ logDebug(`Persona tools (informational): ${toolNames.join(", ")}`);
354
+ }
355
+ const userMessage = context ? `${prompt}
356
+
357
+ --- Context ---
358
+ ${context}` : prompt;
359
+ const collectedToolCalls = [];
360
+ const useCustomSchema = !!(options?.schema || options?.responseFormat);
361
+ let outputConfig;
362
+ if (options?.schema) {
363
+ if (options.responseFormat) {
364
+ logDebug("Both schema (Zod) and responseFormat provided \u2014 using schema (Zod)");
365
+ }
366
+ logDebug("Using custom Zod schema for structured output");
367
+ outputConfig = Output.object({ schema: options.schema });
368
+ } else if (options?.responseFormat) {
369
+ logDebug("Using custom JSON Schema (responseFormat) for structured output");
370
+ outputConfig = Output.object({ schema: jsonSchema(options.responseFormat.schema) });
371
+ } else {
372
+ outputConfig = Output.object({ schema: agentOutputSchema });
373
+ }
374
+ const agent = new ToolLoopAgent({
375
+ model: google(modelName),
376
+ instructions: useCustomSchema ? `${persona.systemPrompt}
377
+
378
+ IMPORTANT: You must respond with structured data matching the requested output schema. Do not include AgentResult wrapper fields \u2014 just return the data matching the schema.` : persona.systemPrompt,
379
+ maxOutputTokens: config2.budget.maxTokensPerCall,
380
+ output: outputConfig,
381
+ providerOptions: Object.keys(providerOptions).length > 0 ? providerOptions : void 0,
382
+ onStepFinish: (step) => {
383
+ if (step.toolCalls) {
384
+ for (const tc of step.toolCalls) {
385
+ collectedToolCalls.push({
386
+ name: tc.toolName,
387
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
388
+ args: tc.args ?? {},
389
+ result: tc.toolName
390
+ });
391
+ }
392
+ }
393
+ logDebug(`Step finished: ${step.finishReason}`);
394
+ }
395
+ });
396
+ const result = await agent.generate({
397
+ prompt: userMessage,
398
+ timeout: config2.timeout
399
+ });
400
+ const latencyMs = Date.now() - startTime;
401
+ const tokensUsed = result.usage?.totalTokens ?? 0;
402
+ logDebug(`Response received: ${latencyMs}ms, ${tokensUsed} tokens`);
403
+ if (useCustomSchema && result.output) {
404
+ const customData = result.output;
405
+ logDebug("Custom schema output received, wrapping in AgentResult");
406
+ return {
407
+ success: true,
408
+ summary: `Structured output returned (${Object.keys(customData).length} fields)`,
409
+ data: customData,
410
+ actions: collectedToolCalls.map((tc) => tc.name),
411
+ confidence: 1,
412
+ metadata: {
413
+ model: modelName,
414
+ tokensUsed,
415
+ latencyMs,
416
+ toolCalls: collectedToolCalls,
417
+ cached: false
418
+ }
419
+ };
420
+ }
421
+ if (result.output) {
422
+ const output = result.output;
423
+ return {
424
+ success: output.success,
425
+ summary: output.summary,
426
+ reasoning: output.reasoning,
427
+ data: output.data,
428
+ actions: output.actions,
429
+ confidence: output.confidence,
430
+ metadata: {
431
+ model: modelName,
432
+ tokensUsed,
433
+ latencyMs,
434
+ toolCalls: collectedToolCalls,
435
+ cached: false
436
+ }
437
+ };
438
+ }
439
+ const parsed = parseResponse(result.text);
440
+ return {
441
+ success: parsed?.success ?? true,
442
+ summary: parsed?.summary ?? result.text.substring(0, 200),
443
+ reasoning: parsed?.reasoning,
444
+ data: parsed?.data ?? { raw: result.text },
445
+ actions: parsed?.actions ?? collectedToolCalls.map((tc) => tc.name),
446
+ confidence: parsed?.confidence ?? 0.5,
447
+ metadata: {
448
+ model: modelName,
449
+ tokensUsed,
450
+ latencyMs,
451
+ toolCalls: collectedToolCalls,
452
+ cached: false
453
+ }
454
+ };
455
+ }
456
+ function parseResponse(text) {
457
+ try {
458
+ return JSON.parse(text);
459
+ } catch {
460
+ const jsonMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
461
+ if (jsonMatch) {
462
+ try {
463
+ return JSON.parse(jsonMatch[1]);
464
+ } catch {
465
+ }
466
+ }
467
+ const objectMatch = text.match(/\{[\s\S]*\}/);
468
+ if (objectMatch) {
469
+ try {
470
+ return JSON.parse(objectMatch[0]);
471
+ } catch {
472
+ }
473
+ }
474
+ return {
475
+ success: true,
476
+ summary: text.substring(0, 200),
477
+ data: { raw: text },
478
+ actions: [],
479
+ confidence: 0.5
480
+ };
481
+ }
482
+ }
483
+
484
+ // src/utils/anonymize.ts
485
+ var patterns = {
486
+ // API keys and tokens (long alphanumeric strings near sensitive keywords)
487
+ apiKey: /(?:api[_-]?key|token|secret|password|credential|auth)['":\s=]+['"]?([A-Za-z0-9_\-/.]{20,})['"]?/gi,
488
+ // Bearer tokens
489
+ bearer: /Bearer\s+[A-Za-z0-9_\-/.+]{20,}/gi,
490
+ // Email addresses
491
+ email: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
492
+ // IPv4 addresses
493
+ ipv4: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g,
494
+ // IPv6 addresses (simplified)
495
+ ipv6: /(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}/g,
496
+ // AWS keys
497
+ awsKey: /(?:AKIA|ASIA)[A-Z0-9]{16}/g,
498
+ // Private keys
499
+ privateKey: /-----BEGIN (?:RSA )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA )?PRIVATE KEY-----/g,
500
+ // Connection strings
501
+ connectionString: /(?:mongodb|postgres|mysql|redis|amqp):\/\/[^\s'"]+/gi,
502
+ // .env style secrets
503
+ envSecret: /^(?:DATABASE_URL|DB_PASSWORD|SECRET_KEY|PRIVATE_KEY|AWS_SECRET|STRIPE_KEY|SENDGRID_KEY)[=:].+$/gm
504
+ };
505
+ function anonymize(content) {
506
+ let result = content;
507
+ result = result.replace(patterns.privateKey, "[REDACTED_PRIVATE_KEY]");
508
+ result = result.replace(patterns.connectionString, "[REDACTED_CONNECTION_STRING]");
509
+ result = result.replace(patterns.awsKey, "[REDACTED_AWS_KEY]");
510
+ result = result.replace(patterns.bearer, "Bearer [REDACTED_TOKEN]");
511
+ result = result.replace(patterns.apiKey, (match, _key) => {
512
+ const colonIdx = match.search(/['":\s=]/);
513
+ const prefix = match.substring(0, colonIdx);
514
+ return `${prefix}: [REDACTED]`;
515
+ });
516
+ result = result.replace(patterns.envSecret, (match) => {
517
+ const eqIdx = match.search(/[=:]/);
518
+ const key = match.substring(0, eqIdx);
519
+ return `${key}=[REDACTED]`;
520
+ });
521
+ result = result.replace(patterns.email, "[EMAIL]");
522
+ result = result.replace(patterns.ipv4, "[IP]");
523
+ result = result.replace(patterns.ipv6, "[IP]");
524
+ return result;
525
+ }
526
+ function anonymizeValue(value) {
527
+ if (typeof value === "string") {
528
+ return anonymize(value);
529
+ }
530
+ if (Array.isArray(value)) {
531
+ return value.map(anonymizeValue);
532
+ }
533
+ if (value !== null && typeof value === "object") {
534
+ const result = {};
535
+ for (const [k, v] of Object.entries(value)) {
536
+ result[k] = anonymizeValue(v);
537
+ }
538
+ return result;
539
+ }
540
+ return value;
541
+ }
542
+
543
+ // src/utils/rate-limit.ts
544
+ var RateLimiter = class {
545
+ tokens;
546
+ maxTokens;
547
+ refillRate;
548
+ // tokens per millisecond
549
+ lastRefill;
550
+ /**
551
+ * @param maxCallsPerDay Maximum calls allowed per day
552
+ */
553
+ constructor(maxCallsPerDay) {
554
+ this.maxTokens = maxCallsPerDay;
555
+ this.tokens = maxCallsPerDay;
556
+ this.refillRate = maxCallsPerDay / (24 * 60 * 60 * 1e3);
557
+ this.lastRefill = Date.now();
558
+ }
559
+ /**
560
+ * Attempt to consume one token.
561
+ * @returns true if allowed, false if rate limited
562
+ */
563
+ tryConsume() {
564
+ this.refill();
565
+ if (this.tokens >= 1) {
566
+ this.tokens -= 1;
567
+ return true;
568
+ }
569
+ return false;
570
+ }
571
+ /**
572
+ * Get remaining tokens (calls available)
573
+ */
574
+ remaining() {
575
+ this.refill();
576
+ return Math.floor(this.tokens);
577
+ }
578
+ /**
579
+ * Reset the limiter (e.g., for testing or manual override)
580
+ */
581
+ reset() {
582
+ this.tokens = this.maxTokens;
583
+ this.lastRefill = Date.now();
584
+ }
585
+ refill() {
586
+ const now = Date.now();
587
+ const elapsed = now - this.lastRefill;
588
+ const newTokens = elapsed * this.refillRate;
589
+ this.tokens = Math.min(this.maxTokens, this.tokens + newTokens);
590
+ this.lastRefill = now;
591
+ }
592
+ };
593
+
594
+ // src/utils/budget.ts
595
+ var BudgetTracker = class {
596
+ callsToday = 0;
597
+ tokensToday = 0;
598
+ costToday = 0;
599
+ dayStart;
600
+ config;
601
+ constructor(config2) {
602
+ this.config = config2;
603
+ this.dayStart = this.getStartOfDay();
604
+ }
605
+ /**
606
+ * Check if a call is within budget. Resets counters at midnight UTC.
607
+ */
608
+ canMakeCall() {
609
+ this.maybeResetDay();
610
+ if (this.callsToday >= this.config.maxCallsPerDay) {
611
+ return {
612
+ allowed: false,
613
+ reason: `Daily call limit reached (${this.config.maxCallsPerDay} calls/day)`
614
+ };
615
+ }
616
+ if (this.costToday >= this.config.costCapDaily) {
617
+ return {
618
+ allowed: false,
619
+ reason: `Daily cost cap reached ($${this.config.costCapDaily.toFixed(2)})`
620
+ };
621
+ }
622
+ return { allowed: true };
623
+ }
624
+ /**
625
+ * Record a completed call's usage.
626
+ */
627
+ recordUsage(tokensUsed, costUsd) {
628
+ this.maybeResetDay();
629
+ this.callsToday += 1;
630
+ this.tokensToday += tokensUsed;
631
+ this.costToday += costUsd;
632
+ }
633
+ /**
634
+ * Get current usage stats.
635
+ */
636
+ getStats() {
637
+ this.maybeResetDay();
638
+ return {
639
+ callsToday: this.callsToday,
640
+ callsRemaining: Math.max(0, this.config.maxCallsPerDay - this.callsToday),
641
+ tokensToday: this.tokensToday,
642
+ costToday: this.costToday,
643
+ costRemaining: Math.max(0, this.config.costCapDaily - this.costToday)
644
+ };
645
+ }
646
+ /**
647
+ * Reset all counters (for testing).
648
+ */
649
+ reset() {
650
+ this.callsToday = 0;
651
+ this.tokensToday = 0;
652
+ this.costToday = 0;
653
+ this.dayStart = this.getStartOfDay();
654
+ }
655
+ /**
656
+ * Get the max tokens allowed per call.
657
+ */
658
+ get maxTokensPerCall() {
659
+ return this.config.maxTokensPerCall;
660
+ }
661
+ maybeResetDay() {
662
+ const currentDayStart = this.getStartOfDay();
663
+ if (currentDayStart > this.dayStart) {
664
+ this.callsToday = 0;
665
+ this.tokensToday = 0;
666
+ this.costToday = 0;
667
+ this.dayStart = currentDayStart;
668
+ }
669
+ }
670
+ getStartOfDay() {
671
+ const now = /* @__PURE__ */ new Date();
672
+ return Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
673
+ }
674
+ };
675
+
676
+ // src/agent.ts
677
+ var DEFAULT_CONFIG = {
678
+ provider: "google",
679
+ model: "gemini-2.5-flash-lite",
680
+ persona: "general",
681
+ budget: {
682
+ maxCallsPerDay: 100,
683
+ maxTokensPerCall: 8e3,
684
+ costCapDaily: 1
685
+ },
686
+ mode: "fire-and-forget",
687
+ timeout: 1e4,
688
+ anonymize: true,
689
+ localOnly: false,
690
+ dryRun: false,
691
+ logLevel: "info",
692
+ safetySettings: []
693
+ };
694
+ var config = { ...DEFAULT_CONFIG };
695
+ var rateLimiter = new RateLimiter(config.budget.maxCallsPerDay);
696
+ var budgetTracker = new BudgetTracker(config.budget);
697
+ function updateConfig(newConfig) {
698
+ config = { ...DEFAULT_CONFIG, ...newConfig };
699
+ if (newConfig.budget) {
700
+ config.budget = { ...DEFAULT_CONFIG.budget, ...newConfig.budget };
701
+ }
702
+ rateLimiter = new RateLimiter(config.budget.maxCallsPerDay);
703
+ budgetTracker = new BudgetTracker(config.budget);
704
+ }
705
+ function getConfig() {
706
+ return { ...config };
707
+ }
708
+ async function executeAgent(prompt, context, options) {
709
+ const personaName = options?.persona ?? config.persona;
710
+ const persona = options?.persona ? getPersona(options.persona) : detectPersona(prompt, personaName);
711
+ logDebug(`Selected persona: ${persona.name} (${persona.icon})`);
712
+ if (config.dryRun) {
713
+ formatDryRun(prompt, persona, context);
714
+ return createDryRunResult(persona.name);
715
+ }
716
+ if (!rateLimiter.tryConsume()) {
717
+ formatRateLimitWarning();
718
+ return createErrorResult("Rate limited \u2014 too many calls. Try again later.");
719
+ }
720
+ const budgetCheck = budgetTracker.canMakeCall();
721
+ if (!budgetCheck.allowed) {
722
+ formatBudgetWarning(budgetCheck.reason);
723
+ return createErrorResult(budgetCheck.reason);
724
+ }
725
+ let contextStr = "";
726
+ if (context !== void 0) {
727
+ const processed = config.anonymize ? anonymizeValue(context) : context;
728
+ contextStr = typeof processed === "string" ? processed : JSON.stringify(processed, null, 2);
729
+ if (context instanceof Error) {
730
+ const errObj = {
731
+ name: context.name,
732
+ message: context.message,
733
+ stack: context.stack,
734
+ ...typeof context === "object" ? Object.getOwnPropertyNames(context).reduce((acc, key) => {
735
+ acc[key] = context[key];
736
+ return acc;
737
+ }, {}) : {}
738
+ };
739
+ const processed2 = config.anonymize ? anonymizeValue(errObj) : errObj;
740
+ contextStr = typeof processed2 === "string" ? processed2 : JSON.stringify(processed2, null, 2);
741
+ }
742
+ }
743
+ const processedPrompt = config.anonymize ? anonymizeValue(prompt) : prompt;
744
+ const spinner = startSpinner(persona, processedPrompt);
745
+ try {
746
+ const result = await Promise.race([
747
+ callGoogle(processedPrompt, contextStr, persona, config, options),
748
+ createTimeout(config.timeout)
749
+ ]);
750
+ budgetTracker.recordUsage(
751
+ result.metadata.tokensUsed,
752
+ estimateCost(result.metadata.tokensUsed, result.metadata.model)
753
+ );
754
+ stopSpinner(spinner, result.success);
755
+ formatResult(result, persona);
756
+ return result;
757
+ } catch (error) {
758
+ stopSpinner(spinner, false);
759
+ const err = error instanceof Error ? error : new Error(String(error));
760
+ formatError(err, persona);
761
+ return createErrorResult(err.message);
762
+ }
763
+ }
764
+ function createTimeout(ms) {
765
+ return new Promise((_, reject) => {
766
+ setTimeout(() => reject(new Error(`Agent timed out after ${ms}ms`)), ms);
767
+ });
768
+ }
769
+ function createErrorResult(message) {
770
+ return {
771
+ success: false,
772
+ summary: message,
773
+ data: {},
774
+ actions: [],
775
+ confidence: 0,
776
+ metadata: {
777
+ model: config.model,
778
+ tokensUsed: 0,
779
+ latencyMs: 0,
780
+ toolCalls: [],
781
+ cached: false
782
+ }
783
+ };
784
+ }
785
+ function createDryRunResult(personaName) {
786
+ return {
787
+ success: true,
788
+ summary: `[DRY RUN] Would have executed with ${personaName} persona`,
789
+ data: { dryRun: true },
790
+ actions: [],
791
+ confidence: 1,
792
+ metadata: {
793
+ model: config.model,
794
+ tokensUsed: 0,
795
+ latencyMs: 0,
796
+ toolCalls: [],
797
+ cached: false
798
+ }
799
+ };
800
+ }
801
+ function estimateCost(tokens, model) {
802
+ const costPer1M = {
803
+ "gemini-2.5-flash-lite": 0.01,
804
+ "gemini-3-flash-preview": 0.03
805
+ };
806
+ const rate = costPer1M[model] ?? 0.01;
807
+ return tokens / 1e6 * rate;
808
+ }
809
+
810
+ // src/index.ts
811
+ function init(config2 = {}) {
812
+ updateConfig(config2);
813
+ const fullConfig = getConfig();
814
+ setLogLevel(fullConfig.logLevel);
815
+ attachConsoleAgent();
816
+ }
817
+ function createAgentProxy() {
818
+ const agentFn = (prompt, context, options) => {
819
+ const config2 = getConfig();
820
+ if (config2.mode === "fire-and-forget" && !options?.mode) {
821
+ const promise = executeAgent(prompt, context, options);
822
+ promise.catch(() => {
823
+ });
824
+ return promise;
825
+ }
826
+ return executeAgent(prompt, context, options);
827
+ };
828
+ agentFn.security = (prompt, context, options) => {
829
+ return executeAgent(prompt, context, { ...options, persona: "security" });
830
+ };
831
+ agentFn.debug = (prompt, context, options) => {
832
+ return executeAgent(prompt, context, { ...options, persona: "debugger" });
833
+ };
834
+ agentFn.architect = (prompt, context, options) => {
835
+ return executeAgent(prompt, context, { ...options, persona: "architect" });
836
+ };
837
+ return agentFn;
838
+ }
839
+ var attached = false;
840
+ function attachConsoleAgent() {
841
+ if (attached) return;
842
+ const agentProxy = createAgentProxy();
843
+ console.agent = agentProxy;
844
+ attached = true;
845
+ }
846
+ attachConsoleAgent();
847
+
848
+ export { DEFAULT_CONFIG, init };
849
+ //# sourceMappingURL=index.js.map
850
+ //# sourceMappingURL=index.js.map