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,287 @@
1
+ /**
2
+ * Rate Limiter - Agno-style Production Throttling
3
+ *
4
+ * Token bucket algorithm for controlling request rates to external APIs.
5
+ * Supports:
6
+ * - Per-agent and per-tool rate limits
7
+ * - Configurable burst capacity
8
+ * - Backpressure modes (queue or reject)
9
+ * - Metrics integration
10
+ */
11
+
12
+ import type { MetricsCollector } from '../observability/types.js';
13
+
14
+ /** Rate limiter configuration */
15
+ export interface RateLimiterConfig {
16
+ /** Unique name for this limiter (for metrics/logging) */
17
+ readonly name: string;
18
+ /** Maximum requests per interval (default: 60) */
19
+ readonly maxRequests: number;
20
+ /** Interval in milliseconds (default: 60000 = 1 minute) */
21
+ readonly intervalMs?: number;
22
+ /** Burst capacity beyond maxRequests (default: 10) */
23
+ readonly burstCapacity?: number;
24
+ /** Action when limit reached: 'reject' throws, 'queue' waits (default: 'reject') */
25
+ readonly overflowMode?: 'reject' | 'queue';
26
+ /** Max queue size when mode is 'queue' (default: 100) */
27
+ readonly maxQueueSize?: number;
28
+ /** Max wait time in queue before rejection in ms (default: 30000) */
29
+ readonly maxQueueWaitMs?: number;
30
+ /** Optional metrics collector */
31
+ readonly metrics?: MetricsCollector;
32
+ }
33
+
34
+ /** Rate limit exceeded error */
35
+ export class RateLimitError extends Error {
36
+ readonly limiterName: string;
37
+ readonly retryAfterMs: number;
38
+
39
+ constructor(name: string, retryAfterMs: number) {
40
+ super(`Rate limit exceeded for '${name}'. Retry after ${retryAfterMs}ms`);
41
+ this.name = 'RateLimitError';
42
+ this.limiterName = name;
43
+ this.retryAfterMs = retryAfterMs;
44
+ Object.setPrototypeOf(this, RateLimitError.prototype);
45
+ }
46
+ }
47
+
48
+ /** Queued request */
49
+ interface QueuedRequest<T> {
50
+ readonly fn: () => Promise<T>;
51
+ readonly resolve: (value: T) => void;
52
+ readonly reject: (error: Error) => void;
53
+ readonly enqueuedAt: number;
54
+ }
55
+
56
+ /**
57
+ * Token bucket rate limiter with optional request queuing.
58
+ *
59
+ * @example
60
+ * const limiter = new RateLimiter({
61
+ * name: 'openai-api',
62
+ * maxRequests: 100,
63
+ * intervalMs: 60000, // 100 requests per minute
64
+ * overflowMode: 'queue',
65
+ * });
66
+ *
67
+ * const result = await limiter.execute(() => openai.chat(...));
68
+ */
69
+ export class RateLimiter {
70
+ private tokens: number;
71
+ private lastRefill: number;
72
+ private queue: QueuedRequest<unknown>[] = [];
73
+ private processing = false;
74
+
75
+ private readonly config: Required<Omit<RateLimiterConfig, 'metrics'>> &
76
+ Pick<RateLimiterConfig, 'metrics'>;
77
+
78
+ constructor(config: RateLimiterConfig) {
79
+ this.config = {
80
+ name: config.name,
81
+ maxRequests: config.maxRequests,
82
+ intervalMs: config.intervalMs ?? 60_000,
83
+ burstCapacity: config.burstCapacity ?? Math.ceil(config.maxRequests * 0.1),
84
+ overflowMode: config.overflowMode ?? 'reject',
85
+ maxQueueSize: config.maxQueueSize ?? 100,
86
+ maxQueueWaitMs: config.maxQueueWaitMs ?? 30_000,
87
+ metrics: config.metrics,
88
+ };
89
+
90
+ this.tokens = this.config.maxRequests + this.config.burstCapacity;
91
+ this.lastRefill = Date.now();
92
+ }
93
+
94
+ /** Get current available tokens */
95
+ getAvailableTokens(): number {
96
+ this.refillTokens();
97
+ return this.tokens;
98
+ }
99
+
100
+ /** Get current queue size */
101
+ getQueueSize(): number {
102
+ return this.queue.length;
103
+ }
104
+
105
+ /** Check if a request can be made immediately */
106
+ canProceed(): boolean {
107
+ this.refillTokens();
108
+ return this.tokens > 0;
109
+ }
110
+
111
+ /** Get time until next token available in ms */
112
+ getTimeUntilAvailable(): number {
113
+ if (this.tokens > 0) return 0;
114
+
115
+ const tokenRefillRate = this.config.intervalMs / this.config.maxRequests;
116
+ const timeSinceLastRefill = Date.now() - this.lastRefill;
117
+ return Math.max(0, tokenRefillRate - timeSinceLastRefill);
118
+ }
119
+
120
+ /**
121
+ * Execute a function through the rate limiter.
122
+ */
123
+ async execute<T>(fn: () => Promise<T>): Promise<T> {
124
+ this.refillTokens();
125
+
126
+ if (this.tokens > 0) {
127
+ this.tokens--;
128
+ this.recordMetric('rate_limiter_allowed', 1);
129
+ return fn();
130
+ }
131
+
132
+ if (this.config.overflowMode === 'queue') {
133
+ return this.enqueue(fn);
134
+ }
135
+
136
+ const retryAfterMs = this.getTimeUntilAvailable();
137
+ this.recordMetric('rate_limiter_rejected', 1);
138
+ throw new RateLimitError(this.config.name, retryAfterMs);
139
+ }
140
+
141
+ /**
142
+ * Try to acquire a token without executing anything.
143
+ * Returns true if token acquired, false otherwise.
144
+ */
145
+ tryAcquire(): boolean {
146
+ this.refillTokens();
147
+ if (this.tokens > 0) {
148
+ this.tokens--;
149
+ return true;
150
+ }
151
+ return false;
152
+ }
153
+
154
+ /** Get rate limiter statistics */
155
+ getStats(): {
156
+ availableTokens: number;
157
+ queueSize: number;
158
+ maxRequests: number;
159
+ intervalMs: number;
160
+ } {
161
+ return {
162
+ availableTokens: this.getAvailableTokens(),
163
+ queueSize: this.queue.length,
164
+ maxRequests: this.config.maxRequests,
165
+ intervalMs: this.config.intervalMs,
166
+ };
167
+ }
168
+
169
+ // --- Private methods ---
170
+
171
+ private refillTokens(): void {
172
+ const now = Date.now();
173
+ const elapsed = now - this.lastRefill;
174
+ const maxTokens = this.config.maxRequests + this.config.burstCapacity;
175
+
176
+ // Token refill rate: tokens per ms
177
+ const refillRate = this.config.maxRequests / this.config.intervalMs;
178
+ const tokensToAdd = elapsed * refillRate;
179
+
180
+ this.tokens = Math.min(maxTokens, this.tokens + tokensToAdd);
181
+ this.lastRefill = now;
182
+ }
183
+
184
+ private async enqueue<T>(fn: () => Promise<T>): Promise<T> {
185
+ if (this.queue.length >= this.config.maxQueueSize) {
186
+ this.recordMetric('rate_limiter_queue_full', 1);
187
+ throw new RateLimitError(
188
+ this.config.name,
189
+ this.getTimeUntilAvailable()
190
+ );
191
+ }
192
+
193
+ this.recordMetric('rate_limiter_queued', 1);
194
+
195
+ return new Promise<T>((resolve, reject) => {
196
+ this.queue.push({
197
+ fn: fn as () => Promise<unknown>,
198
+ resolve: resolve as (value: unknown) => void,
199
+ reject,
200
+ enqueuedAt: Date.now(),
201
+ });
202
+
203
+ this.processQueue();
204
+ });
205
+ }
206
+
207
+ private async processQueue(): Promise<void> {
208
+ if (this.processing) return;
209
+ this.processing = true;
210
+
211
+ while (this.queue.length > 0) {
212
+ this.refillTokens();
213
+
214
+ if (this.tokens <= 0) {
215
+ // Wait for a token
216
+ await this.waitForToken();
217
+ continue;
218
+ }
219
+
220
+ const request = this.queue.shift()!;
221
+ const waitTime = Date.now() - request.enqueuedAt;
222
+
223
+ if (waitTime > this.config.maxQueueWaitMs) {
224
+ request.reject(
225
+ new RateLimitError(this.config.name, this.getTimeUntilAvailable())
226
+ );
227
+ this.recordMetric('rate_limiter_queue_timeout', 1);
228
+ continue;
229
+ }
230
+
231
+ this.tokens--;
232
+ this.recordMetric('rate_limiter_dequeued', 1, {
233
+ wait_time_ms: String(waitTime),
234
+ });
235
+
236
+ try {
237
+ const result = await request.fn();
238
+ request.resolve(result);
239
+ } catch (error) {
240
+ request.reject(error as Error);
241
+ }
242
+ }
243
+
244
+ this.processing = false;
245
+ }
246
+
247
+ private async waitForToken(): Promise<void> {
248
+ const waitTime = this.getTimeUntilAvailable();
249
+ await new Promise(resolve => setTimeout(resolve, Math.max(waitTime, 10)));
250
+ }
251
+
252
+ private recordMetric(
253
+ name: string,
254
+ value: number,
255
+ labels: Record<string, string> = {}
256
+ ): void {
257
+ this.config.metrics?.counter(`${this.config.name}.${name}`, value, {
258
+ limiter: this.config.name,
259
+ ...labels,
260
+ });
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Create a rate limiter with defaults for OpenAI API (RPM limits).
266
+ */
267
+ export function createOpenAIRateLimiter(
268
+ tier: 'free' | 'tier1' | 'tier2' | 'tier3' | 'tier4' | 'tier5' = 'tier1',
269
+ options?: Partial<RateLimiterConfig>
270
+ ): RateLimiter {
271
+ const tierLimits: Record<string, number> = {
272
+ free: 3,
273
+ tier1: 60,
274
+ tier2: 100,
275
+ tier3: 500,
276
+ tier4: 5000,
277
+ tier5: 10000,
278
+ };
279
+
280
+ return new RateLimiter({
281
+ name: 'openai-api',
282
+ maxRequests: tierLimits[tier] ?? 60,
283
+ intervalMs: 60_000,
284
+ overflowMode: 'queue',
285
+ ...options,
286
+ });
287
+ }
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Resumable Streaming - VoltAgent-Style Stream Reconnection
3
+ *
4
+ * Enables clients to reconnect to in-flight streams after refresh/disconnect:
5
+ * - Checkpoint-based stream state persistence
6
+ * - Automatic resume from last position
7
+ * - SSE-compatible output format
8
+ */
9
+
10
+ /** Stream checkpoint for resumption */
11
+ export interface StreamCheckpoint {
12
+ /** Unique stream ID */
13
+ readonly streamId: string;
14
+ /** Position in the stream (chunk index) */
15
+ readonly position: number;
16
+ /** Accumulated content up to this point */
17
+ readonly accumulatedContent: string;
18
+ /** Tool calls seen so far */
19
+ readonly toolCalls: Array<{
20
+ id: string;
21
+ name: string;
22
+ arguments: string;
23
+ }>;
24
+ /** Stream start time */
25
+ readonly startedAt: Date;
26
+ /** Last activity time */
27
+ readonly lastActivityAt: Date;
28
+ /** Is stream complete? */
29
+ readonly isComplete: boolean;
30
+ /** Finish reason if complete */
31
+ readonly finishReason?: string;
32
+ }
33
+
34
+ /** Resumable stream configuration */
35
+ export interface ResumableStreamConfig {
36
+ /** Maximum age of resumable streams in ms (default: 5 minutes) */
37
+ readonly maxAgeMs?: number;
38
+ /** Cleanup interval in ms (default: 1 minute) */
39
+ readonly cleanupIntervalMs?: number;
40
+ /** Maximum number of stored streams (default: 1000) */
41
+ readonly maxStreams?: number;
42
+ }
43
+
44
+ /** Stream chunk for SSE */
45
+ export interface StreamChunkSSE {
46
+ readonly id: string;
47
+ readonly event: 'delta' | 'error' | 'done';
48
+ readonly data: {
49
+ type: 'text' | 'tool_call';
50
+ content?: string;
51
+ toolCall?: {
52
+ id: string;
53
+ name: string;
54
+ arguments: string;
55
+ };
56
+ };
57
+ readonly position: number;
58
+ }
59
+
60
+ /**
61
+ * ResumableStreamManager - manages stream checkpoints for reconnection.
62
+ *
63
+ * @example
64
+ * const manager = new ResumableStreamManager();
65
+ *
66
+ * // Start a stream
67
+ * const streamId = manager.createStream();
68
+ *
69
+ * // As chunks arrive, save checkpoints
70
+ * manager.saveChunk(streamId, { type: 'text', content: 'Hello' });
71
+ *
72
+ * // Client reconnects - get missed content
73
+ * const checkpoint = manager.getCheckpoint(streamId);
74
+ * const missedChunks = manager.getChunksSince(streamId, clientPosition);
75
+ */
76
+ export class ResumableStreamManager {
77
+ private readonly streams = new Map<string, StreamCheckpoint>();
78
+ private readonly chunks = new Map<string, StreamChunkSSE[]>();
79
+ private readonly config: Required<ResumableStreamConfig>;
80
+ private cleanupTimer: NodeJS.Timeout | null = null;
81
+
82
+ constructor(config: ResumableStreamConfig = {}) {
83
+ this.config = {
84
+ maxAgeMs: config.maxAgeMs ?? 5 * 60 * 1000, // 5 minutes
85
+ cleanupIntervalMs: config.cleanupIntervalMs ?? 60 * 1000, // 1 minute
86
+ maxStreams: config.maxStreams ?? 1000,
87
+ };
88
+
89
+ this.startCleanup();
90
+ }
91
+
92
+ /** Create a new resumable stream */
93
+ createStream(): string {
94
+ const streamId = this.generateId();
95
+ const now = new Date();
96
+
97
+ // Evict if at capacity
98
+ if (this.streams.size >= this.config.maxStreams) {
99
+ this.evictOldest();
100
+ }
101
+
102
+ this.streams.set(streamId, {
103
+ streamId,
104
+ position: 0,
105
+ accumulatedContent: '',
106
+ toolCalls: [],
107
+ startedAt: now,
108
+ lastActivityAt: now,
109
+ isComplete: false,
110
+ });
111
+
112
+ this.chunks.set(streamId, []);
113
+
114
+ return streamId;
115
+ }
116
+
117
+ /** Get current checkpoint for a stream */
118
+ getCheckpoint(streamId: string): StreamCheckpoint | null {
119
+ return this.streams.get(streamId) ?? null;
120
+ }
121
+
122
+ /** Save a chunk to the stream */
123
+ saveChunk(
124
+ streamId: string,
125
+ chunk: { type: 'text'; content: string } | { type: 'tool_call'; toolCall: { id: string; name: string; arguments: string } }
126
+ ): StreamChunkSSE | null {
127
+ const checkpoint = this.streams.get(streamId);
128
+ if (!checkpoint) return null;
129
+
130
+ const position = checkpoint.position + 1;
131
+ const now = new Date();
132
+
133
+ // Create SSE chunk
134
+ const sseChunk: StreamChunkSSE = {
135
+ id: `${streamId}_${position}`,
136
+ event: 'delta',
137
+ data: chunk.type === 'text'
138
+ ? { type: 'text', content: chunk.content }
139
+ : { type: 'tool_call', toolCall: chunk.toolCall },
140
+ position,
141
+ };
142
+
143
+ // Save chunk
144
+ this.chunks.get(streamId)!.push(sseChunk);
145
+
146
+ // Update checkpoint
147
+ const newToolCalls = chunk.type === 'tool_call'
148
+ ? [...checkpoint.toolCalls, chunk.toolCall]
149
+ : checkpoint.toolCalls;
150
+
151
+ this.streams.set(streamId, {
152
+ ...checkpoint,
153
+ position,
154
+ accumulatedContent: checkpoint.accumulatedContent + (chunk.type === 'text' ? chunk.content : ''),
155
+ toolCalls: newToolCalls,
156
+ lastActivityAt: now,
157
+ });
158
+
159
+ return sseChunk;
160
+ }
161
+
162
+ /** Complete the stream */
163
+ completeStream(streamId: string, finishReason = 'stop'): void {
164
+ const checkpoint = this.streams.get(streamId);
165
+ if (!checkpoint) return;
166
+
167
+ this.streams.set(streamId, {
168
+ ...checkpoint,
169
+ isComplete: true,
170
+ finishReason,
171
+ lastActivityAt: new Date(),
172
+ });
173
+
174
+ // Send done event
175
+ this.chunks.get(streamId)?.push({
176
+ id: `${streamId}_done`,
177
+ event: 'done',
178
+ data: { type: 'text', content: finishReason },
179
+ position: checkpoint.position + 1,
180
+ });
181
+ }
182
+
183
+ /** Get all chunks since a position (for resume) */
184
+ getChunksSince(streamId: string, position: number): StreamChunkSSE[] {
185
+ const chunks = this.chunks.get(streamId);
186
+ if (!chunks) return [];
187
+
188
+ return chunks.filter(c => c.position > position);
189
+ }
190
+
191
+ /** Get all chunks for a stream */
192
+ getAllChunks(streamId: string): StreamChunkSSE[] {
193
+ return this.chunks.get(streamId) ?? [];
194
+ }
195
+
196
+ /** Check if stream exists and is active */
197
+ isStreamActive(streamId: string): boolean {
198
+ const checkpoint = this.streams.get(streamId);
199
+ return checkpoint !== undefined && !checkpoint.isComplete;
200
+ }
201
+
202
+ /** Delete a stream */
203
+ deleteStream(streamId: string): boolean {
204
+ this.chunks.delete(streamId);
205
+ return this.streams.delete(streamId);
206
+ }
207
+
208
+ /** Shutdown the manager */
209
+ shutdown(): void {
210
+ if (this.cleanupTimer) {
211
+ clearInterval(this.cleanupTimer);
212
+ this.cleanupTimer = null;
213
+ }
214
+ }
215
+
216
+ // --- Private ---
217
+
218
+ private generateId(): string {
219
+ return `stream_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
220
+ }
221
+
222
+ private startCleanup(): void {
223
+ this.cleanupTimer = setInterval(() => {
224
+ this.cleanup();
225
+ }, this.config.cleanupIntervalMs);
226
+ }
227
+
228
+ private cleanup(): void {
229
+ const now = Date.now();
230
+ const maxAge = this.config.maxAgeMs;
231
+
232
+ for (const [streamId, checkpoint] of this.streams.entries()) {
233
+ const age = now - checkpoint.lastActivityAt.getTime();
234
+ if (age > maxAge) {
235
+ this.deleteStream(streamId);
236
+ }
237
+ }
238
+ }
239
+
240
+ private evictOldest(): void {
241
+ let oldest: StreamCheckpoint | null = null;
242
+ let oldestId = '';
243
+
244
+ for (const [id, checkpoint] of this.streams.entries()) {
245
+ if (!oldest || checkpoint.lastActivityAt < oldest.lastActivityAt) {
246
+ oldest = checkpoint;
247
+ oldestId = id;
248
+ }
249
+ }
250
+
251
+ if (oldestId) {
252
+ this.deleteStream(oldestId);
253
+ }
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Format a chunk for SSE transmission
259
+ */
260
+ export function formatSSE(chunk: StreamChunkSSE): string {
261
+ return `id: ${chunk.id}\nevent: ${chunk.event}\ndata: ${JSON.stringify(chunk.data)}\n\n`;
262
+ }
263
+
264
+ /**
265
+ * Create a resumable stream wrapper for async generators
266
+ */
267
+ export function createResumableStream(
268
+ manager: ResumableStreamManager,
269
+ generator: AsyncGenerator<{ type: 'text'; content: string } | { type: 'tool_call'; toolCall: { id: string; name: string; arguments: string } }>
270
+ ): { streamId: string; stream: AsyncGenerator<StreamChunkSSE> } {
271
+ const streamId = manager.createStream();
272
+
273
+ async function* wrappedGenerator(): AsyncGenerator<StreamChunkSSE> {
274
+ try {
275
+ for await (const chunk of generator) {
276
+ const sseChunk = manager.saveChunk(streamId, chunk);
277
+ if (sseChunk) {
278
+ yield sseChunk;
279
+ }
280
+ }
281
+ manager.completeStream(streamId);
282
+ } catch (error) {
283
+ manager.completeStream(streamId, 'error');
284
+ throw error;
285
+ }
286
+ }
287
+
288
+ return { streamId, stream: wrappedGenerator() };
289
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Production: runtime, control plane, evals for accuracy, performance, latency.
3
+ */
4
+
5
+ import type { EntityId } from '../core/types.js';
6
+
7
+ /** HTTP runtime: serve agents over HTTP (FastAPI-style; use Express/Hono/Fastify impl) */
8
+ export interface AgentRuntime {
9
+ /** Start the server (e.g. listen on port) */
10
+ start(): Promise<void>;
11
+
12
+ /** Stop the server */
13
+ stop(): Promise<void>;
14
+
15
+ /** Register an agent route (e.g. POST /agents/:id/run) */
16
+ registerAgent?(agentId: EntityId, handler: (body: unknown) => Promise<unknown>): void;
17
+ }
18
+
19
+ /** Control plane: monitor and manage agents (optional UI backend) */
20
+ export interface ControlPlane {
21
+ /** List agents */
22
+ listAgents(): Promise<{ id: EntityId; name: string; status?: string }[]>;
23
+
24
+ /** Get agent stats (runs, latency, errors) */
25
+ getAgentStats?(agentId: EntityId): Promise<ProductionAgentStats>;
26
+
27
+ /** Optional: get runs/sessions for an agent */
28
+ getRuns?(agentId: EntityId, options?: { limit?: number }): Promise<RunSummary[]>;
29
+ }
30
+
31
+ export interface ProductionAgentStats {
32
+ readonly agentId: EntityId;
33
+ readonly totalRuns: number;
34
+ readonly successCount: number;
35
+ readonly failureCount: number;
36
+ readonly avgLatencyMs: number;
37
+ readonly p95LatencyMs?: number;
38
+ }
39
+
40
+ export interface RunSummary {
41
+ readonly id: string;
42
+ readonly agentId: EntityId;
43
+ readonly sessionId?: string;
44
+ readonly status: string;
45
+ readonly latencyMs?: number;
46
+ readonly startedAt: Date;
47
+ }
48
+
49
+ /** Eval: accuracy, performance, latency */
50
+ export interface EvalSuite {
51
+ readonly id: string;
52
+ readonly name: string;
53
+ /** Run eval and return metrics */
54
+ run(options?: EvalRunOptions): Promise<EvalResult>;
55
+ }
56
+
57
+ export interface EvalRunOptions {
58
+ readonly agentId?: EntityId;
59
+ readonly dataset?: EvalSample[];
60
+ readonly maxSamples?: number;
61
+ }
62
+
63
+ export interface EvalSample {
64
+ readonly id: string;
65
+ readonly input: string | Record<string, unknown>;
66
+ readonly expectedOutput?: string | Record<string, unknown>;
67
+ readonly metadata?: Record<string, unknown>;
68
+ }
69
+
70
+ export interface EvalResult {
71
+ readonly suiteId: string;
72
+ readonly accuracy?: number;
73
+ readonly latencyMs?: number;
74
+ readonly latencyP95Ms?: number;
75
+ readonly latencyP99Ms?: number;
76
+ readonly throughputPerMin?: number;
77
+ readonly errorRate?: number;
78
+ readonly samplesTotal: number;
79
+ readonly samplesPassed?: number;
80
+ readonly details?: Record<string, unknown>;
81
+ }