confused-ai-core 0.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.
Files changed (114) hide show
  1. package/FEATURES.md +169 -0
  2. package/package.json +119 -0
  3. package/src/agent.ts +187 -0
  4. package/src/agentic/index.ts +87 -0
  5. package/src/agentic/runner.ts +386 -0
  6. package/src/agentic/types.ts +91 -0
  7. package/src/artifacts/artifact.ts +417 -0
  8. package/src/artifacts/index.ts +42 -0
  9. package/src/artifacts/media.ts +304 -0
  10. package/src/cli/index.ts +122 -0
  11. package/src/core/base-agent.ts +151 -0
  12. package/src/core/context-builder.ts +106 -0
  13. package/src/core/index.ts +8 -0
  14. package/src/core/schemas.ts +17 -0
  15. package/src/core/types.ts +158 -0
  16. package/src/create-agent.ts +309 -0
  17. package/src/debug-logger.ts +188 -0
  18. package/src/dx/agent.ts +88 -0
  19. package/src/dx/define-agent.ts +183 -0
  20. package/src/dx/dev-logger.ts +57 -0
  21. package/src/dx/index.ts +11 -0
  22. package/src/errors.ts +175 -0
  23. package/src/execution/engine.ts +522 -0
  24. package/src/execution/graph-builder.ts +362 -0
  25. package/src/execution/index.ts +8 -0
  26. package/src/execution/types.ts +257 -0
  27. package/src/execution/worker-pool.ts +308 -0
  28. package/src/extensions/index.ts +123 -0
  29. package/src/guardrails/allowlist.ts +155 -0
  30. package/src/guardrails/index.ts +17 -0
  31. package/src/guardrails/types.ts +159 -0
  32. package/src/guardrails/validator.ts +265 -0
  33. package/src/index.ts +74 -0
  34. package/src/knowledge/index.ts +5 -0
  35. package/src/knowledge/types.ts +52 -0
  36. package/src/learning/in-memory-store.ts +72 -0
  37. package/src/learning/index.ts +6 -0
  38. package/src/learning/types.ts +42 -0
  39. package/src/llm/cache.ts +300 -0
  40. package/src/llm/index.ts +22 -0
  41. package/src/llm/model-resolver.ts +81 -0
  42. package/src/llm/openai-provider.ts +313 -0
  43. package/src/llm/openrouter-provider.ts +29 -0
  44. package/src/llm/types.ts +131 -0
  45. package/src/memory/in-memory-store.ts +255 -0
  46. package/src/memory/index.ts +7 -0
  47. package/src/memory/types.ts +193 -0
  48. package/src/memory/vector-store.ts +251 -0
  49. package/src/observability/console-logger.ts +123 -0
  50. package/src/observability/index.ts +12 -0
  51. package/src/observability/metrics.ts +85 -0
  52. package/src/observability/otlp-exporter.ts +417 -0
  53. package/src/observability/tracer.ts +105 -0
  54. package/src/observability/types.ts +341 -0
  55. package/src/orchestration/agent-adapter.ts +33 -0
  56. package/src/orchestration/index.ts +34 -0
  57. package/src/orchestration/load-balancer.ts +151 -0
  58. package/src/orchestration/mcp-types.ts +59 -0
  59. package/src/orchestration/message-bus.ts +192 -0
  60. package/src/orchestration/orchestrator.ts +349 -0
  61. package/src/orchestration/pipeline.ts +66 -0
  62. package/src/orchestration/supervisor.ts +107 -0
  63. package/src/orchestration/swarm.ts +1099 -0
  64. package/src/orchestration/toolkit.ts +47 -0
  65. package/src/orchestration/types.ts +339 -0
  66. package/src/planner/classical-planner.ts +383 -0
  67. package/src/planner/index.ts +8 -0
  68. package/src/planner/llm-planner.ts +353 -0
  69. package/src/planner/types.ts +227 -0
  70. package/src/planner/validator.ts +297 -0
  71. package/src/production/circuit-breaker.ts +290 -0
  72. package/src/production/graceful-shutdown.ts +251 -0
  73. package/src/production/health.ts +333 -0
  74. package/src/production/index.ts +57 -0
  75. package/src/production/latency-eval.ts +62 -0
  76. package/src/production/rate-limiter.ts +287 -0
  77. package/src/production/resumable-stream.ts +289 -0
  78. package/src/production/types.ts +81 -0
  79. package/src/sdk/index.ts +374 -0
  80. package/src/session/db-driver.ts +50 -0
  81. package/src/session/in-memory-store.ts +235 -0
  82. package/src/session/index.ts +12 -0
  83. package/src/session/sql-store.ts +315 -0
  84. package/src/session/sqlite-store.ts +61 -0
  85. package/src/session/types.ts +153 -0
  86. package/src/tools/base-tool.ts +223 -0
  87. package/src/tools/browser-tool.ts +123 -0
  88. package/src/tools/calculator-tool.ts +265 -0
  89. package/src/tools/file-tools.ts +394 -0
  90. package/src/tools/github-tool.ts +432 -0
  91. package/src/tools/hackernews-tool.ts +187 -0
  92. package/src/tools/http-tool.ts +118 -0
  93. package/src/tools/index.ts +99 -0
  94. package/src/tools/jira-tool.ts +373 -0
  95. package/src/tools/notion-tool.ts +322 -0
  96. package/src/tools/openai-tool.ts +236 -0
  97. package/src/tools/registry.ts +131 -0
  98. package/src/tools/serpapi-tool.ts +234 -0
  99. package/src/tools/shell-tool.ts +118 -0
  100. package/src/tools/slack-tool.ts +327 -0
  101. package/src/tools/telegram-tool.ts +127 -0
  102. package/src/tools/types.ts +229 -0
  103. package/src/tools/websearch-tool.ts +335 -0
  104. package/src/tools/wikipedia-tool.ts +177 -0
  105. package/src/tools/yfinance-tool.ts +33 -0
  106. package/src/voice/index.ts +17 -0
  107. package/src/voice/voice-provider.ts +228 -0
  108. package/tests/artifact.test.ts +241 -0
  109. package/tests/circuit-breaker.test.ts +171 -0
  110. package/tests/health.test.ts +192 -0
  111. package/tests/llm-cache.test.ts +186 -0
  112. package/tests/rate-limiter.test.ts +161 -0
  113. package/tsconfig.json +29 -0
  114. package/vitest.config.ts +47 -0
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Console Logger Implementation
3
+ *
4
+ * Simple console-based logging for development and debugging
5
+ */
6
+
7
+ import { Logger, LogLevel, LogEntry, LogContext } from './types.js';
8
+
9
+ /**
10
+ * Console logger configuration
11
+ */
12
+ export interface ConsoleLoggerConfig {
13
+ readonly minLevel?: LogLevel;
14
+ readonly includeTimestamp?: boolean;
15
+ readonly prefix?: string;
16
+ }
17
+
18
+ /**
19
+ * Console logger implementation
20
+ */
21
+ export class ConsoleLogger implements Logger {
22
+ private minLevel: LogLevel;
23
+ private includeTimestamp: boolean;
24
+ private prefix: string;
25
+ private context: Partial<LogContext> = {};
26
+
27
+ private static readonly LEVEL_PRIORITY: Record<LogLevel, number> = {
28
+ [LogLevel.DEBUG]: 0,
29
+ [LogLevel.INFO]: 1,
30
+ [LogLevel.WARN]: 2,
31
+ [LogLevel.ERROR]: 3,
32
+ [LogLevel.FATAL]: 4,
33
+ };
34
+
35
+ constructor(config: ConsoleLoggerConfig = {}) {
36
+ this.minLevel = config.minLevel ?? LogLevel.INFO;
37
+ this.includeTimestamp = config.includeTimestamp ?? true;
38
+ this.prefix = config.prefix ?? '[AgentFramework]';
39
+ }
40
+
41
+ debug(message: string, context?: Partial<LogContext>, metadata?: Record<string, unknown>): void {
42
+ this.log(LogLevel.DEBUG, message, context, metadata);
43
+ }
44
+
45
+ info(message: string, context?: Partial<LogContext>, metadata?: Record<string, unknown>): void {
46
+ this.log(LogLevel.INFO, message, context, metadata);
47
+ }
48
+
49
+ warn(message: string, context?: Partial<LogContext>, metadata?: Record<string, unknown>): void {
50
+ this.log(LogLevel.WARN, message, context, metadata);
51
+ }
52
+
53
+ error(message: string, context?: Partial<LogContext>, metadata?: Record<string, unknown>): void {
54
+ this.log(LogLevel.ERROR, message, context, metadata);
55
+ }
56
+
57
+ fatal(message: string, context?: Partial<LogContext>, metadata?: Record<string, unknown>): void {
58
+ this.log(LogLevel.FATAL, message, context, metadata);
59
+ }
60
+
61
+ log(level: LogLevel, message: string, context?: Partial<LogContext>, metadata?: Record<string, unknown>): void {
62
+ if (!this.shouldLog(level)) {
63
+ return;
64
+ }
65
+
66
+ const entry: LogEntry = {
67
+ id: this.generateId(),
68
+ timestamp: new Date(),
69
+ level,
70
+ message,
71
+ source: this.prefix,
72
+ context: { ...this.context, ...context },
73
+ metadata,
74
+ };
75
+
76
+ this.output(entry);
77
+ }
78
+
79
+ child(additionalContext: Partial<LogContext>): Logger {
80
+ const childLogger = new ConsoleLogger({
81
+ minLevel: this.minLevel,
82
+ includeTimestamp: this.includeTimestamp,
83
+ prefix: this.prefix,
84
+ });
85
+ childLogger.context = { ...this.context, ...additionalContext };
86
+ return childLogger;
87
+ }
88
+
89
+ private shouldLog(level: LogLevel): boolean {
90
+ return ConsoleLogger.LEVEL_PRIORITY[level] >= ConsoleLogger.LEVEL_PRIORITY[this.minLevel];
91
+ }
92
+
93
+ private output(entry: LogEntry): void {
94
+ const timestamp = this.includeTimestamp
95
+ ? `[${entry.timestamp.toISOString()}]`
96
+ : '';
97
+ const level = `[${entry.level.toUpperCase()}]`;
98
+ const prefix = entry.source;
99
+
100
+ const parts = [timestamp, prefix, level, entry.message].filter(Boolean);
101
+ const formattedMessage = parts.join(' ');
102
+
103
+ switch (entry.level) {
104
+ case LogLevel.DEBUG:
105
+ console.debug(formattedMessage, entry.metadata ?? '');
106
+ break;
107
+ case LogLevel.INFO:
108
+ console.info(formattedMessage, entry.metadata ?? '');
109
+ break;
110
+ case LogLevel.WARN:
111
+ console.warn(formattedMessage, entry.metadata ?? '');
112
+ break;
113
+ case LogLevel.ERROR:
114
+ case LogLevel.FATAL:
115
+ console.error(formattedMessage, entry.metadata ?? '');
116
+ break;
117
+ }
118
+ }
119
+
120
+ private generateId(): string {
121
+ return `log-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
122
+ }
123
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Observability module exports
3
+ */
4
+
5
+ export * from './types.js';
6
+ export { ConsoleLogger } from './console-logger.js';
7
+ export { InMemoryTracer } from './tracer.js';
8
+ export { MetricsCollectorImpl } from './metrics.js';
9
+
10
+ // OTLP Export
11
+ export { OTLPTraceExporter, OTLPMetricsExporter } from './otlp-exporter.js';
12
+ export type { OTLPExporterConfig } from './otlp-exporter.js';
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Metrics Collector Implementation
3
+ *
4
+ * In-memory metrics collection for monitoring and observability
5
+ */
6
+
7
+ import { MetricsCollector, MetricValue, MetricType } from './types.js';
8
+
9
+ /**
10
+ * Metrics collector implementation
11
+ */
12
+ export class MetricsCollectorImpl implements MetricsCollector {
13
+ private metrics: MetricValue[] = [];
14
+
15
+ counter(name: string, value = 1, labels: Record<string, string> = {}): void {
16
+ this.metrics.push({
17
+ name,
18
+ type: MetricType.COUNTER,
19
+ value,
20
+ labels,
21
+ timestamp: new Date(),
22
+ });
23
+ }
24
+
25
+ gauge(name: string, value: number, labels: Record<string, string> = {}): void {
26
+ // Remove previous gauge with same name and labels
27
+ this.metrics = this.metrics.filter(m =>
28
+ !(m.name === name &&
29
+ m.type === MetricType.GAUGE &&
30
+ JSON.stringify(m.labels) === JSON.stringify(labels))
31
+ );
32
+
33
+ this.metrics.push({
34
+ name,
35
+ type: MetricType.GAUGE,
36
+ value,
37
+ labels,
38
+ timestamp: new Date(),
39
+ });
40
+ }
41
+
42
+ histogram(name: string, value: number, labels: Record<string, string> = {}): void {
43
+ this.metrics.push({
44
+ name,
45
+ type: MetricType.HISTOGRAM,
46
+ value,
47
+ labels,
48
+ timestamp: new Date(),
49
+ });
50
+ }
51
+
52
+ getMetrics(): MetricValue[] {
53
+ return [...this.metrics];
54
+ }
55
+
56
+ clear(): void {
57
+ this.metrics = [];
58
+ }
59
+
60
+ /**
61
+ * Get metrics by name
62
+ */
63
+ getMetricsByName(name: string): MetricValue[] {
64
+ return this.metrics.filter(m => m.name === name);
65
+ }
66
+
67
+ /**
68
+ * Get metrics by type
69
+ */
70
+ getMetricsByType(type: MetricType): MetricValue[] {
71
+ return this.metrics.filter(m => m.type === type);
72
+ }
73
+
74
+ /**
75
+ * Get the latest value for a metric
76
+ */
77
+ getLatestValue(name: string): number | undefined {
78
+ const metrics = this.getMetricsByName(name);
79
+ if (metrics.length === 0) return undefined;
80
+
81
+ return metrics.sort((a, b) =>
82
+ b.timestamp.getTime() - a.timestamp.getTime()
83
+ )[0].value;
84
+ }
85
+ }
@@ -0,0 +1,417 @@
1
+ /**
2
+ * OTLP Exporter - OpenTelemetry Protocol Export for Distributed Tracing
3
+ *
4
+ * Production-grade observability export supporting:
5
+ * - OTLP/HTTP trace export (Jaeger, Datadog, Honeycomb, etc.)
6
+ * - Batched export with configurable intervals
7
+ * - W3C trace context propagation
8
+ * - Automatic retry with backoff
9
+ */
10
+
11
+ import type { TraceSpan, SpanStatus, MetricValue } from './types.js';
12
+
13
+ /** OTLP export configuration */
14
+ export interface OTLPExporterConfig {
15
+ /** OTLP endpoint URL (e.g., https://api.honeycomb.io/v1/traces) */
16
+ readonly endpoint: string;
17
+ /** Service name for trace attribution */
18
+ readonly serviceName: string;
19
+ /** Optional headers (e.g., API keys) */
20
+ readonly headers?: Record<string, string>;
21
+ /** Batch size before export (default: 50) */
22
+ readonly batchSize?: number;
23
+ /** Export interval in ms (default: 5000) */
24
+ readonly exportIntervalMs?: number;
25
+ /** Max queue size before dropping (default: 2048) */
26
+ readonly maxQueueSize?: number;
27
+ /** Export timeout in ms (default: 30000) */
28
+ readonly timeoutMs?: number;
29
+ /** Enable console logging for debug (default: false) */
30
+ readonly debug?: boolean;
31
+ }
32
+
33
+ /** OTLP span format */
34
+ interface OTLPSpan {
35
+ traceId: string;
36
+ spanId: string;
37
+ parentSpanId?: string;
38
+ name: string;
39
+ kind: number;
40
+ startTimeUnixNano: string;
41
+ endTimeUnixNano?: string;
42
+ status: { code: number; message?: string };
43
+ attributes: Array<{ key: string; value: { stringValue?: string; intValue?: number; boolValue?: boolean } }>;
44
+ events: Array<{
45
+ name: string;
46
+ timeUnixNano: string;
47
+ attributes?: Array<{ key: string; value: { stringValue: string } }>;
48
+ }>;
49
+ }
50
+
51
+ /** Export batch request */
52
+ interface OTLPTraceRequest {
53
+ resourceSpans: Array<{
54
+ resource: {
55
+ attributes: Array<{ key: string; value: { stringValue: string } }>;
56
+ };
57
+ scopeSpans: Array<{
58
+ scope: { name: string; version: string };
59
+ spans: OTLPSpan[];
60
+ }>;
61
+ }>;
62
+ }
63
+
64
+ /**
65
+ * OTLP Trace Exporter - exports spans to OTLP-compatible backends.
66
+ *
67
+ * @example
68
+ * const exporter = new OTLPTraceExporter({
69
+ * endpoint: 'https://api.honeycomb.io/v1/traces',
70
+ * serviceName: 'my-agent',
71
+ * headers: { 'x-honeycomb-team': process.env.HONEYCOMB_API_KEY },
72
+ * });
73
+ *
74
+ * // Export spans
75
+ * exporter.addSpan(span);
76
+ *
77
+ * // Start automatic export
78
+ * exporter.start();
79
+ *
80
+ * // Clean shutdown
81
+ * await exporter.shutdown();
82
+ */
83
+ export class OTLPTraceExporter {
84
+ private readonly config: Required<Omit<OTLPExporterConfig, 'headers'>> &
85
+ Pick<OTLPExporterConfig, 'headers'>;
86
+ private spanQueue: TraceSpan[] = [];
87
+ private exportTimer: NodeJS.Timeout | null = null;
88
+ private isShuttingDown = false;
89
+
90
+ constructor(config: OTLPExporterConfig) {
91
+ this.config = {
92
+ endpoint: config.endpoint,
93
+ serviceName: config.serviceName,
94
+ headers: config.headers,
95
+ batchSize: config.batchSize ?? 50,
96
+ exportIntervalMs: config.exportIntervalMs ?? 5_000,
97
+ maxQueueSize: config.maxQueueSize ?? 2048,
98
+ timeoutMs: config.timeoutMs ?? 30_000,
99
+ debug: config.debug ?? false,
100
+ };
101
+ }
102
+
103
+ /** Add a span to the export queue */
104
+ addSpan(span: TraceSpan): void {
105
+ if (this.isShuttingDown) return;
106
+
107
+ if (this.spanQueue.length >= this.config.maxQueueSize) {
108
+ this.log('warn', `Queue full (${this.config.maxQueueSize}), dropping oldest spans`);
109
+ this.spanQueue.shift();
110
+ }
111
+
112
+ this.spanQueue.push(span);
113
+
114
+ // Export immediately if batch is full
115
+ if (this.spanQueue.length >= this.config.batchSize) {
116
+ this.export().catch(e => this.log('error', `Export failed: ${e}`));
117
+ }
118
+ }
119
+
120
+ /** Add multiple spans */
121
+ addSpans(spans: TraceSpan[]): void {
122
+ for (const span of spans) {
123
+ this.addSpan(span);
124
+ }
125
+ }
126
+
127
+ /** Start automatic periodic export */
128
+ start(): void {
129
+ if (this.exportTimer) return;
130
+
131
+ this.exportTimer = setInterval(() => {
132
+ this.export().catch(e => this.log('error', `Periodic export failed: ${e}`));
133
+ }, this.config.exportIntervalMs);
134
+
135
+ this.log('debug', `Started with interval ${this.config.exportIntervalMs}ms`);
136
+ }
137
+
138
+ /** Stop automatic export and flush remaining spans */
139
+ async shutdown(): Promise<void> {
140
+ this.isShuttingDown = true;
141
+
142
+ if (this.exportTimer) {
143
+ clearInterval(this.exportTimer);
144
+ this.exportTimer = null;
145
+ }
146
+
147
+ // Flush remaining spans
148
+ if (this.spanQueue.length > 0) {
149
+ await this.export();
150
+ }
151
+
152
+ this.log('debug', 'Shutdown complete');
153
+ }
154
+
155
+ /** Force export current batch */
156
+ async export(): Promise<{ success: boolean; exported: number; errors?: string[] }> {
157
+ if (this.spanQueue.length === 0) {
158
+ return { success: true, exported: 0 };
159
+ }
160
+
161
+ const batch = this.spanQueue.splice(0, this.config.batchSize);
162
+ const request = this.buildRequest(batch);
163
+
164
+ try {
165
+ const controller = new AbortController();
166
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);
167
+
168
+ const response = await fetch(this.config.endpoint, {
169
+ method: 'POST',
170
+ headers: {
171
+ 'Content-Type': 'application/json',
172
+ ...this.config.headers,
173
+ },
174
+ body: JSON.stringify(request),
175
+ signal: controller.signal,
176
+ });
177
+
178
+ clearTimeout(timeoutId);
179
+
180
+ if (!response.ok) {
181
+ const errorText = await response.text();
182
+ this.log('error', `Export failed: ${response.status} ${errorText}`);
183
+ // Re-queue failed spans
184
+ this.spanQueue.unshift(...batch);
185
+ return { success: false, exported: 0, errors: [errorText] };
186
+ }
187
+
188
+ this.log('debug', `Exported ${batch.length} spans`);
189
+ return { success: true, exported: batch.length };
190
+ } catch (error) {
191
+ const message = error instanceof Error ? error.message : String(error);
192
+ this.log('error', `Export error: ${message}`);
193
+ // Re-queue failed spans
194
+ this.spanQueue.unshift(...batch);
195
+ return { success: false, exported: 0, errors: [message] };
196
+ }
197
+ }
198
+
199
+ /** Get current queue size */
200
+ getQueueSize(): number {
201
+ return this.spanQueue.length;
202
+ }
203
+
204
+ // --- Private methods ---
205
+
206
+ private buildRequest(spans: TraceSpan[]): OTLPTraceRequest {
207
+ return {
208
+ resourceSpans: [
209
+ {
210
+ resource: {
211
+ attributes: [
212
+ { key: 'service.name', value: { stringValue: this.config.serviceName } },
213
+ ],
214
+ },
215
+ scopeSpans: [
216
+ {
217
+ scope: { name: '@confused-ai/core', version: '0.1.0' },
218
+ spans: spans.map(span => this.convertSpan(span)),
219
+ },
220
+ ],
221
+ },
222
+ ],
223
+ };
224
+ }
225
+
226
+ private convertSpan(span: TraceSpan): OTLPSpan {
227
+ return {
228
+ traceId: span.traceId,
229
+ spanId: String(span.id),
230
+ parentSpanId: span.parentId ? String(span.parentId) : undefined,
231
+ name: span.name,
232
+ kind: 1, // INTERNAL
233
+ startTimeUnixNano: String(span.startTime.getTime() * 1_000_000),
234
+ endTimeUnixNano: span.endTime ? String(span.endTime.getTime() * 1_000_000) : undefined,
235
+ status: {
236
+ code: this.statusToCode(span.status),
237
+ },
238
+ attributes: Object.entries(span.attributes).map(([key, value]) => ({
239
+ key,
240
+ value: this.convertAttributeValue(value),
241
+ })),
242
+ events: span.events.map(event => ({
243
+ name: event.name,
244
+ timeUnixNano: String(event.timestamp.getTime() * 1_000_000),
245
+ attributes: event.attributes
246
+ ? Object.entries(event.attributes).map(([key, value]) => ({
247
+ key,
248
+ value: { stringValue: String(value) },
249
+ }))
250
+ : undefined,
251
+ })),
252
+ };
253
+ }
254
+
255
+ private statusToCode(status: SpanStatus): number {
256
+ switch (status) {
257
+ case 'ok':
258
+ return 1;
259
+ case 'error':
260
+ return 2;
261
+ default:
262
+ return 0;
263
+ }
264
+ }
265
+
266
+ private convertAttributeValue(value: unknown): { stringValue?: string; intValue?: number; boolValue?: boolean } {
267
+ if (typeof value === 'string') {
268
+ return { stringValue: value };
269
+ }
270
+ if (typeof value === 'number') {
271
+ return { intValue: value };
272
+ }
273
+ if (typeof value === 'boolean') {
274
+ return { boolValue: value };
275
+ }
276
+ return { stringValue: String(value) };
277
+ }
278
+
279
+ private log(level: 'debug' | 'warn' | 'error', message: string): void {
280
+ if (!this.config.debug && level === 'debug') return;
281
+
282
+ const prefix = `[OTLPExporter] [${level.toUpperCase()}]`;
283
+ if (level === 'error') {
284
+ console.error(prefix, message);
285
+ } else if (level === 'warn') {
286
+ console.warn(prefix, message);
287
+ } else {
288
+ console.log(prefix, message);
289
+ }
290
+ }
291
+ }
292
+
293
+ /**
294
+ * OTLP Metrics Exporter - exports metrics to OTLP-compatible backends.
295
+ */
296
+ export class OTLPMetricsExporter {
297
+ private readonly config: Required<Omit<OTLPExporterConfig, 'headers'>> &
298
+ Pick<OTLPExporterConfig, 'headers'>;
299
+ private metricsQueue: MetricValue[] = [];
300
+ private exportTimer: NodeJS.Timeout | null = null;
301
+ private isShuttingDown = false;
302
+
303
+ constructor(config: OTLPExporterConfig) {
304
+ this.config = {
305
+ endpoint: config.endpoint,
306
+ serviceName: config.serviceName,
307
+ headers: config.headers,
308
+ batchSize: config.batchSize ?? 100,
309
+ exportIntervalMs: config.exportIntervalMs ?? 10_000,
310
+ maxQueueSize: config.maxQueueSize ?? 4096,
311
+ timeoutMs: config.timeoutMs ?? 30_000,
312
+ debug: config.debug ?? false,
313
+ };
314
+ }
315
+
316
+ /** Add a metric to the export queue */
317
+ addMetric(metric: MetricValue): void {
318
+ if (this.isShuttingDown) return;
319
+
320
+ if (this.metricsQueue.length >= this.config.maxQueueSize) {
321
+ this.metricsQueue.shift();
322
+ }
323
+
324
+ this.metricsQueue.push(metric);
325
+ }
326
+
327
+ /** Start automatic periodic export */
328
+ start(): void {
329
+ if (this.exportTimer) return;
330
+
331
+ this.exportTimer = setInterval(() => {
332
+ this.export().catch(e => console.error('[OTLPMetrics] Export failed:', e));
333
+ }, this.config.exportIntervalMs);
334
+ }
335
+
336
+ /** Stop and flush */
337
+ async shutdown(): Promise<void> {
338
+ this.isShuttingDown = true;
339
+
340
+ if (this.exportTimer) {
341
+ clearInterval(this.exportTimer);
342
+ this.exportTimer = null;
343
+ }
344
+
345
+ if (this.metricsQueue.length > 0) {
346
+ await this.export();
347
+ }
348
+ }
349
+
350
+ /** Export current batch */
351
+ async export(): Promise<{ success: boolean; exported: number }> {
352
+ if (this.metricsQueue.length === 0) {
353
+ return { success: true, exported: 0 };
354
+ }
355
+
356
+ const batch = this.metricsQueue.splice(0, this.config.batchSize);
357
+
358
+ try {
359
+ const response = await fetch(this.config.endpoint, {
360
+ method: 'POST',
361
+ headers: {
362
+ 'Content-Type': 'application/json',
363
+ ...this.config.headers,
364
+ },
365
+ body: JSON.stringify(this.buildMetricsRequest(batch)),
366
+ });
367
+
368
+ if (!response.ok) {
369
+ this.metricsQueue.unshift(...batch);
370
+ return { success: false, exported: 0 };
371
+ }
372
+
373
+ return { success: true, exported: batch.length };
374
+ } catch {
375
+ this.metricsQueue.unshift(...batch);
376
+ return { success: false, exported: 0 };
377
+ }
378
+ }
379
+
380
+ private buildMetricsRequest(metrics: MetricValue[]) {
381
+ return {
382
+ resourceMetrics: [
383
+ {
384
+ resource: {
385
+ attributes: [
386
+ { key: 'service.name', value: { stringValue: this.config.serviceName } },
387
+ ],
388
+ },
389
+ scopeMetrics: [
390
+ {
391
+ scope: { name: '@confused-ai/core', version: '0.1.0' },
392
+ metrics: metrics.map(m => ({
393
+ name: m.name,
394
+ description: '',
395
+ unit: '',
396
+ sum: {
397
+ dataPoints: [
398
+ {
399
+ asDouble: m.value,
400
+ timeUnixNano: String(m.timestamp.getTime() * 1_000_000),
401
+ attributes: Object.entries(m.labels).map(([k, v]) => ({
402
+ key: k,
403
+ value: { stringValue: v },
404
+ })),
405
+ },
406
+ ],
407
+ aggregationTemporality: 2, // CUMULATIVE
408
+ isMonotonic: m.type === 'counter',
409
+ },
410
+ })),
411
+ },
412
+ ],
413
+ },
414
+ ],
415
+ };
416
+ }
417
+ }