aden-ts 0.1.1

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.
@@ -0,0 +1,2478 @@
1
+ import OpenAI from 'openai';
2
+
3
+ /**
4
+ * Control Types - Types for the Control Agent
5
+ *
6
+ * Defines control actions, events, and policies for bidirectional
7
+ * communication with the control server.
8
+ */
9
+
10
+ /**
11
+ * Control actions that can be applied to requests
12
+ * - allow: Request proceeds normally
13
+ * - block: Request is rejected
14
+ * - throttle: Request is delayed before proceeding
15
+ * - degrade: Request uses a cheaper/fallback model
16
+ * - alert: Request proceeds but triggers an alert notification
17
+ */
18
+ type ControlAction = "allow" | "block" | "throttle" | "degrade" | "alert";
19
+ /**
20
+ * Control decision - what action to take for a request
21
+ */
22
+ interface ControlDecision {
23
+ /** The action to take */
24
+ action: ControlAction;
25
+ /** Human-readable reason for the decision */
26
+ reason?: string;
27
+ /** If action is "degrade", switch to this model */
28
+ degradeToModel?: string;
29
+ /** If action is "throttle", delay by this many milliseconds */
30
+ throttleDelayMs?: number;
31
+ /** If action is "alert", the severity level */
32
+ alertLevel?: "info" | "warning" | "critical";
33
+ }
34
+ /**
35
+ * Base event structure with common fields
36
+ */
37
+ interface BaseEvent {
38
+ /** Event type discriminator */
39
+ event_type: string;
40
+ /** ISO timestamp of the event */
41
+ timestamp: string;
42
+ /** SDK instance ID for tracking */
43
+ sdk_instance_id: string;
44
+ }
45
+ /**
46
+ * Control event - emitted when a control action is taken
47
+ */
48
+ interface ControlEvent extends BaseEvent {
49
+ event_type: "control";
50
+ /** Trace ID for correlation */
51
+ trace_id: string;
52
+ /** Span ID of the affected request */
53
+ span_id: string;
54
+ /** Context ID (user, session, deal, etc.) */
55
+ context_id?: string;
56
+ /** Provider (openai, anthropic, gemini) */
57
+ provider: string;
58
+ /** Original model that was requested */
59
+ original_model: string;
60
+ /** Action that was taken */
61
+ action: ControlAction;
62
+ /** Reason for the action */
63
+ reason?: string;
64
+ /** If degraded, what model was used instead */
65
+ degraded_to?: string;
66
+ /** If throttled, how long was the delay in ms */
67
+ throttle_delay_ms?: number;
68
+ /** Estimated cost that triggered the decision */
69
+ estimated_cost?: number;
70
+ }
71
+ /**
72
+ * Heartbeat event - periodic health check
73
+ */
74
+ interface HeartbeatEvent extends BaseEvent {
75
+ event_type: "heartbeat";
76
+ /** Connection status */
77
+ status: "healthy" | "degraded" | "reconnecting";
78
+ /** Requests processed since last heartbeat */
79
+ requests_since_last: number;
80
+ /** Errors since last heartbeat */
81
+ errors_since_last: number;
82
+ /** Current policy cache age in seconds */
83
+ policy_cache_age_seconds: number;
84
+ /** Whether WebSocket is connected */
85
+ websocket_connected: boolean;
86
+ /** SDK version */
87
+ sdk_version: string;
88
+ }
89
+ /**
90
+ * Budget type - what scope the budget applies to
91
+ */
92
+ type BudgetType = "global" | "agent" | "tenant" | "customer" | "feature" | "tag";
93
+ /**
94
+ * Limit action - what to do when budget is exceeded
95
+ */
96
+ type LimitAction = "kill" | "throttle" | "degrade";
97
+ /**
98
+ * Budget alert configuration
99
+ */
100
+ interface BudgetAlert {
101
+ /** Threshold percentage (0-100) */
102
+ threshold: number;
103
+ /** Whether this alert is enabled */
104
+ enabled: boolean;
105
+ }
106
+ /**
107
+ * Budget notification settings
108
+ */
109
+ interface BudgetNotifications {
110
+ /** Show in-app notifications */
111
+ inApp: boolean;
112
+ /** Send email notifications */
113
+ email: boolean;
114
+ /** Email recipients */
115
+ emailRecipients: string[];
116
+ /** Send webhook notifications */
117
+ webhook: boolean;
118
+ }
119
+ /**
120
+ * Budget rule - limits spend per context
121
+ */
122
+ interface BudgetRule {
123
+ /** Unique identifier for this budget */
124
+ id: string;
125
+ /** Human-readable name */
126
+ name: string;
127
+ /** Budget type/scope */
128
+ type: BudgetType;
129
+ /** Tags for tag-based budgets (required when type is 'tag') */
130
+ tags?: string[];
131
+ /** Budget limit in USD */
132
+ limit: number;
133
+ /** Current spend in USD */
134
+ spent: number;
135
+ /** Action to take when budget is exceeded */
136
+ limitAction: LimitAction;
137
+ /** If limitAction is "degrade", switch to this model */
138
+ degradeToModel?: string;
139
+ /** Alert thresholds */
140
+ alerts: BudgetAlert[];
141
+ /** Notification settings */
142
+ notifications: BudgetNotifications;
143
+ }
144
+ /**
145
+ * Throttle rule - rate limiting
146
+ */
147
+ interface ThrottleRule {
148
+ /** Context ID this rule applies to (omit for global) */
149
+ context_id?: string;
150
+ /** Provider this rule applies to (omit for all) */
151
+ provider?: string;
152
+ /** Maximum requests per minute */
153
+ requests_per_minute?: number;
154
+ /** Fixed delay to apply to each request (ms) */
155
+ delay_ms?: number;
156
+ }
157
+ /**
158
+ * Block rule - hard block on certain requests
159
+ */
160
+ interface BlockRule {
161
+ /** Context ID to block (omit for pattern match) */
162
+ context_id?: string;
163
+ /** Provider to block (omit for all) */
164
+ provider?: string;
165
+ /** Model pattern to block (e.g., "gpt-4*") */
166
+ model_pattern?: string;
167
+ /** Reason shown to caller */
168
+ reason: string;
169
+ }
170
+ /**
171
+ * Degrade rule - automatic model downgrade
172
+ */
173
+ interface DegradeRule {
174
+ /** Model to downgrade from */
175
+ from_model: string;
176
+ /** Model to downgrade to */
177
+ to_model: string;
178
+ /** When to trigger the downgrade */
179
+ trigger: "budget_threshold" | "rate_limit" | "always";
180
+ /** For budget_threshold: percentage at which to trigger (0-100) */
181
+ threshold_percent?: number;
182
+ /** Context ID this rule applies to (omit for all) */
183
+ context_id?: string;
184
+ }
185
+ /**
186
+ * Alert rule - trigger notifications without blocking
187
+ */
188
+ interface AlertRule {
189
+ /** Context ID this rule applies to (omit for global) */
190
+ context_id?: string;
191
+ /** Provider this rule applies to (omit for all) */
192
+ provider?: string;
193
+ /** Model pattern to alert on (e.g., "gpt-4*" for expensive models) */
194
+ model_pattern?: string;
195
+ /** When to trigger the alert */
196
+ trigger: "budget_threshold" | "model_usage" | "always";
197
+ /** For budget_threshold: percentage at which to trigger (0-100) */
198
+ threshold_percent?: number;
199
+ /** Alert severity level */
200
+ level: "info" | "warning" | "critical";
201
+ /** Message to include in the alert */
202
+ message: string;
203
+ }
204
+ /**
205
+ * Complete control policy from server
206
+ */
207
+ interface ControlPolicy {
208
+ /** Policy version for cache invalidation */
209
+ version: string;
210
+ /** When this policy was last updated */
211
+ updated_at: string;
212
+ /** Budget rules */
213
+ budgets?: BudgetRule[];
214
+ /** Throttle rules */
215
+ throttles?: ThrottleRule[];
216
+ /** Block rules */
217
+ blocks?: BlockRule[];
218
+ /** Degrade rules */
219
+ degradations?: DegradeRule[];
220
+ /** Alert rules */
221
+ alerts?: AlertRule[];
222
+ }
223
+ /**
224
+ * Request context for getting a control decision
225
+ */
226
+ interface ControlRequest {
227
+ /** Context ID (user, session, deal, etc.) */
228
+ context_id?: string;
229
+ /** Provider being called */
230
+ provider: string;
231
+ /** Model being requested */
232
+ model: string;
233
+ /** Estimated cost of this request in USD */
234
+ estimated_cost?: number;
235
+ /** Estimated input tokens */
236
+ estimated_input_tokens?: number;
237
+ /** Custom metadata */
238
+ metadata?: Record<string, unknown>;
239
+ }
240
+ /**
241
+ * Alert event passed to onAlert callback
242
+ */
243
+ interface AlertEvent {
244
+ /** Alert severity level */
245
+ level: "info" | "warning" | "critical";
246
+ /** Alert message */
247
+ message: string;
248
+ /** Reason the alert was triggered */
249
+ reason: string;
250
+ /** Context ID that triggered the alert */
251
+ contextId?: string;
252
+ /** Provider that triggered the alert */
253
+ provider: string;
254
+ /** Model that triggered the alert */
255
+ model: string;
256
+ /** Timestamp of the alert */
257
+ timestamp: Date;
258
+ }
259
+ /**
260
+ * Options for creating a control agent
261
+ */
262
+ interface ControlAgentOptions {
263
+ /** Server URL (wss:// for WebSocket, https:// for HTTP-only) */
264
+ serverUrl: string;
265
+ /** API key for authentication */
266
+ apiKey: string;
267
+ /** Polling interval for HTTP fallback (ms), default: 30000 */
268
+ pollingIntervalMs?: number;
269
+ /** Heartbeat interval (ms), default: 10000 */
270
+ heartbeatIntervalMs?: number;
271
+ /** Request timeout (ms), default: 5000 */
272
+ timeoutMs?: number;
273
+ /** Fail open (allow) if server is unreachable, default: true */
274
+ failOpen?: boolean;
275
+ /** Custom context ID extractor */
276
+ getContextId?: () => string | undefined;
277
+ /** SDK instance identifier (auto-generated if not provided) */
278
+ instanceId?: string;
279
+ /**
280
+ * Callback invoked when an alert is triggered.
281
+ * Alerts do NOT block requests - they are notifications only.
282
+ * Use this for logging, notifications, or monitoring.
283
+ */
284
+ onAlert?: (alert: AlertEvent) => void | Promise<void>;
285
+ /**
286
+ * Enable hybrid enforcement (local + server-side validation).
287
+ * When enabled, budgets above the threshold are validated with the server.
288
+ * Default: false
289
+ */
290
+ enableHybridEnforcement?: boolean;
291
+ /**
292
+ * Budget usage threshold (percentage) at which to start server validation.
293
+ * Requests below this threshold use local-only enforcement.
294
+ * Default: 80
295
+ */
296
+ serverValidationThreshold?: number;
297
+ /**
298
+ * Timeout for server validation requests (ms).
299
+ * Default: 2000
300
+ */
301
+ serverValidationTimeoutMs?: number;
302
+ /**
303
+ * Enable adaptive threshold adjustment based on remaining budget.
304
+ * When enabled, force validation when remaining budget is critically low.
305
+ * Default: true
306
+ */
307
+ adaptiveThresholdEnabled?: boolean;
308
+ /**
309
+ * Minimum remaining budget (USD) that triggers forced server validation.
310
+ * Only applies when adaptiveThresholdEnabled is true.
311
+ * Default: 1.0
312
+ */
313
+ adaptiveMinRemainingUsd?: number;
314
+ /**
315
+ * Enable probabilistic sampling for server validation.
316
+ * Reduces latency impact by validating a fraction of requests.
317
+ * Default: true
318
+ */
319
+ samplingEnabled?: boolean;
320
+ /**
321
+ * Base sampling rate at the threshold (0-1).
322
+ * This is the minimum rate used at serverValidationThreshold.
323
+ * Default: 0.1 (10%)
324
+ */
325
+ samplingBaseRate?: number;
326
+ /**
327
+ * Budget usage percentage at which to validate all requests.
328
+ * Between threshold and this value, sampling rate interpolates to 1.0.
329
+ * Default: 95
330
+ */
331
+ samplingFullValidationPercent?: number;
332
+ /**
333
+ * Maximum expected overspend percentage beyond the soft limit.
334
+ * Acts as a hard limit safety net to prevent runaway spending.
335
+ * Default: 10 (allowing up to 110% of budget)
336
+ */
337
+ maxExpectedOverspendPercent?: number;
338
+ }
339
+ /**
340
+ * Budget validation request sent to server
341
+ */
342
+ interface BudgetValidationRequest {
343
+ /** Budget ID to validate */
344
+ budgetId: string;
345
+ /** Estimated cost of the pending request */
346
+ estimatedCost: number;
347
+ /** Local spend tracking (server uses max of local vs server) */
348
+ localSpend?: number;
349
+ /** Budget context */
350
+ context?: {
351
+ type?: string;
352
+ value?: string;
353
+ tags?: string[];
354
+ };
355
+ }
356
+ /**
357
+ * Budget validation response from server
358
+ */
359
+ interface BudgetValidationResponse {
360
+ /** Whether the request is allowed */
361
+ allowed: boolean;
362
+ /** Action to take */
363
+ action: "allow" | "block" | "degrade" | "throttle";
364
+ /** Authoritative spend from server (TSDB) */
365
+ authoritativeSpend: number;
366
+ /** Budget limit */
367
+ budgetLimit: number;
368
+ /** Current usage percentage */
369
+ usagePercent: number;
370
+ /** Policy version */
371
+ policyVersion: string;
372
+ /** Updated spend after this request */
373
+ updatedSpend: number;
374
+ /** Reason for the decision */
375
+ reason?: string;
376
+ /** Projected usage percentage */
377
+ projectedPercent?: number;
378
+ /** Model to degrade to (if action is "degrade") */
379
+ degradeToModel?: string;
380
+ }
381
+ /**
382
+ * Control Agent interface - the public API
383
+ */
384
+ interface IControlAgent {
385
+ /**
386
+ * Connect to the control server
387
+ */
388
+ connect(): Promise<void>;
389
+ /**
390
+ * Disconnect from the control server
391
+ */
392
+ disconnect(): Promise<void>;
393
+ /**
394
+ * Get a control decision for a request
395
+ */
396
+ getDecision(request: ControlRequest): Promise<ControlDecision>;
397
+ /**
398
+ * Report a metric event to the server
399
+ */
400
+ reportMetric(event: MetricEvent): Promise<void>;
401
+ /**
402
+ * Report a control event to the server
403
+ */
404
+ reportControlEvent(event: Omit<ControlEvent, "event_type" | "timestamp" | "sdk_instance_id">): Promise<void>;
405
+ /**
406
+ * Check if connected to server
407
+ */
408
+ isConnected(): boolean;
409
+ /**
410
+ * Get current cached policy
411
+ */
412
+ getPolicy(): ControlPolicy | null;
413
+ }
414
+
415
+ /**
416
+ * Normalized usage metrics that work across both API response shapes
417
+ * (Responses API vs Chat Completions API)
418
+ */
419
+ interface NormalizedUsage {
420
+ /** Input/prompt tokens consumed */
421
+ input_tokens: number;
422
+ /** Output/completion tokens consumed */
423
+ output_tokens: number;
424
+ /** Total tokens (input + output) */
425
+ total_tokens: number;
426
+ /** Reasoning tokens used (for o1/o3 models) */
427
+ reasoning_tokens: number;
428
+ /** Tokens served from prompt cache (reduces cost) */
429
+ cached_tokens: number;
430
+ /** Prediction tokens that were accepted */
431
+ accepted_prediction_tokens: number;
432
+ /** Prediction tokens that were rejected */
433
+ rejected_prediction_tokens: number;
434
+ }
435
+ /**
436
+ * Request metadata that affects billing/cost
437
+ */
438
+ interface RequestMetadata {
439
+ /** Unique trace ID for this request */
440
+ traceId: string;
441
+ /** Model used for the request */
442
+ model: string;
443
+ /** Service tier (affects pricing/performance) */
444
+ service_tier?: "auto" | "default" | "flex" | "priority" | string;
445
+ /** Maximum output tokens cap */
446
+ max_output_tokens?: number;
447
+ /** Maximum tool calls allowed */
448
+ max_tool_calls?: number;
449
+ /** Prompt cache key for improved cache hits */
450
+ prompt_cache_key?: string;
451
+ /** Prompt cache retention policy */
452
+ prompt_cache_retention?: "in_memory" | "24h" | string;
453
+ /** Whether streaming was enabled */
454
+ stream: boolean;
455
+ }
456
+ /**
457
+ * Information about where an LLM call originated in the code
458
+ */
459
+ interface CallSite {
460
+ /** File path where the call originated */
461
+ file: string;
462
+ /** Line number */
463
+ line: number;
464
+ /** Column number */
465
+ column: number;
466
+ /** Function name (if available) */
467
+ function?: string;
468
+ }
469
+ /**
470
+ * Complete metric event emitted after each API call.
471
+ * All fields are flat (not nested) for consistent cross-provider analytics.
472
+ * Uses OpenTelemetry-compatible naming: trace_id groups operations, span_id identifies each operation.
473
+ */
474
+ interface MetricEvent {
475
+ /** Trace ID grouping related operations (OTel standard) */
476
+ trace_id: string;
477
+ /** Unique span ID for this specific operation (OTel standard) */
478
+ span_id: string;
479
+ /** Parent span ID for nested/hierarchical calls (OTel standard) */
480
+ parent_span_id?: string;
481
+ /** Provider-specific request ID (if available) */
482
+ request_id: string | null;
483
+ /** LLM provider: openai, gemini, anthropic */
484
+ provider: "openai" | "gemini" | "anthropic";
485
+ /** Model used for the request */
486
+ model: string;
487
+ /** Whether streaming was enabled */
488
+ stream: boolean;
489
+ /** ISO timestamp when the request started */
490
+ timestamp: string;
491
+ /** Request latency in milliseconds */
492
+ latency_ms: number;
493
+ /** HTTP status code (if available) */
494
+ status_code?: number;
495
+ /** Error message if request failed */
496
+ error?: string;
497
+ /** Input/prompt tokens consumed */
498
+ input_tokens: number;
499
+ /** Output/completion tokens consumed */
500
+ output_tokens: number;
501
+ /** Total tokens (input + output) */
502
+ total_tokens: number;
503
+ /** Tokens served from cache (reduces cost) */
504
+ cached_tokens: number;
505
+ /** Reasoning tokens used (for o1/o3 models) */
506
+ reasoning_tokens: number;
507
+ /** Remaining requests in current window */
508
+ rate_limit_remaining_requests?: number;
509
+ /** Remaining tokens in current window */
510
+ rate_limit_remaining_tokens?: number;
511
+ /** Time until request limit resets (seconds) */
512
+ rate_limit_reset_requests?: number;
513
+ /** Time until token limit resets (seconds) */
514
+ rate_limit_reset_tokens?: number;
515
+ /** Sequence number within the trace */
516
+ call_sequence?: number;
517
+ /** Stack of agent/handler names leading to this call */
518
+ agent_stack?: string[];
519
+ /** File path where the call originated (immediate caller) */
520
+ call_site_file?: string;
521
+ /** Line number where the call originated */
522
+ call_site_line?: number;
523
+ /** Column number where the call originated */
524
+ call_site_column?: number;
525
+ /** Function name where the call originated */
526
+ call_site_function?: string;
527
+ /** Full call stack for detailed tracing (file:line:function) */
528
+ call_stack?: string[];
529
+ /** Number of tool calls made */
530
+ tool_call_count?: number;
531
+ /** Tool names that were called (comma-separated) */
532
+ tool_names?: string;
533
+ /** Service tier (OpenAI: auto, default, flex, priority) */
534
+ service_tier?: string;
535
+ /** Custom metadata attached to the request */
536
+ metadata?: Record<string, string>;
537
+ }
538
+ /**
539
+ * Rate limit information from response headers
540
+ */
541
+ interface RateLimitInfo {
542
+ /** Remaining requests in current window */
543
+ remaining_requests?: number;
544
+ /** Remaining tokens in current window */
545
+ remaining_tokens?: number;
546
+ /** Time until request limit resets (seconds) */
547
+ reset_requests?: number;
548
+ /** Time until token limit resets (seconds) */
549
+ reset_tokens?: number;
550
+ }
551
+ /**
552
+ * Metric for individual tool calls
553
+ */
554
+ interface ToolCallMetric {
555
+ /** Tool type (function, web_search, code_interpreter, etc.) */
556
+ type: string;
557
+ /** Tool/function name */
558
+ name?: string;
559
+ /** Duration of tool execution in ms (if available) */
560
+ duration_ms?: number;
561
+ }
562
+ /**
563
+ * Callback function for emitting metrics
564
+ */
565
+ type MetricEmitter = (event: MetricEvent) => void | Promise<void>;
566
+ /**
567
+ * Context passed to the beforeRequest hook
568
+ */
569
+ interface BeforeRequestContext {
570
+ /** The model being used for this request */
571
+ model: string;
572
+ /** Whether this is a streaming request */
573
+ stream: boolean;
574
+ /** Generated span ID for this request (OTel standard) */
575
+ spanId: string;
576
+ /** Trace ID grouping related operations (OTel standard) */
577
+ traceId: string;
578
+ /** Timestamp when the request was initiated */
579
+ timestamp: Date;
580
+ /** Custom metadata that can be passed through */
581
+ metadata?: Record<string, unknown>;
582
+ }
583
+ /**
584
+ * Result from the beforeRequest hook
585
+ */
586
+ type BeforeRequestResult = {
587
+ action: "proceed";
588
+ } | {
589
+ action: "throttle";
590
+ delayMs: number;
591
+ } | {
592
+ action: "cancel";
593
+ reason: string;
594
+ } | {
595
+ action: "degrade";
596
+ toModel: string;
597
+ reason?: string;
598
+ delayMs?: number;
599
+ } | {
600
+ action: "alert";
601
+ level: "info" | "warning" | "critical";
602
+ message: string;
603
+ delayMs?: number;
604
+ };
605
+ /**
606
+ * Hook called before each API request, allowing user-defined rate limiting
607
+ *
608
+ * @example
609
+ * ```ts
610
+ * beforeRequest: async (params, context) => {
611
+ * const remaining = await checkQuota(context.metadata?.tenantId);
612
+ * if (remaining <= 0) {
613
+ * return { action: 'cancel', reason: 'Quota exceeded' };
614
+ * }
615
+ * if (remaining < 10) {
616
+ * return { action: 'throttle', delayMs: 1000 };
617
+ * }
618
+ * return { action: 'proceed' };
619
+ * }
620
+ * ```
621
+ */
622
+ type BeforeRequestHook = (params: Record<string, unknown>, context: BeforeRequestContext) => BeforeRequestResult | Promise<BeforeRequestResult>;
623
+ /**
624
+ * Error thrown when a request is cancelled by the beforeRequest hook
625
+ */
626
+ declare class RequestCancelledError extends Error {
627
+ readonly reason: string;
628
+ readonly context: BeforeRequestContext;
629
+ constructor(reason: string, context: BeforeRequestContext);
630
+ }
631
+ /**
632
+ * Options for the metered OpenAI client
633
+ */
634
+ /**
635
+ * SDK classes that can be passed for instrumentation.
636
+ * Use this when you have multiple copies of SDK packages in your node_modules
637
+ * (e.g., when using file: dependencies or monorepos).
638
+ */
639
+ interface SDKClasses {
640
+ /** GoogleGenerativeAI class from @google/generative-ai */
641
+ GoogleGenerativeAI?: any;
642
+ /** OpenAI class from openai */
643
+ OpenAI?: any;
644
+ /** Anthropic class from @anthropic-ai/sdk */
645
+ Anthropic?: any;
646
+ }
647
+ /**
648
+ * Default control server URL
649
+ */
650
+ declare const DEFAULT_CONTROL_SERVER = "https://kube.acho.io";
651
+ /**
652
+ * Get the control server URL with priority:
653
+ * 1. Explicit serverUrl option
654
+ * 2. ADEN_API_URL environment variable
655
+ * 3. DEFAULT_CONTROL_SERVER constant
656
+ */
657
+ declare function getControlServerUrl(serverUrl?: string): string;
658
+ interface MeterOptions {
659
+ /**
660
+ * API key for the control server.
661
+ * When provided, automatically creates a control agent and emitter.
662
+ * This is the simplest way to enable metering with remote control.
663
+ *
664
+ * If not provided, checks ADEN_API_KEY environment variable.
665
+ *
666
+ * @example
667
+ * ```typescript
668
+ * // Simplest setup - just provide API key
669
+ * await instrument({
670
+ * apiKey: process.env.ADEN_API_KEY,
671
+ * sdks: { OpenAI },
672
+ * });
673
+ * ```
674
+ */
675
+ apiKey?: string;
676
+ /**
677
+ * Control server URL.
678
+ * Priority: serverUrl option > ADEN_API_URL env var > https://kube.acho.io
679
+ * Only used when apiKey is provided.
680
+ */
681
+ serverUrl?: string;
682
+ /**
683
+ * Whether to allow requests when control server is unreachable.
684
+ * Default: true (fail open - requests proceed if server is down)
685
+ * Set to false for strict control (fail closed - block if server unreachable)
686
+ */
687
+ failOpen?: boolean;
688
+ /**
689
+ * Custom metric emitter function.
690
+ * When apiKey is provided, this is optional - metrics go to control server.
691
+ * When apiKey is NOT provided, this is required.
692
+ *
693
+ * You can combine with apiKey to emit to multiple destinations:
694
+ * @example
695
+ * ```typescript
696
+ * await instrument({
697
+ * apiKey: process.env.ADEN_API_KEY,
698
+ * emitMetric: createConsoleEmitter({ pretty: true }), // Also log locally
699
+ * });
700
+ * ```
701
+ */
702
+ emitMetric?: MetricEmitter;
703
+ /** Whether to include tool call metrics (default: true) */
704
+ trackToolCalls?: boolean;
705
+ /** Custom span ID generator (default: crypto.randomUUID) */
706
+ generateSpanId?: () => string;
707
+ /**
708
+ * Hook called before each request for user-defined rate limiting.
709
+ * Can cancel requests, throttle them with a delay, or allow them to proceed.
710
+ */
711
+ beforeRequest?: BeforeRequestHook;
712
+ /** Custom metadata to pass to beforeRequest hook */
713
+ requestMetadata?: Record<string, unknown>;
714
+ /**
715
+ * Whether to automatically track call relationships using AsyncLocalStorage.
716
+ * When enabled, related LLM calls are grouped by session, with parent/child
717
+ * relationships, agent stacks, and call sites automatically detected.
718
+ * Default: true
719
+ */
720
+ trackCallRelationships?: boolean;
721
+ /**
722
+ * SDK classes to instrument. Pass these when you have multiple copies of
723
+ * SDK packages in your node_modules (common in monorepos or with file: dependencies).
724
+ * If not provided, Aden will try to import the SDKs from its own node_modules,
725
+ * which may not be the same instance your application uses.
726
+ *
727
+ * @example
728
+ * ```typescript
729
+ * import { GoogleGenerativeAI } from "@google/generative-ai";
730
+ *
731
+ * instrument({
732
+ * apiKey: process.env.ADEN_API_KEY,
733
+ * sdks: { GoogleGenerativeAI },
734
+ * });
735
+ * ```
736
+ */
737
+ sdks?: SDKClasses;
738
+ /**
739
+ * Function to get the current context ID (user ID, session ID, etc.)
740
+ * Used for budget tracking and policy enforcement per context.
741
+ *
742
+ * @example
743
+ * ```typescript
744
+ * instrument({
745
+ * apiKey: process.env.ADEN_API_KEY,
746
+ * getContextId: () => getCurrentUserId(),
747
+ * });
748
+ * ```
749
+ */
750
+ getContextId?: () => string | undefined;
751
+ /**
752
+ * Pre-configured control agent instance.
753
+ * Use this for advanced control agent configuration.
754
+ * When apiKey is provided, a control agent is created automatically.
755
+ */
756
+ controlAgent?: IControlAgent;
757
+ /**
758
+ * Callback invoked when an alert is triggered by the control agent.
759
+ * Alerts do NOT block requests - they are notifications only.
760
+ * Use this for logging, notifications, or monitoring.
761
+ *
762
+ * @example
763
+ * ```typescript
764
+ * instrument({
765
+ * apiKey: process.env.ADEN_API_KEY,
766
+ * onAlert: (alert) => {
767
+ * console.warn(`[${alert.level}] ${alert.message}`);
768
+ * // Send to Slack, PagerDuty, etc.
769
+ * },
770
+ * });
771
+ * ```
772
+ */
773
+ onAlert?: (alert: {
774
+ level: "info" | "warning" | "critical";
775
+ message: string;
776
+ reason: string;
777
+ contextId?: string;
778
+ provider: string;
779
+ model: string;
780
+ timestamp: Date;
781
+ }) => void | Promise<void>;
782
+ }
783
+ /**
784
+ * Budget configuration for guardrails
785
+ */
786
+ interface BudgetConfig {
787
+ /** Maximum input tokens allowed per request */
788
+ maxInputTokens?: number;
789
+ /** Maximum total tokens allowed per request */
790
+ maxTotalTokens?: number;
791
+ /** Action to take when budget is exceeded */
792
+ onExceeded?: "throw" | "truncate" | "warn";
793
+ /** Custom handler when budget is exceeded */
794
+ onExceededHandler?: (info: BudgetExceededInfo) => void | Promise<void>;
795
+ }
796
+ /**
797
+ * Information about a budget violation
798
+ */
799
+ interface BudgetExceededInfo {
800
+ /** Estimated input tokens */
801
+ estimatedInputTokens: number;
802
+ /** Configured maximum */
803
+ maxInputTokens: number;
804
+ /** Model being used */
805
+ model: string;
806
+ /** Original input that exceeded budget */
807
+ input: unknown;
808
+ }
809
+ /**
810
+ * Streaming event types we care about for metrics
811
+ */
812
+ type StreamingEventType = "response.created" | "response.in_progress" | "response.completed" | "response.failed" | "response.incomplete" | "response.output_item.added" | "response.content_part.added" | "response.content_part.done" | "response.output_item.done" | "response.function_call_arguments.delta" | "response.function_call_arguments.done";
813
+ /**
814
+ * Extended OpenAI client with metering capabilities
815
+ */
816
+ type MeteredOpenAI = OpenAI & {
817
+ __metered: true;
818
+ __meterOptions: MeterOptions;
819
+ };
820
+
821
+ /**
822
+ * Wraps an OpenAI client with metering capabilities.
823
+ *
824
+ * This function injects metering into the client without modifying the SDK,
825
+ * allowing you to track usage metrics, billing data, and request metadata
826
+ * for every API call.
827
+ *
828
+ * @param client - The OpenAI client instance to wrap
829
+ * @param options - Metering options including the metric emitter
830
+ * @returns The same client with metering injected
831
+ *
832
+ * @example
833
+ * ```ts
834
+ * import OpenAI from "openai";
835
+ * import { makeMeteredOpenAI } from "openai-meter";
836
+ *
837
+ * const client = new OpenAI();
838
+ * const metered = makeMeteredOpenAI(client, {
839
+ * emitMetric: (event) => {
840
+ * console.log("Usage:", event.usage);
841
+ * // Send to your metrics backend
842
+ * },
843
+ * });
844
+ *
845
+ * // Use normally - metrics are collected automatically
846
+ * const response = await metered.responses.create({
847
+ * model: "gpt-4.1",
848
+ * input: "Hello!",
849
+ * });
850
+ * ```
851
+ */
852
+ declare function makeMeteredOpenAI(client: OpenAI, options: MeterOptions): MeteredOpenAI;
853
+ /**
854
+ * Check if a client has already been wrapped with metering
855
+ */
856
+ declare function isMetered(client: OpenAI): client is MeteredOpenAI;
857
+
858
+ /**
859
+ * OpenAI SDK instrumentation.
860
+ *
861
+ * Call `instrumentOpenAI()` to instrument the OpenAI SDK.
862
+ * This is called automatically by the main `instrument()` function.
863
+ */
864
+
865
+ /**
866
+ * Instrument OpenAI SDK globally.
867
+ *
868
+ * Call once at application startup. All OpenAI client instances
869
+ * will automatically be metered.
870
+ *
871
+ * @returns true if instrumentation was successful, false if SDK not found
872
+ */
873
+ declare function instrumentOpenAI(options: MeterOptions): Promise<boolean>;
874
+ /**
875
+ * Remove OpenAI instrumentation.
876
+ *
877
+ * Restores original behavior for all clients.
878
+ */
879
+ declare function uninstrumentOpenAI(): Promise<void>;
880
+ /**
881
+ * Check if OpenAI is currently instrumented
882
+ */
883
+ declare function isOpenAIInstrumented(): boolean;
884
+
885
+ /**
886
+ * Global instrumentation for Google Generative AI (Gemini) clients.
887
+ *
888
+ * Call `instrumentGemini()` once at startup, and all Gemini client instances
889
+ * (existing and future) are automatically metered.
890
+ */
891
+
892
+ /**
893
+ * Instrument Google Generative AI (Gemini) globally.
894
+ *
895
+ * Call once at application startup. All Gemini GenerativeModel instances
896
+ * will automatically be metered.
897
+ *
898
+ * @returns true if instrumentation was successful, false if SDK not found
899
+ */
900
+ declare function instrumentGemini(options: MeterOptions): Promise<boolean>;
901
+ /**
902
+ * Remove Gemini instrumentation.
903
+ *
904
+ * Restores original behavior for all clients.
905
+ */
906
+ declare function uninstrumentGemini(): Promise<void>;
907
+ /**
908
+ * Check if Gemini is currently instrumented
909
+ */
910
+ declare function isGeminiInstrumented(): boolean;
911
+
912
+ /**
913
+ * Global instrumentation for Anthropic (Claude) SDK clients.
914
+ *
915
+ * Call `instrumentAnthropic()` once at startup, and all Anthropic client instances
916
+ * (existing and future) are automatically metered.
917
+ */
918
+
919
+ /**
920
+ * Instrument Anthropic (Claude) SDK globally.
921
+ *
922
+ * Call once at application startup. All Anthropic client instances
923
+ * will automatically be metered.
924
+ *
925
+ * @returns true if instrumentation was successful, false if SDK not found
926
+ */
927
+ declare function instrumentAnthropic(options: MeterOptions): Promise<boolean>;
928
+ /**
929
+ * Remove Anthropic instrumentation.
930
+ *
931
+ * Restores original behavior for all clients.
932
+ */
933
+ declare function uninstrumentAnthropic(): Promise<void>;
934
+ /**
935
+ * Check if Anthropic is currently instrumented
936
+ */
937
+ declare function isAnthropicInstrumented(): boolean;
938
+
939
+ /**
940
+ * Fetch-based instrumentation for frameworks that bypass SDK classes
941
+ *
942
+ * Works with: Vercel AI SDK, LangChain, Mastra, and any framework
943
+ * that makes direct HTTP calls to LLM APIs.
944
+ */
945
+
946
+ /**
947
+ * Instrument global fetch to capture LLM API calls
948
+ */
949
+ declare function instrumentFetch(options: MeterOptions): Promise<boolean>;
950
+ /**
951
+ * Remove fetch instrumentation
952
+ */
953
+ declare function uninstrumentFetch(): Promise<void>;
954
+ /**
955
+ * Check if fetch is instrumented
956
+ */
957
+ declare function isFetchInstrumented(): boolean;
958
+
959
+ /**
960
+ * Unified instrumentation for LLM SDKs.
961
+ *
962
+ * Call `instrument()` once at startup, and all available LLM client instances
963
+ * (OpenAI, Gemini, Anthropic) are automatically detected and metered.
964
+ */
965
+
966
+ /**
967
+ * Result of instrumentation showing which SDKs were instrumented
968
+ */
969
+ interface InstrumentationResult {
970
+ openai: boolean;
971
+ gemini: boolean;
972
+ anthropic: boolean;
973
+ controlAgent: IControlAgent | null;
974
+ }
975
+ /**
976
+ * Instrument all available LLM SDKs globally.
977
+ *
978
+ * Call once at application startup. All detected LLM client instances
979
+ * (OpenAI, Gemini, Anthropic) will automatically be metered.
980
+ *
981
+ * The function auto-detects which SDKs are installed and instruments them.
982
+ * SDKs that aren't installed are silently skipped.
983
+ *
984
+ * @example
985
+ * ```typescript
986
+ * import { instrument } from "aden";
987
+ * import OpenAI from "openai";
988
+ *
989
+ * // Simplest setup - just provide API key
990
+ * await instrument({
991
+ * apiKey: process.env.ADEN_API_KEY,
992
+ * sdks: { OpenAI },
993
+ * });
994
+ *
995
+ * // Or use ADEN_API_KEY environment variable
996
+ * await instrument({ sdks: { OpenAI } });
997
+ *
998
+ * // Use any LLM SDK normally - metrics collected automatically
999
+ * const openai = new OpenAI();
1000
+ * ```
1001
+ */
1002
+ declare function instrument(options: MeterOptions): Promise<InstrumentationResult>;
1003
+ /**
1004
+ * Remove instrumentation from all LLM SDKs.
1005
+ *
1006
+ * Restores original behavior for all clients.
1007
+ */
1008
+ declare function uninstrument(): Promise<void>;
1009
+ /**
1010
+ * Check which SDKs are currently instrumented
1011
+ */
1012
+ declare function getInstrumentedSDKs(): InstrumentationResult;
1013
+ /**
1014
+ * Check if any SDK is currently instrumented
1015
+ */
1016
+ declare function isInstrumented(): boolean;
1017
+ /**
1018
+ * Get the current instrumentation options
1019
+ */
1020
+ declare function getInstrumentationOptions(): MeterOptions | null;
1021
+ /**
1022
+ * Update instrumentation options without re-instrumenting.
1023
+ *
1024
+ * Useful for changing emitters or settings at runtime.
1025
+ */
1026
+ declare function updateInstrumentationOptions(updates: Partial<MeterOptions>): void;
1027
+
1028
+ /**
1029
+ * Google GenAI SDK instrumentation (new SDK).
1030
+ *
1031
+ * This module provides global instrumentation for the new Google GenAI SDK
1032
+ * (@google/genai package) used by Google ADK and other modern Google AI tools.
1033
+ *
1034
+ * The new SDK uses:
1035
+ * import { GoogleGenAI } from "@google/genai";
1036
+ * const client = new GoogleGenAI({ apiKey: "..." });
1037
+ * await client.models.generateContent({ model: "...", contents: "..." });
1038
+ */
1039
+
1040
+ /**
1041
+ * Instrument the Google GenAI SDK (google-genai package).
1042
+ *
1043
+ * This is the new SDK used by Google ADK and replaces google-generativeai.
1044
+ *
1045
+ * @param options - Metering options including the metric emitter
1046
+ * @returns true if instrumentation succeeded, false if SDK not available
1047
+ */
1048
+ declare function instrumentGenai(options: MeterOptions): Promise<boolean>;
1049
+ /**
1050
+ * Remove Google GenAI SDK instrumentation.
1051
+ */
1052
+ declare function uninstrumentGenai(): Promise<void>;
1053
+ /**
1054
+ * Check if Google GenAI SDK is currently instrumented.
1055
+ */
1056
+ declare function isGenaiInstrumented(): boolean;
1057
+ /**
1058
+ * Get current GenAI instrumentation options.
1059
+ */
1060
+ declare function getGenaiOptions(): MeterOptions | null;
1061
+
1062
+ /**
1063
+ * Context for tracking related LLM calls within a trace (OTel-compatible)
1064
+ */
1065
+ interface MeterContext {
1066
+ /** Trace ID grouping related operations (OTel standard) */
1067
+ traceId: string;
1068
+ /** Current call sequence number within trace */
1069
+ callSequence: number;
1070
+ /** Stack of agent/function names for nested calls */
1071
+ agentStack: string[];
1072
+ /** Parent span ID for hierarchical tracking (OTel standard) */
1073
+ parentSpanId?: string;
1074
+ /** Custom metadata attached to this context */
1075
+ metadata?: Record<string, unknown>;
1076
+ /** Stack fingerprint for heuristic grouping */
1077
+ stackFingerprint?: string;
1078
+ }
1079
+ /**
1080
+ * Extended metric event with call relationship tracking (OTel-compatible)
1081
+ */
1082
+ interface CallRelationship {
1083
+ /** Trace ID grouping related operations (OTel standard) */
1084
+ traceId: string;
1085
+ /** Parent span ID if this is a nested call (OTel standard) */
1086
+ parentSpanId?: string;
1087
+ /** Sequence number within the trace */
1088
+ callSequence: number;
1089
+ /** Stack of agent names leading to this call */
1090
+ agentStack: string[];
1091
+ /** Where in the code this call originated (immediate caller) */
1092
+ callSite?: CallSite;
1093
+ /** Full call stack for detailed tracing */
1094
+ callStack?: string[];
1095
+ }
1096
+ /**
1097
+ * Extract the first user-code call site from the current stack
1098
+ */
1099
+ declare function extractCallSite(): CallSite | undefined;
1100
+ /**
1101
+ * Extract agent names from the current call stack
1102
+ */
1103
+ declare function extractAgentStack(): string[];
1104
+ /**
1105
+ * Generate a fingerprint from the call stack for grouping related calls
1106
+ */
1107
+ declare function generateStackFingerprint(): string;
1108
+ /**
1109
+ * Create a new meter context
1110
+ */
1111
+ declare function createMeterContext(options?: {
1112
+ traceId?: string;
1113
+ metadata?: Record<string, unknown>;
1114
+ }): MeterContext;
1115
+ /**
1116
+ * Get the current meter context, creating one automatically if needed
1117
+ *
1118
+ * This enables zero-wrapper usage - the first LLM call will auto-create
1119
+ * a session context that subsequent calls will inherit.
1120
+ */
1121
+ declare function getCurrentContext(): MeterContext;
1122
+ /**
1123
+ * Check if we're currently inside a meter context
1124
+ */
1125
+ declare function hasContext(): boolean;
1126
+ /**
1127
+ * Run a function within a new meter context (trace)
1128
+ *
1129
+ * @example
1130
+ * ```ts
1131
+ * await withMeterContext(async () => {
1132
+ * // All LLM calls here share the same trace
1133
+ * await client.responses.create({ ... });
1134
+ * await client.responses.create({ ... });
1135
+ * }, { metadata: { userId: "123" } });
1136
+ * ```
1137
+ */
1138
+ declare function withMeterContext<T>(fn: () => T, options?: {
1139
+ traceId?: string;
1140
+ metadata?: Record<string, unknown>;
1141
+ }): T;
1142
+ /**
1143
+ * Run an async function within a new meter context (trace)
1144
+ */
1145
+ declare function withMeterContextAsync<T>(fn: () => Promise<T>, options?: {
1146
+ traceId?: string;
1147
+ metadata?: Record<string, unknown>;
1148
+ }): Promise<T>;
1149
+ /**
1150
+ * Enter a meter context that persists across awaits without explicit wrapping
1151
+ *
1152
+ * This is the zero-wrapper approach - call once at the start of your
1153
+ * request/session handler, and all subsequent LLM calls will be tracked.
1154
+ *
1155
+ * @example
1156
+ * ```ts
1157
+ * app.post('/chat', async (req, res) => {
1158
+ * enterMeterContext({ metadata: { userId: req.userId } });
1159
+ *
1160
+ * // All LLM calls in this request are now tracked together
1161
+ * const response = await client.responses.create({ ... });
1162
+ * res.json(response);
1163
+ * });
1164
+ * ```
1165
+ */
1166
+ declare function enterMeterContext(options?: {
1167
+ traceId?: string;
1168
+ metadata?: Record<string, unknown>;
1169
+ }): MeterContext;
1170
+ /**
1171
+ * Push an agent name onto the current context's agent stack
1172
+ *
1173
+ * Use this when entering a named agent/handler to track nesting.
1174
+ */
1175
+ declare function pushAgent(name: string): void;
1176
+ /**
1177
+ * Pop an agent name from the current context's agent stack
1178
+ */
1179
+ declare function popAgent(): string | undefined;
1180
+ /**
1181
+ * Run a function within a named agent context
1182
+ *
1183
+ * @example
1184
+ * ```ts
1185
+ * await withAgent("ResearchAgent", async () => {
1186
+ * // LLM calls here will have "ResearchAgent" in their agentStack
1187
+ * await client.responses.create({ ... });
1188
+ * });
1189
+ * ```
1190
+ */
1191
+ declare function withAgent<T>(name: string, fn: () => Promise<T>): Promise<T>;
1192
+ /**
1193
+ * Reset the global session (useful for testing)
1194
+ */
1195
+ declare function resetGlobalSession(): void;
1196
+ /**
1197
+ * Set custom metadata on the current context
1198
+ */
1199
+ declare function setContextMetadata(key: string, value: unknown): void;
1200
+ /**
1201
+ * Get custom metadata from the current context
1202
+ */
1203
+ declare function getContextMetadata(key: string): unknown;
1204
+ /**
1205
+ * Merge stack-detected agents with explicit agent stack
1206
+ */
1207
+ declare function getFullAgentStack(): string[];
1208
+
1209
+ /**
1210
+ * Normalizes usage data from OpenAI API responses.
1211
+ *
1212
+ * Handles both API shapes:
1213
+ * - Responses API: uses `input_tokens` / `output_tokens`
1214
+ * - Chat Completions API: uses `prompt_tokens` / `completion_tokens`
1215
+ *
1216
+ * @param usage - Raw usage object from OpenAI API response
1217
+ * @returns Normalized usage metrics, or null if no usage data provided
1218
+ */
1219
+ declare function normalizeOpenAIUsage(usage: unknown): NormalizedUsage | null;
1220
+ /**
1221
+ * Normalizes usage data from Anthropic API responses.
1222
+ *
1223
+ * Anthropic Messages API usage fields:
1224
+ * - input_tokens: Input tokens consumed
1225
+ * - output_tokens: Output tokens generated
1226
+ * - cache_read_input_tokens: Tokens served from cache (optional)
1227
+ * - cache_creation_input_tokens: Tokens used to create cache (optional)
1228
+ *
1229
+ * @param usage - Raw usage object from Anthropic API response
1230
+ * @returns Normalized usage metrics, or null if no usage data provided
1231
+ */
1232
+ declare function normalizeAnthropicUsage(usage: unknown): NormalizedUsage | null;
1233
+ /**
1234
+ * Normalizes usage data from Google Gemini API responses.
1235
+ *
1236
+ * Gemini GenerateContent usage_metadata fields:
1237
+ * - promptTokenCount: Input tokens
1238
+ * - candidatesTokenCount: Output tokens
1239
+ * - totalTokenCount: Total tokens
1240
+ * - cachedContentTokenCount: Cached tokens (optional)
1241
+ *
1242
+ * @param usageMetadata - Raw usage_metadata from Gemini API response
1243
+ * @returns Normalized usage metrics, or null if no usage data provided
1244
+ */
1245
+ declare function normalizeGeminiUsage(usageMetadata: unknown): NormalizedUsage | null;
1246
+ /**
1247
+ * Normalizes usage data from any supported LLM provider.
1248
+ *
1249
+ * @param usage - Raw usage object from API response
1250
+ * @param provider - The provider the usage came from (default: "openai")
1251
+ * @returns Normalized usage metrics, or null if no usage data provided
1252
+ *
1253
+ * @example
1254
+ * ```typescript
1255
+ * // OpenAI
1256
+ * const response = await openai.chat.completions.create({ ... });
1257
+ * const normalized = normalizeUsage(response.usage, "openai");
1258
+ *
1259
+ * // Anthropic
1260
+ * const response = await anthropic.messages.create({ ... });
1261
+ * const normalized = normalizeUsage(response.usage, "anthropic");
1262
+ *
1263
+ * // Gemini
1264
+ * const response = await model.generateContent({ ... });
1265
+ * const normalized = normalizeUsage(response.usageMetadata, "gemini");
1266
+ * ```
1267
+ */
1268
+ declare function normalizeUsage(usage: unknown, provider?: "openai" | "anthropic" | "gemini"): NormalizedUsage | null;
1269
+ /**
1270
+ * Creates an empty/zero usage object
1271
+ */
1272
+ declare function emptyUsage(): NormalizedUsage;
1273
+ /**
1274
+ * Merges two usage objects (useful for accumulating streaming deltas)
1275
+ */
1276
+ declare function mergeUsage(a: NormalizedUsage, b: NormalizedUsage): NormalizedUsage;
1277
+
1278
+ /**
1279
+ * Error thrown when a request exceeds the configured budget
1280
+ */
1281
+ declare class BudgetExceededError extends Error {
1282
+ readonly estimatedInputTokens: number;
1283
+ readonly maxInputTokens: number;
1284
+ readonly model: string;
1285
+ constructor(info: BudgetExceededInfo);
1286
+ }
1287
+ /**
1288
+ * Counts input tokens using OpenAI's input token counting endpoint.
1289
+ *
1290
+ * Uses POST /v1/responses/input_tokens to get exact token counts
1291
+ * before making the actual API call.
1292
+ *
1293
+ * @param client - OpenAI client instance
1294
+ * @param model - Model to count tokens for
1295
+ * @param input - The input to count tokens for
1296
+ * @returns The estimated input token count
1297
+ *
1298
+ * @example
1299
+ * ```ts
1300
+ * const tokens = await countInputTokens(client, "gpt-4.1-mini", "Hello world");
1301
+ * console.log(`This prompt will use ${tokens} input tokens`);
1302
+ * ```
1303
+ */
1304
+ declare function countInputTokens(client: OpenAI, model: string, input: unknown): Promise<number>;
1305
+ /**
1306
+ * Creates a budget-enforced wrapper around the OpenAI client.
1307
+ *
1308
+ * This wrapper checks input token counts before making API calls and
1309
+ * can throw, warn, or truncate based on your configuration.
1310
+ *
1311
+ * @param client - OpenAI client instance (can be metered or not)
1312
+ * @param config - Budget configuration
1313
+ * @returns The client with budget enforcement
1314
+ *
1315
+ * @example
1316
+ * ```ts
1317
+ * import OpenAI from "openai";
1318
+ * import { withBudgetGuardrails } from "openai-meter";
1319
+ *
1320
+ * const client = new OpenAI();
1321
+ * const budgeted = withBudgetGuardrails(client, {
1322
+ * maxInputTokens: 4000,
1323
+ * onExceeded: "throw",
1324
+ * });
1325
+ *
1326
+ * // This will throw if input exceeds 4000 tokens
1327
+ * await budgeted.responses.create({
1328
+ * model: "gpt-4.1",
1329
+ * input: veryLongPrompt,
1330
+ * });
1331
+ * ```
1332
+ */
1333
+ declare function withBudgetGuardrails<T extends OpenAI>(client: T, config: BudgetConfig): T;
1334
+ /**
1335
+ * Convenience function to create a fully metered and budgeted client
1336
+ */
1337
+ declare function createBudgetedMeteredClient(client: MeteredOpenAI, budgetConfig: BudgetConfig): MeteredOpenAI;
1338
+
1339
+ /**
1340
+ * A simple console emitter for development/debugging
1341
+ */
1342
+ declare function createConsoleEmitter(options?: {
1343
+ /** Log level: "info" logs all events, "warn" logs only errors */
1344
+ level?: "info" | "warn";
1345
+ /** Whether to pretty-print the output */
1346
+ pretty?: boolean;
1347
+ }): MetricEmitter;
1348
+ /**
1349
+ * Creates an emitter that batches metrics and flushes periodically
1350
+ */
1351
+ declare function createBatchEmitter(flush: (events: MetricEvent[]) => void | Promise<void>, options?: {
1352
+ /** Maximum batch size before auto-flush */
1353
+ maxBatchSize?: number;
1354
+ /** Maximum time (ms) to wait before flushing */
1355
+ flushInterval?: number;
1356
+ }): MetricEmitter & {
1357
+ flush: () => Promise<void>;
1358
+ stop: () => void;
1359
+ };
1360
+ /**
1361
+ * Creates an emitter that writes to multiple destinations
1362
+ */
1363
+ declare function createMultiEmitter(emitters: MetricEmitter[]): MetricEmitter;
1364
+ /**
1365
+ * Creates an emitter that filters events before passing to another emitter
1366
+ */
1367
+ declare function createFilteredEmitter(emitter: MetricEmitter, filter: (event: MetricEvent) => boolean): MetricEmitter;
1368
+ /**
1369
+ * Creates an emitter that transforms events before passing to another emitter
1370
+ */
1371
+ declare function createTransformEmitter<T>(emitter: (transformed: T) => void | Promise<void>, transform: (event: MetricEvent) => T): MetricEmitter;
1372
+ /**
1373
+ * Creates a no-op emitter (useful for testing or disabling metrics)
1374
+ */
1375
+ declare function createNoopEmitter(): MetricEmitter;
1376
+ /**
1377
+ * Helper to collect metrics in memory (useful for testing)
1378
+ */
1379
+ declare function createMemoryEmitter(): MetricEmitter & {
1380
+ events: MetricEvent[];
1381
+ clear: () => void;
1382
+ };
1383
+ /**
1384
+ * Options for the JSON file emitter
1385
+ */
1386
+ interface JsonFileEmitterOptions {
1387
+ /** File path to write metrics to */
1388
+ filePath: string;
1389
+ /** Format: "jsonl" for JSON Lines (one event per line), "json" for array */
1390
+ format?: "jsonl" | "json";
1391
+ /** Whether to use async writes (default: true for better performance) */
1392
+ async?: boolean;
1393
+ /** Whether to pretty-print JSON (default: false) */
1394
+ pretty?: boolean;
1395
+ }
1396
+ /**
1397
+ * Creates an emitter that writes metrics to a local JSON/JSONL file.
1398
+ *
1399
+ * JSONL format (default) is recommended - one JSON object per line:
1400
+ * - Efficient for appending
1401
+ * - Easy to stream/parse line by line
1402
+ * - Works well with tools like jq
1403
+ *
1404
+ * @example
1405
+ * ```typescript
1406
+ * // JSONL format (recommended)
1407
+ * const emitter = createJsonFileEmitter({
1408
+ * filePath: "./metrics.jsonl",
1409
+ * });
1410
+ *
1411
+ * // JSON array format
1412
+ * const emitter = createJsonFileEmitter({
1413
+ * filePath: "./metrics.json",
1414
+ * format: "json",
1415
+ * pretty: true,
1416
+ * });
1417
+ *
1418
+ * instrument({ emitMetric: emitter });
1419
+ * ```
1420
+ */
1421
+ declare function createJsonFileEmitter(options: JsonFileEmitterOptions): MetricEmitter & {
1422
+ flush: () => Promise<void>;
1423
+ };
1424
+
1425
+ /**
1426
+ * File-based metric logging for local storage and analysis.
1427
+ *
1428
+ * Writes raw metric data to JSONL files organized by date and session,
1429
+ * enabling offline analysis, debugging, and compliance auditing.
1430
+ */
1431
+
1432
+ /**
1433
+ * Options for the MetricFileLogger
1434
+ */
1435
+ interface MetricFileLoggerOptions {
1436
+ /** Base directory for log files. Default: ./meter_logs or METER_LOG_DIR env var */
1437
+ logDir?: string;
1438
+ /** Whether to use async writes (default: true for better performance) */
1439
+ async?: boolean;
1440
+ }
1441
+ /**
1442
+ * Writes raw metric data to local JSONL files for analysis.
1443
+ *
1444
+ * Files are organized by date and session:
1445
+ * meter_logs/
1446
+ * 2024-01-15/
1447
+ * session_abc123.jsonl
1448
+ * session_def456.jsonl
1449
+ * 2024-01-16/
1450
+ * ...
1451
+ *
1452
+ * Each line in the JSONL file is a complete JSON object representing
1453
+ * one metric event (LLM request, TTS synthesis, STT transcription).
1454
+ *
1455
+ * @example
1456
+ * ```typescript
1457
+ * import { MetricFileLogger } from "aden";
1458
+ *
1459
+ * const logger = new MetricFileLogger({ logDir: "./my_logs" });
1460
+ * logger.writeLLMEvent({
1461
+ * sessionId: "session_123",
1462
+ * inputTokens: 100,
1463
+ * outputTokens: 50,
1464
+ * model: "gpt-4o-mini",
1465
+ * });
1466
+ * ```
1467
+ */
1468
+ declare class MetricFileLogger {
1469
+ private logDir;
1470
+ private useAsync;
1471
+ constructor(options?: MetricFileLoggerOptions);
1472
+ /**
1473
+ * Create the log directory if it doesn't exist.
1474
+ */
1475
+ private ensureDirExists;
1476
+ /**
1477
+ * Get the log file path for a session.
1478
+ */
1479
+ private getSessionFile;
1480
+ /**
1481
+ * Write a metric event to the session's log file.
1482
+ */
1483
+ writeEvent(sessionId: string, eventType: string, data: Record<string, unknown>): Promise<void>;
1484
+ /**
1485
+ * Write a MetricEvent to the log file.
1486
+ */
1487
+ writeMetricEvent(event: MetricEvent): Promise<void>;
1488
+ /**
1489
+ * Write an LLM metric event.
1490
+ */
1491
+ writeLLMEvent(options: {
1492
+ sessionId: string;
1493
+ inputTokens: number;
1494
+ outputTokens: number;
1495
+ model: string;
1496
+ latencyMs?: number;
1497
+ metadata?: Record<string, unknown>;
1498
+ }): Promise<void>;
1499
+ /**
1500
+ * Write a TTS metric event.
1501
+ */
1502
+ writeTTSEvent(options: {
1503
+ sessionId: string;
1504
+ characters: number;
1505
+ model: string;
1506
+ metadata?: Record<string, unknown>;
1507
+ }): Promise<void>;
1508
+ /**
1509
+ * Write an STT metric event.
1510
+ */
1511
+ writeSTTEvent(options: {
1512
+ sessionId: string;
1513
+ audioSeconds: number;
1514
+ model: string;
1515
+ metadata?: Record<string, unknown>;
1516
+ }): Promise<void>;
1517
+ /**
1518
+ * Write session start event.
1519
+ */
1520
+ writeSessionStart(options: {
1521
+ sessionId: string;
1522
+ roomName: string;
1523
+ metadata?: Record<string, unknown>;
1524
+ }): Promise<void>;
1525
+ /**
1526
+ * Write session end event with final summary.
1527
+ */
1528
+ writeSessionEnd(options: {
1529
+ sessionId: string;
1530
+ summary: Record<string, unknown>;
1531
+ }): Promise<void>;
1532
+ }
1533
+ /**
1534
+ * Create a file-based metric emitter.
1535
+ *
1536
+ * This creates a MetricEmitter that writes to session-organized JSONL files.
1537
+ *
1538
+ * @example
1539
+ * ```typescript
1540
+ * import { instrument, createFileEmitter } from "aden";
1541
+ *
1542
+ * instrument({
1543
+ * emitMetric: createFileEmitter({ logDir: "./my_logs" }),
1544
+ * });
1545
+ * ```
1546
+ *
1547
+ * @param options - Options for the file logger
1548
+ * @returns A MetricEmitter function
1549
+ */
1550
+ declare function createFileEmitter(options?: MetricFileLoggerOptions): MetricEmitter;
1551
+
1552
+ /**
1553
+ * HTTP transport for sending metrics to a central API endpoint.
1554
+ *
1555
+ * This is the recommended approach for production - clients send metrics
1556
+ * to your API, which handles storage, aggregation, and multi-tenancy.
1557
+ */
1558
+
1559
+ /**
1560
+ * Callback when metrics are dropped due to queue overflow
1561
+ */
1562
+ type QueueOverflowHandler = (droppedCount: number) => void;
1563
+ /**
1564
+ * Callback when a batch send fails after all retries
1565
+ */
1566
+ type SendErrorHandler = (error: Error, batch: MetricEvent[], stats: TransportStats) => void;
1567
+ /**
1568
+ * Transport statistics for observability
1569
+ */
1570
+ interface TransportStats {
1571
+ /** Total metrics successfully sent */
1572
+ sent: number;
1573
+ /** Total metrics dropped due to queue overflow */
1574
+ dropped: number;
1575
+ /** Total metrics that failed to send after retries */
1576
+ errors: number;
1577
+ /** Current queue size */
1578
+ queued: number;
1579
+ }
1580
+ /**
1581
+ * Options for the HTTP transport
1582
+ */
1583
+ interface HttpTransportOptions {
1584
+ /** API endpoint URL */
1585
+ apiUrl: string;
1586
+ /** API key for authentication (sent as Bearer token) */
1587
+ apiKey?: string;
1588
+ /** Number of events to batch before sending (default: 50) */
1589
+ batchSize?: number;
1590
+ /** Milliseconds between automatic flushes (default: 5000) */
1591
+ flushInterval?: number;
1592
+ /** Request timeout in milliseconds (default: 10000) */
1593
+ timeout?: number;
1594
+ /** Maximum retry attempts (default: 3) */
1595
+ maxRetries?: number;
1596
+ /** Maximum queue size before dropping events (default: 10000) */
1597
+ maxQueueSize?: number;
1598
+ /** Additional headers to send with requests */
1599
+ headers?: Record<string, string>;
1600
+ /** Callback when events are dropped due to queue overflow */
1601
+ onQueueOverflow?: QueueOverflowHandler;
1602
+ /** Callback when a batch fails to send */
1603
+ onSendError?: SendErrorHandler;
1604
+ }
1605
+ /**
1606
+ * HTTP transport that batches and sends metrics to an API endpoint.
1607
+ *
1608
+ * Features:
1609
+ * - Batched sending for efficiency
1610
+ * - Automatic periodic flushing
1611
+ * - Retry with exponential backoff
1612
+ * - Queue overflow protection
1613
+ * - Observability via stats
1614
+ *
1615
+ * @example
1616
+ * ```typescript
1617
+ * const transport = createHttpTransport({
1618
+ * apiUrl: "https://api.example.com/v1/metrics",
1619
+ * apiKey: "your-api-key",
1620
+ * });
1621
+ *
1622
+ * const metered = makeMeteredOpenAI(client, {
1623
+ * emitMetric: transport,
1624
+ * });
1625
+ *
1626
+ * // On shutdown
1627
+ * await transport.flush();
1628
+ * transport.stop();
1629
+ * ```
1630
+ */
1631
+ declare class HttpTransport {
1632
+ private readonly apiUrl;
1633
+ private readonly apiKey?;
1634
+ private readonly batchSize;
1635
+ private readonly flushInterval;
1636
+ private readonly timeout;
1637
+ private readonly maxRetries;
1638
+ private readonly maxQueueSize;
1639
+ private readonly extraHeaders;
1640
+ private readonly onQueueOverflow?;
1641
+ private readonly onSendError?;
1642
+ private queue;
1643
+ private flushTimer;
1644
+ private isStopped;
1645
+ private sentCount;
1646
+ private droppedCount;
1647
+ private errorCount;
1648
+ constructor(options: HttpTransportOptions);
1649
+ private startFlushTimer;
1650
+ /**
1651
+ * Add an event to the send queue.
1652
+ * This is the MetricEmitter interface.
1653
+ */
1654
+ emit: MetricEmitter;
1655
+ /**
1656
+ * Flush pending events (async version)
1657
+ */
1658
+ flushAsync(): Promise<void>;
1659
+ /**
1660
+ * Flush pending events (sync-friendly, returns promise)
1661
+ */
1662
+ flush(): Promise<void>;
1663
+ /**
1664
+ * Flush all pending events (may require multiple batches)
1665
+ */
1666
+ flushAll(): Promise<void>;
1667
+ private sendBatch;
1668
+ private sleep;
1669
+ /**
1670
+ * Get transport statistics
1671
+ */
1672
+ get stats(): TransportStats;
1673
+ /**
1674
+ * Stop the transport and flush remaining events
1675
+ */
1676
+ stop(): Promise<void>;
1677
+ /**
1678
+ * Check if the transport is stopped
1679
+ */
1680
+ get stopped(): boolean;
1681
+ }
1682
+ /**
1683
+ * Create an HTTP transport for sending metrics to an API.
1684
+ *
1685
+ * @example
1686
+ * ```typescript
1687
+ * // Using options
1688
+ * const transport = createHttpTransport({
1689
+ * apiUrl: "https://api.example.com/v1/metrics",
1690
+ * apiKey: "your-api-key",
1691
+ * batchSize: 100,
1692
+ * });
1693
+ *
1694
+ * // Using environment variables
1695
+ * // Set METER_API_URL and optionally METER_API_KEY
1696
+ * const transport = createHttpTransport();
1697
+ * ```
1698
+ */
1699
+ declare function createHttpTransport(options?: Partial<HttpTransportOptions>): HttpTransport;
1700
+ /**
1701
+ * Create an HTTP transport emitter function.
1702
+ *
1703
+ * This is a convenience function that returns just the emit function,
1704
+ * suitable for use directly as a MetricEmitter.
1705
+ *
1706
+ * Note: The returned emitter cannot be stopped or flushed. For production use,
1707
+ * prefer createHttpTransport() which gives you access to stop() and flush().
1708
+ *
1709
+ * @example
1710
+ * ```typescript
1711
+ * const metered = makeMeteredOpenAI(client, {
1712
+ * emitMetric: createHttpEmitter({
1713
+ * apiUrl: "https://api.example.com/v1/metrics",
1714
+ * }),
1715
+ * });
1716
+ * ```
1717
+ */
1718
+ declare function createHttpEmitter(options?: Partial<HttpTransportOptions>): MetricEmitter;
1719
+
1720
+ /**
1721
+ * Time-series data point for trend analysis
1722
+ */
1723
+ interface TimeSeriesPoint {
1724
+ timestamp: Date;
1725
+ value: number;
1726
+ }
1727
+ /**
1728
+ * Comprehensive analytics for CTO-level reporting
1729
+ */
1730
+ interface AnalyticsReport {
1731
+ costs: {
1732
+ total: number;
1733
+ byModel: Record<string, number>;
1734
+ byHour: TimeSeriesPoint[];
1735
+ projectedMonthly: number;
1736
+ cacheSavings: number;
1737
+ avgCostPerRequest: number;
1738
+ avgCostPer1kTokens: number;
1739
+ };
1740
+ performance: {
1741
+ avgLatency: number;
1742
+ p50Latency: number;
1743
+ p95Latency: number;
1744
+ p99Latency: number;
1745
+ requestsPerMinute: number;
1746
+ tokensPerSecond: number;
1747
+ };
1748
+ efficiency: {
1749
+ cacheHitRate: number;
1750
+ avgInputTokens: number;
1751
+ avgOutputTokens: number;
1752
+ inputOutputRatio: number;
1753
+ reasoningOverhead: number;
1754
+ };
1755
+ reliability: {
1756
+ successRate: number;
1757
+ errorRate: number;
1758
+ errorsByType: Record<string, number>;
1759
+ avgRetriesPerRequest: number;
1760
+ };
1761
+ usage: {
1762
+ totalRequests: number;
1763
+ totalTokens: number;
1764
+ peakRequestsPerMinute: number;
1765
+ peakHour: string;
1766
+ modelDistribution: Record<string, number>;
1767
+ };
1768
+ }
1769
+ /**
1770
+ * Analytics engine for collecting and analyzing metrics
1771
+ */
1772
+ declare class AnalyticsEngine {
1773
+ private events;
1774
+ private pricing;
1775
+ private startTime;
1776
+ constructor(customPricing?: Record<string, {
1777
+ input: number;
1778
+ output: number;
1779
+ cached: number;
1780
+ }>);
1781
+ /**
1782
+ * Record a metric event
1783
+ */
1784
+ record(event: MetricEvent): void;
1785
+ /**
1786
+ * Get the pricing for a model (with fallback to base model)
1787
+ */
1788
+ private getModelPricing;
1789
+ /**
1790
+ * Calculate cost for a single event
1791
+ */
1792
+ private calculateEventCost;
1793
+ /**
1794
+ * Calculate percentile from sorted array
1795
+ */
1796
+ private percentile;
1797
+ /**
1798
+ * Generate comprehensive analytics report
1799
+ */
1800
+ generateReport(): AnalyticsReport;
1801
+ /**
1802
+ * Classify error type
1803
+ */
1804
+ private classifyError;
1805
+ /**
1806
+ * Format report as markdown for CTO presentation
1807
+ */
1808
+ formatMarkdown(report: AnalyticsReport): string;
1809
+ /**
1810
+ * Generate actionable recommendations based on metrics
1811
+ */
1812
+ private generateRecommendations;
1813
+ /**
1814
+ * Reset analytics data
1815
+ */
1816
+ reset(): void;
1817
+ /**
1818
+ * Get raw events
1819
+ */
1820
+ getEvents(): MetricEvent[];
1821
+ }
1822
+ /**
1823
+ * Create an emitter that feeds into the analytics engine
1824
+ */
1825
+ declare function createAnalyticsEmitter(engine: AnalyticsEngine): (event: MetricEvent) => void;
1826
+
1827
+ /**
1828
+ * JSON Schema types for comprehensive analytics reporting
1829
+ */
1830
+ /**
1831
+ * Individual request/event within a pattern
1832
+ */
1833
+ interface PatternEvent {
1834
+ id: string;
1835
+ timestamp: string;
1836
+ model: string;
1837
+ latency_ms: number;
1838
+ input_tokens: number;
1839
+ output_tokens: number;
1840
+ cached_tokens: number;
1841
+ reasoning_tokens: number;
1842
+ cost: number;
1843
+ error?: string;
1844
+ metadata?: Record<string, unknown>;
1845
+ }
1846
+ /**
1847
+ * Multi-turn conversation pattern
1848
+ */
1849
+ interface ConversationPattern {
1850
+ type: "conversation";
1851
+ conversation_id: string;
1852
+ user_id?: string;
1853
+ started_at: string;
1854
+ ended_at?: string;
1855
+ turns: Array<{
1856
+ turn_number: number;
1857
+ role: "user" | "assistant";
1858
+ event: PatternEvent;
1859
+ context_tokens: number;
1860
+ context_utilization: number;
1861
+ cumulative_cost: number;
1862
+ cache_efficiency: number;
1863
+ }>;
1864
+ summary: {
1865
+ total_turns: number;
1866
+ total_tokens: number;
1867
+ total_cost: number;
1868
+ avg_latency: number;
1869
+ avg_tokens_per_turn: number;
1870
+ context_growth_rate: number;
1871
+ cache_savings: number;
1872
+ };
1873
+ }
1874
+ /**
1875
+ * Tenant in multi-tenant pattern
1876
+ */
1877
+ interface TenantUsage {
1878
+ tenant_id: string;
1879
+ tenant_name?: string;
1880
+ tier: "free" | "pro" | "enterprise" | string;
1881
+ users: Array<{
1882
+ user_id: string;
1883
+ request_count: number;
1884
+ total_tokens: number;
1885
+ total_cost: number;
1886
+ }>;
1887
+ events: PatternEvent[];
1888
+ summary: {
1889
+ total_requests: number;
1890
+ total_tokens: number;
1891
+ total_cost: number;
1892
+ quota_used: number;
1893
+ quota_limit: number;
1894
+ avg_cost_per_request: number;
1895
+ peak_requests_per_minute: number;
1896
+ };
1897
+ }
1898
+ /**
1899
+ * Multi-tenant agent fleet pattern
1900
+ */
1901
+ interface MultiTenantPattern {
1902
+ type: "multi_tenant";
1903
+ period_start: string;
1904
+ period_end: string;
1905
+ tenants: TenantUsage[];
1906
+ summary: {
1907
+ total_tenants: number;
1908
+ total_requests: number;
1909
+ total_tokens: number;
1910
+ total_revenue?: number;
1911
+ total_cost: number;
1912
+ gross_margin?: number;
1913
+ top_tenant_by_usage: string;
1914
+ top_tenant_by_cost: string;
1915
+ tier_distribution: Record<string, number>;
1916
+ };
1917
+ }
1918
+ /**
1919
+ * Tool call within an agent
1920
+ */
1921
+ interface ToolCall {
1922
+ tool_name: string;
1923
+ tool_id: string;
1924
+ input: Record<string, unknown>;
1925
+ output?: unknown;
1926
+ duration_ms: number;
1927
+ success: boolean;
1928
+ error?: string;
1929
+ }
1930
+ /**
1931
+ * Agent execution within cascaded pattern
1932
+ */
1933
+ interface AgentExecution {
1934
+ agent_id: string;
1935
+ agent_name: string;
1936
+ agent_type: "orchestrator" | "worker" | "specialist";
1937
+ parent_agent_id?: string;
1938
+ depth: number;
1939
+ started_at: string;
1940
+ ended_at: string;
1941
+ model: string;
1942
+ events: PatternEvent[];
1943
+ tool_calls: ToolCall[];
1944
+ subagents: AgentExecution[];
1945
+ summary: {
1946
+ total_llm_calls: number;
1947
+ total_tool_calls: number;
1948
+ total_tokens: number;
1949
+ total_cost: number;
1950
+ total_duration_ms: number;
1951
+ success: boolean;
1952
+ error?: string;
1953
+ };
1954
+ }
1955
+ /**
1956
+ * Multi-agent cascaded pattern
1957
+ */
1958
+ interface CascadedAgentPattern {
1959
+ type: "cascaded_agents";
1960
+ task_id: string;
1961
+ task_description?: string;
1962
+ started_at: string;
1963
+ ended_at: string;
1964
+ root_agent: AgentExecution;
1965
+ summary: {
1966
+ total_agents: number;
1967
+ max_depth: number;
1968
+ total_llm_calls: number;
1969
+ total_tool_calls: number;
1970
+ total_tokens: number;
1971
+ total_cost: number;
1972
+ total_duration_ms: number;
1973
+ parallelism_factor: number;
1974
+ cost_by_agent_type: Record<string, number>;
1975
+ tokens_by_depth: Record<number, number>;
1976
+ tool_usage: Record<string, {
1977
+ count: number;
1978
+ avg_duration_ms: number;
1979
+ success_rate: number;
1980
+ }>;
1981
+ };
1982
+ }
1983
+ /**
1984
+ * Complete analytics report with all patterns
1985
+ */
1986
+ interface AnalyticsJSON {
1987
+ generated_at: string;
1988
+ period: {
1989
+ start: string;
1990
+ end: string;
1991
+ duration_hours: number;
1992
+ };
1993
+ summary: {
1994
+ total_requests: number;
1995
+ total_tokens: number;
1996
+ total_cost: number;
1997
+ projected_monthly_cost: number;
1998
+ cache_savings: number;
1999
+ success_rate: number;
2000
+ avg_latency_ms: number;
2001
+ p50_latency_ms: number;
2002
+ p95_latency_ms: number;
2003
+ p99_latency_ms: number;
2004
+ };
2005
+ costs: {
2006
+ by_model: Record<string, {
2007
+ requests: number;
2008
+ tokens: number;
2009
+ cost: number;
2010
+ }>;
2011
+ by_hour: Array<{
2012
+ hour: string;
2013
+ cost: number;
2014
+ requests: number;
2015
+ }>;
2016
+ by_pattern_type: Record<string, number>;
2017
+ };
2018
+ patterns: {
2019
+ conversations: ConversationPattern[];
2020
+ multi_tenant: MultiTenantPattern | null;
2021
+ cascaded_agents: CascadedAgentPattern[];
2022
+ };
2023
+ recommendations: Array<{
2024
+ category: "cost" | "performance" | "efficiency" | "reliability";
2025
+ severity: "info" | "warning" | "critical";
2026
+ title: string;
2027
+ description: string;
2028
+ potential_savings?: number;
2029
+ action: string;
2030
+ }>;
2031
+ }
2032
+
2033
+ /**
2034
+ * Report Builder
2035
+ * Combines real analytics data with pattern simulations to generate comprehensive reports
2036
+ */
2037
+
2038
+ interface ReportBuilderOptions {
2039
+ /**
2040
+ * Include simulated conversation patterns
2041
+ */
2042
+ simulateConversations?: number;
2043
+ /**
2044
+ * Include simulated multi-tenant data
2045
+ */
2046
+ simulateMultiTenant?: {
2047
+ tenants: number;
2048
+ requestsPerTenant?: {
2049
+ min: number;
2050
+ max: number;
2051
+ };
2052
+ };
2053
+ /**
2054
+ * Include simulated cascaded agent executions
2055
+ */
2056
+ simulateCascadedAgents?: number;
2057
+ /**
2058
+ * Custom model pricing
2059
+ */
2060
+ pricing?: Record<string, {
2061
+ input: number;
2062
+ output: number;
2063
+ cached: number;
2064
+ }>;
2065
+ }
2066
+ /**
2067
+ * Report Builder for generating comprehensive analytics reports
2068
+ */
2069
+ declare class ReportBuilder {
2070
+ private events;
2071
+ private conversations;
2072
+ private multiTenant;
2073
+ private cascadedAgents;
2074
+ private startTime;
2075
+ private pricing;
2076
+ constructor(options?: {
2077
+ pricing?: Record<string, {
2078
+ input: number;
2079
+ output: number;
2080
+ cached: number;
2081
+ }>;
2082
+ });
2083
+ /**
2084
+ * Record a metric event from real API calls
2085
+ */
2086
+ recordEvent(event: MetricEvent): void;
2087
+ /**
2088
+ * Add a conversation pattern (real or simulated)
2089
+ */
2090
+ addConversation(conversation: ConversationPattern): void;
2091
+ /**
2092
+ * Set multi-tenant data (real or simulated)
2093
+ */
2094
+ setMultiTenant(data: MultiTenantPattern): void;
2095
+ /**
2096
+ * Add a cascaded agent execution (real or simulated)
2097
+ */
2098
+ addCascadedAgent(execution: CascadedAgentPattern): void;
2099
+ /**
2100
+ * Simulate and add patterns based on options
2101
+ */
2102
+ addSimulatedPatterns(options: ReportBuilderOptions): void;
2103
+ /**
2104
+ * Calculate cost for MetricEvent
2105
+ */
2106
+ private calculateEventCost;
2107
+ /**
2108
+ * Calculate cost for UnifiedEvent
2109
+ */
2110
+ private calculateUnifiedEventCost;
2111
+ /**
2112
+ * Generate the complete analytics JSON
2113
+ */
2114
+ buildJSON(): AnalyticsJSON;
2115
+ /**
2116
+ * Generate recommendations based on metrics
2117
+ */
2118
+ private generateRecommendations;
2119
+ /**
2120
+ * Build and return HTML report
2121
+ */
2122
+ buildHTML(): string;
2123
+ /**
2124
+ * Reset the builder
2125
+ */
2126
+ reset(): void;
2127
+ }
2128
+ /**
2129
+ * Create a metric emitter that feeds into the report builder
2130
+ */
2131
+ declare function createReportEmitter(builder: ReportBuilder): (event: MetricEvent) => void;
2132
+
2133
+ /**
2134
+ * HTML Report Generator
2135
+ * Creates interactive HTML reports with charts for CTO-level analytics
2136
+ */
2137
+
2138
+ /**
2139
+ * Generate complete HTML report from analytics JSON
2140
+ */
2141
+ declare function generateHTMLReport(data: AnalyticsJSON): string;
2142
+
2143
+ /**
2144
+ * Pattern Simulator for generating realistic analytics data
2145
+ * Simulates multi-turn conversations, multi-tenant fleets, and cascaded agents
2146
+ */
2147
+
2148
+ /**
2149
+ * Simulate a multi-turn conversation
2150
+ */
2151
+ declare function simulateConversation(options: {
2152
+ turns?: number;
2153
+ model?: string;
2154
+ userId?: string;
2155
+ enableCaching?: boolean;
2156
+ }): ConversationPattern;
2157
+ /**
2158
+ * Simulate multi-tenant usage
2159
+ */
2160
+ declare function simulateMultiTenant(options: {
2161
+ tenantCount?: number;
2162
+ requestsPerTenant?: {
2163
+ min: number;
2164
+ max: number;
2165
+ };
2166
+ tiers?: Array<{
2167
+ name: string;
2168
+ quota: number;
2169
+ weight: number;
2170
+ }>;
2171
+ }): MultiTenantPattern;
2172
+ /**
2173
+ * Simulate cascaded multi-agent execution
2174
+ */
2175
+ declare function simulateCascadedAgents(options: {
2176
+ taskDescription?: string;
2177
+ maxDepth?: number;
2178
+ avgSubagentsPerLevel?: number;
2179
+ toolsAvailable?: string[];
2180
+ }): CascadedAgentPattern;
2181
+
2182
+ /**
2183
+ * Logging configuration for the aden SDK.
2184
+ *
2185
+ * Provides a simple logging abstraction that can be configured via
2186
+ * environment variables or programmatically.
2187
+ */
2188
+ /**
2189
+ * Log levels in order of severity
2190
+ */
2191
+ type LogLevel = "debug" | "info" | "warn" | "error" | "silent";
2192
+ /**
2193
+ * Logging configuration options
2194
+ */
2195
+ interface LoggingConfig {
2196
+ /** Global log level for the SDK. Default: "info" or ADEN_LOG_LEVEL env var */
2197
+ level?: LogLevel;
2198
+ /** Log level for metrics-specific logs. Default: same as level */
2199
+ metricsLevel?: LogLevel;
2200
+ /** Custom log handler. Default: console */
2201
+ handler?: LogHandler;
2202
+ }
2203
+ /**
2204
+ * Log handler interface
2205
+ */
2206
+ interface LogHandler {
2207
+ debug: (message: string, ...args: unknown[]) => void;
2208
+ info: (message: string, ...args: unknown[]) => void;
2209
+ warn: (message: string, ...args: unknown[]) => void;
2210
+ error: (message: string, ...args: unknown[]) => void;
2211
+ }
2212
+ /**
2213
+ * Configure logging for the aden SDK.
2214
+ *
2215
+ * @example
2216
+ * ```typescript
2217
+ * import { configureLogging } from "aden";
2218
+ *
2219
+ * // Set log level to debug
2220
+ * configureLogging({ level: "debug" });
2221
+ *
2222
+ * // Quiet metrics logs but keep other logs
2223
+ * configureLogging({ level: "info", metricsLevel: "warn" });
2224
+ *
2225
+ * // Use a custom log handler
2226
+ * configureLogging({
2227
+ * handler: {
2228
+ * debug: (msg, ...args) => myLogger.debug(msg, ...args),
2229
+ * info: (msg, ...args) => myLogger.info(msg, ...args),
2230
+ * warn: (msg, ...args) => myLogger.warn(msg, ...args),
2231
+ * error: (msg, ...args) => myLogger.error(msg, ...args),
2232
+ * },
2233
+ * });
2234
+ * ```
2235
+ */
2236
+ declare function configureLogging(config: LoggingConfig): void;
2237
+ /**
2238
+ * Get the current logging configuration
2239
+ */
2240
+ declare function getLoggingConfig(): Readonly<Required<LoggingConfig>>;
2241
+ /**
2242
+ * Reset logging configuration to defaults
2243
+ */
2244
+ declare function resetLoggingConfig(): void;
2245
+ /**
2246
+ * Logger for the aden SDK
2247
+ */
2248
+ declare const logger: {
2249
+ debug(message: string, ...args: unknown[]): void;
2250
+ info(message: string, ...args: unknown[]): void;
2251
+ warn(message: string, ...args: unknown[]): void;
2252
+ error(message: string, ...args: unknown[]): void;
2253
+ };
2254
+ /**
2255
+ * Logger for metrics-specific logs
2256
+ */
2257
+ declare const metricsLogger: {
2258
+ debug(message: string, ...args: unknown[]): void;
2259
+ info(message: string, ...args: unknown[]): void;
2260
+ warn(message: string, ...args: unknown[]): void;
2261
+ error(message: string, ...args: unknown[]): void;
2262
+ };
2263
+
2264
+ /**
2265
+ * Control Agent - Bidirectional communication with control server
2266
+ *
2267
+ * Emits: metrics, control events, heartbeat
2268
+ * Receives: control policies (budgets, throttle, block, degrade)
2269
+ *
2270
+ * Uses WebSocket for real-time communication with HTTP polling fallback.
2271
+ */
2272
+
2273
+ /**
2274
+ * Control Agent implementation
2275
+ */
2276
+ declare class ControlAgent implements IControlAgent {
2277
+ private options;
2278
+ private ws;
2279
+ private cachedPolicy;
2280
+ private lastPolicyFetch;
2281
+ private pollingTimer;
2282
+ private heartbeatTimer;
2283
+ private reconnectTimer;
2284
+ private connected;
2285
+ private reconnectAttempts;
2286
+ private maxReconnectAttempts;
2287
+ private eventQueue;
2288
+ private maxQueueSize;
2289
+ private requestsSinceLastHeartbeat;
2290
+ private errorsSinceLastHeartbeat;
2291
+ private requestCounts;
2292
+ constructor(options: ControlAgentOptions);
2293
+ /**
2294
+ * Connect to the control server
2295
+ */
2296
+ connect(): Promise<void>;
2297
+ /**
2298
+ * Connect via WebSocket
2299
+ */
2300
+ private connectWebSocket;
2301
+ /**
2302
+ * Schedule WebSocket reconnection
2303
+ */
2304
+ private scheduleReconnect;
2305
+ /**
2306
+ * Handle incoming WebSocket message
2307
+ */
2308
+ private handleMessage;
2309
+ /**
2310
+ * Start HTTP polling for policy updates
2311
+ * Returns a promise that resolves when the first policy fetch completes
2312
+ */
2313
+ private startPolling;
2314
+ /**
2315
+ * Stop HTTP polling
2316
+ */
2317
+ private stopPolling;
2318
+ /**
2319
+ * Fetch policy via HTTP
2320
+ */
2321
+ private fetchPolicy;
2322
+ /**
2323
+ * Start heartbeat timer
2324
+ */
2325
+ private startHeartbeat;
2326
+ /**
2327
+ * Stop heartbeat timer
2328
+ */
2329
+ private stopHeartbeat;
2330
+ /**
2331
+ * Send heartbeat event
2332
+ */
2333
+ private sendHeartbeat;
2334
+ /**
2335
+ * Disconnect from the control server
2336
+ */
2337
+ disconnect(): Promise<void>;
2338
+ /**
2339
+ * Get a control decision for a request
2340
+ */
2341
+ getDecision(request: ControlRequest): Promise<ControlDecision>;
2342
+ /**
2343
+ * Evaluate policy rules against a request
2344
+ * Priority order: block > budget/degrade > throttle > alert > allow
2345
+ * Note: throttle adds delay but doesn't skip other checks
2346
+ */
2347
+ private evaluatePolicy;
2348
+ /**
2349
+ * Check if request matches an alert rule
2350
+ */
2351
+ private matchesAlertRule;
2352
+ /**
2353
+ * Check if request matches a block rule
2354
+ */
2355
+ private matchesBlockRule;
2356
+ /**
2357
+ * Find budgets that apply to the given request based on budget type
2358
+ */
2359
+ private findApplicableBudgets;
2360
+ /**
2361
+ * Check rate limit for a key
2362
+ */
2363
+ private checkRateLimit;
2364
+ /**
2365
+ * Report a metric event to the server
2366
+ */
2367
+ reportMetric(event: MetricEvent): Promise<void>;
2368
+ /**
2369
+ * Estimate cost from a metric event
2370
+ * Uses gpt-4o pricing as default: $2.50/1M input, $10/1M output
2371
+ */
2372
+ private estimateCost;
2373
+ /**
2374
+ * Calculate the sampling rate for server validation based on usage percentage.
2375
+ *
2376
+ * The rate interpolates from samplingBaseRate at threshold to 1.0 at
2377
+ * samplingFullValidationPercent.
2378
+ *
2379
+ * Example with defaults (threshold=80%, base_rate=0.1, full=95%):
2380
+ * - At 80%: 10% of requests validated
2381
+ * - At 87.5%: 55% of requests validated
2382
+ * - At 95%+: 100% of requests validated
2383
+ */
2384
+ private calculateSamplingRate;
2385
+ /**
2386
+ * Determine if we should validate this request with the server.
2387
+ *
2388
+ * Uses adaptive thresholds and probabilistic sampling to minimize
2389
+ * latency impact while maintaining enforcement accuracy.
2390
+ *
2391
+ * Returns true if:
2392
+ * 1. Hybrid enforcement is enabled
2393
+ * 2. Budget usage is at or above the validation threshold
2394
+ * 3. Either:
2395
+ * a. Remaining budget is below adaptiveMinRemainingUsd (always validate)
2396
+ * b. Sampling dice roll succeeds based on current usage level
2397
+ */
2398
+ private shouldValidateWithServer;
2399
+ /**
2400
+ * Check if request exceeds the hard limit (soft limit + max overspend buffer).
2401
+ *
2402
+ * This provides a safety net to prevent runaway spending even under
2403
+ * concurrency race conditions.
2404
+ *
2405
+ * Returns a BLOCK decision if hard limit exceeded, null otherwise.
2406
+ */
2407
+ private checkHardLimit;
2408
+ /**
2409
+ * Validate budget with server synchronously.
2410
+ *
2411
+ * Returns BudgetValidationResponse if successful, null if validation failed.
2412
+ * On failure, caller should fall back to local enforcement based on failOpen.
2413
+ */
2414
+ private validateBudgetWithServer;
2415
+ /**
2416
+ * Convert server validation response to a ControlDecision
2417
+ */
2418
+ private applyServerValidationResult;
2419
+ /**
2420
+ * Evaluate a single budget with hybrid enforcement
2421
+ */
2422
+ private evaluateBudgetWithHybridEnforcement;
2423
+ /**
2424
+ * Report a control event to the server
2425
+ */
2426
+ reportControlEvent(event: Omit<ControlEvent, "event_type" | "timestamp" | "sdk_instance_id">): Promise<void>;
2427
+ /**
2428
+ * Report an error event
2429
+ */
2430
+ reportError(message: string, error?: Error, traceId?: string): Promise<void>;
2431
+ /**
2432
+ * Send an event to the server
2433
+ */
2434
+ private sendEvent;
2435
+ /**
2436
+ * Queue an event for later sending
2437
+ */
2438
+ private queueEvent;
2439
+ /**
2440
+ * Flush queued events
2441
+ */
2442
+ private flushEventQueue;
2443
+ /**
2444
+ * Make HTTP request to server
2445
+ */
2446
+ private httpRequest;
2447
+ /**
2448
+ * Check if connected to server (WebSocket)
2449
+ */
2450
+ isConnected(): boolean;
2451
+ /**
2452
+ * Get current cached policy
2453
+ */
2454
+ getPolicy(): ControlPolicy | null;
2455
+ }
2456
+ /**
2457
+ * Create a control agent
2458
+ */
2459
+ declare function createControlAgent(options: ControlAgentOptions): ControlAgent;
2460
+ /**
2461
+ * Create a metric emitter that sends to the control agent
2462
+ *
2463
+ * This allows the control agent to work alongside other emitters:
2464
+ * ```typescript
2465
+ * const agent = createControlAgent({ ... });
2466
+ *
2467
+ * await instrument({
2468
+ * emitMetric: createMultiEmitter([
2469
+ * createConsoleEmitter({ pretty: true }),
2470
+ * createControlAgentEmitter(agent),
2471
+ * ]),
2472
+ * controlAgent: agent,
2473
+ * });
2474
+ * ```
2475
+ */
2476
+ declare function createControlAgentEmitter(agent: IControlAgent): (event: MetricEvent) => Promise<void>;
2477
+
2478
+ export { type AgentExecution, type AlertEvent, type AlertRule, AnalyticsEngine, type AnalyticsJSON, type AnalyticsReport, type BeforeRequestContext, type BeforeRequestHook, type BeforeRequestResult, type BlockRule, type BudgetConfig, BudgetExceededError, type BudgetExceededInfo, type BudgetRule, type BudgetValidationRequest, type BudgetValidationResponse, type CallRelationship, type CallSite, type CascadedAgentPattern, type ControlAction, ControlAgent, type ControlAgentOptions, type ControlDecision, type ControlEvent, type ControlPolicy, type ControlRequest, type ConversationPattern, DEFAULT_CONTROL_SERVER, type DegradeRule, type HeartbeatEvent, HttpTransport, type HttpTransportOptions, type IControlAgent, type InstrumentationResult, type JsonFileEmitterOptions, type LogHandler, type LogLevel, type LoggingConfig, type MeterContext, type MeterOptions, type MeteredOpenAI, type MetricEmitter, type MetricEvent, MetricFileLogger, type MetricFileLoggerOptions, type MultiTenantPattern, type NormalizedUsage, type PatternEvent, type QueueOverflowHandler, type RateLimitInfo, ReportBuilder, RequestCancelledError, type RequestMetadata, type SDKClasses, type SendErrorHandler, type StreamingEventType, type TenantUsage, type ThrottleRule, type ToolCall, type ToolCallMetric, type TransportStats, configureLogging, countInputTokens, createAnalyticsEmitter, createBatchEmitter, createBudgetedMeteredClient, createConsoleEmitter, createControlAgent, createControlAgentEmitter, createFileEmitter, createFilteredEmitter, createHttpEmitter, createHttpTransport, createJsonFileEmitter, createMemoryEmitter, createMeterContext, createMultiEmitter, createNoopEmitter, createReportEmitter, createTransformEmitter, emptyUsage, enterMeterContext, extractAgentStack, extractCallSite, generateHTMLReport, generateStackFingerprint, getContextMetadata, getControlServerUrl, getCurrentContext, getFullAgentStack, getGenaiOptions, getInstrumentationOptions, getInstrumentedSDKs, getLoggingConfig, hasContext, instrument, instrumentAnthropic, instrumentFetch, instrumentGemini, instrumentGenai, instrumentOpenAI, isAnthropicInstrumented, isFetchInstrumented, isGeminiInstrumented, isGenaiInstrumented, isInstrumented, isMetered, isOpenAIInstrumented, logger, makeMeteredOpenAI, mergeUsage, metricsLogger, normalizeAnthropicUsage, normalizeGeminiUsage, normalizeOpenAIUsage, normalizeUsage, popAgent, pushAgent, resetGlobalSession, resetLoggingConfig, setContextMetadata, simulateCascadedAgents, simulateConversation, simulateMultiTenant, uninstrument, uninstrumentAnthropic, uninstrumentFetch, uninstrumentGemini, uninstrumentGenai, uninstrumentOpenAI, updateInstrumentationOptions, withAgent, withBudgetGuardrails, withMeterContext, withMeterContextAsync };