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