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.
- package/FEATURES.md +169 -0
- package/package.json +119 -0
- package/src/agent.ts +187 -0
- package/src/agentic/index.ts +87 -0
- package/src/agentic/runner.ts +386 -0
- package/src/agentic/types.ts +91 -0
- package/src/artifacts/artifact.ts +417 -0
- package/src/artifacts/index.ts +42 -0
- package/src/artifacts/media.ts +304 -0
- package/src/cli/index.ts +122 -0
- package/src/core/base-agent.ts +151 -0
- package/src/core/context-builder.ts +106 -0
- package/src/core/index.ts +8 -0
- package/src/core/schemas.ts +17 -0
- package/src/core/types.ts +158 -0
- package/src/create-agent.ts +309 -0
- package/src/debug-logger.ts +188 -0
- package/src/dx/agent.ts +88 -0
- package/src/dx/define-agent.ts +183 -0
- package/src/dx/dev-logger.ts +57 -0
- package/src/dx/index.ts +11 -0
- package/src/errors.ts +175 -0
- package/src/execution/engine.ts +522 -0
- package/src/execution/graph-builder.ts +362 -0
- package/src/execution/index.ts +8 -0
- package/src/execution/types.ts +257 -0
- package/src/execution/worker-pool.ts +308 -0
- package/src/extensions/index.ts +123 -0
- package/src/guardrails/allowlist.ts +155 -0
- package/src/guardrails/index.ts +17 -0
- package/src/guardrails/types.ts +159 -0
- package/src/guardrails/validator.ts +265 -0
- package/src/index.ts +74 -0
- package/src/knowledge/index.ts +5 -0
- package/src/knowledge/types.ts +52 -0
- package/src/learning/in-memory-store.ts +72 -0
- package/src/learning/index.ts +6 -0
- package/src/learning/types.ts +42 -0
- package/src/llm/cache.ts +300 -0
- package/src/llm/index.ts +22 -0
- package/src/llm/model-resolver.ts +81 -0
- package/src/llm/openai-provider.ts +313 -0
- package/src/llm/openrouter-provider.ts +29 -0
- package/src/llm/types.ts +131 -0
- package/src/memory/in-memory-store.ts +255 -0
- package/src/memory/index.ts +7 -0
- package/src/memory/types.ts +193 -0
- package/src/memory/vector-store.ts +251 -0
- package/src/observability/console-logger.ts +123 -0
- package/src/observability/index.ts +12 -0
- package/src/observability/metrics.ts +85 -0
- package/src/observability/otlp-exporter.ts +417 -0
- package/src/observability/tracer.ts +105 -0
- package/src/observability/types.ts +341 -0
- package/src/orchestration/agent-adapter.ts +33 -0
- package/src/orchestration/index.ts +34 -0
- package/src/orchestration/load-balancer.ts +151 -0
- package/src/orchestration/mcp-types.ts +59 -0
- package/src/orchestration/message-bus.ts +192 -0
- package/src/orchestration/orchestrator.ts +349 -0
- package/src/orchestration/pipeline.ts +66 -0
- package/src/orchestration/supervisor.ts +107 -0
- package/src/orchestration/swarm.ts +1099 -0
- package/src/orchestration/toolkit.ts +47 -0
- package/src/orchestration/types.ts +339 -0
- package/src/planner/classical-planner.ts +383 -0
- package/src/planner/index.ts +8 -0
- package/src/planner/llm-planner.ts +353 -0
- package/src/planner/types.ts +227 -0
- package/src/planner/validator.ts +297 -0
- package/src/production/circuit-breaker.ts +290 -0
- package/src/production/graceful-shutdown.ts +251 -0
- package/src/production/health.ts +333 -0
- package/src/production/index.ts +57 -0
- package/src/production/latency-eval.ts +62 -0
- package/src/production/rate-limiter.ts +287 -0
- package/src/production/resumable-stream.ts +289 -0
- package/src/production/types.ts +81 -0
- package/src/sdk/index.ts +374 -0
- package/src/session/db-driver.ts +50 -0
- package/src/session/in-memory-store.ts +235 -0
- package/src/session/index.ts +12 -0
- package/src/session/sql-store.ts +315 -0
- package/src/session/sqlite-store.ts +61 -0
- package/src/session/types.ts +153 -0
- package/src/tools/base-tool.ts +223 -0
- package/src/tools/browser-tool.ts +123 -0
- package/src/tools/calculator-tool.ts +265 -0
- package/src/tools/file-tools.ts +394 -0
- package/src/tools/github-tool.ts +432 -0
- package/src/tools/hackernews-tool.ts +187 -0
- package/src/tools/http-tool.ts +118 -0
- package/src/tools/index.ts +99 -0
- package/src/tools/jira-tool.ts +373 -0
- package/src/tools/notion-tool.ts +322 -0
- package/src/tools/openai-tool.ts +236 -0
- package/src/tools/registry.ts +131 -0
- package/src/tools/serpapi-tool.ts +234 -0
- package/src/tools/shell-tool.ts +118 -0
- package/src/tools/slack-tool.ts +327 -0
- package/src/tools/telegram-tool.ts +127 -0
- package/src/tools/types.ts +229 -0
- package/src/tools/websearch-tool.ts +335 -0
- package/src/tools/wikipedia-tool.ts +177 -0
- package/src/tools/yfinance-tool.ts +33 -0
- package/src/voice/index.ts +17 -0
- package/src/voice/voice-provider.ts +228 -0
- package/tests/artifact.test.ts +241 -0
- package/tests/circuit-breaker.test.ts +171 -0
- package/tests/health.test.ts +192 -0
- package/tests/llm-cache.test.ts +186 -0
- package/tests/rate-limiter.test.ts +161 -0
- package/tsconfig.json +29 -0
- package/vitest.config.ts +47 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graceful Shutdown - Agno-style Production Lifecycle Management
|
|
3
|
+
*
|
|
4
|
+
* Handles process termination signals gracefully:
|
|
5
|
+
* - SIGTERM/SIGINT signal handling
|
|
6
|
+
* - In-flight request draining
|
|
7
|
+
* - Resource cleanup callbacks
|
|
8
|
+
* - Configurable shutdown timeout
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Logger } from '../observability/types.js';
|
|
12
|
+
|
|
13
|
+
/** Shutdown handler configuration */
|
|
14
|
+
export interface GracefulShutdownConfig {
|
|
15
|
+
/** Timeout for shutdown in ms (default: 30000) */
|
|
16
|
+
readonly timeoutMs?: number;
|
|
17
|
+
/** Signals to handle (default: ['SIGTERM', 'SIGINT']) */
|
|
18
|
+
readonly signals?: NodeJS.Signals[];
|
|
19
|
+
/** Optional logger */
|
|
20
|
+
readonly logger?: Logger;
|
|
21
|
+
/** Force exit after timeout (default: true) */
|
|
22
|
+
readonly forceExitOnTimeout?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Cleanup handler function */
|
|
26
|
+
export type CleanupHandler = () => Promise<void> | void;
|
|
27
|
+
|
|
28
|
+
/** Shutdown event */
|
|
29
|
+
export interface ShutdownEvent {
|
|
30
|
+
readonly signal?: NodeJS.Signals;
|
|
31
|
+
readonly reason?: string;
|
|
32
|
+
readonly timestamp: Date;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Graceful Shutdown Manager - handles process termination cleanly.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* const shutdown = new GracefulShutdown({ timeoutMs: 30000 });
|
|
40
|
+
*
|
|
41
|
+
* // Register cleanup handlers
|
|
42
|
+
* shutdown.addHandler('database', async () => {
|
|
43
|
+
* await db.close();
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* shutdown.addHandler('http-server', async () => {
|
|
47
|
+
* await server.close();
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
50
|
+
* // Start listening for signals
|
|
51
|
+
* shutdown.listen();
|
|
52
|
+
*/
|
|
53
|
+
export class GracefulShutdown {
|
|
54
|
+
private readonly config: Required<Omit<GracefulShutdownConfig, 'logger'>> &
|
|
55
|
+
Pick<GracefulShutdownConfig, 'logger'>;
|
|
56
|
+
private readonly handlers = new Map<string, CleanupHandler>();
|
|
57
|
+
private isShuttingDown = false;
|
|
58
|
+
private shutdownPromise: Promise<void> | null = null;
|
|
59
|
+
|
|
60
|
+
constructor(config: GracefulShutdownConfig = {}) {
|
|
61
|
+
this.config = {
|
|
62
|
+
timeoutMs: config.timeoutMs ?? 30_000,
|
|
63
|
+
signals: config.signals ?? ['SIGTERM', 'SIGINT'],
|
|
64
|
+
logger: config.logger,
|
|
65
|
+
forceExitOnTimeout: config.forceExitOnTimeout ?? true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Check if shutdown is in progress */
|
|
70
|
+
isInProgress(): boolean {
|
|
71
|
+
return this.isShuttingDown;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Add a cleanup handler */
|
|
75
|
+
addHandler(name: string, handler: CleanupHandler): void {
|
|
76
|
+
if (this.handlers.has(name)) {
|
|
77
|
+
this.log('warn', `Replacing existing handler: ${name}`);
|
|
78
|
+
}
|
|
79
|
+
this.handlers.set(name, handler);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Remove a cleanup handler */
|
|
83
|
+
removeHandler(name: string): boolean {
|
|
84
|
+
return this.handlers.delete(name);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Get registered handler names */
|
|
88
|
+
getHandlerNames(): string[] {
|
|
89
|
+
return Array.from(this.handlers.keys());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Start listening for shutdown signals.
|
|
94
|
+
* Only call this once.
|
|
95
|
+
*/
|
|
96
|
+
listen(): void {
|
|
97
|
+
for (const signal of this.config.signals) {
|
|
98
|
+
process.on(signal, () => {
|
|
99
|
+
this.log('info', `Received ${signal}, initiating graceful shutdown...`);
|
|
100
|
+
this.shutdown({ signal, timestamp: new Date() });
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.log('debug', `Listening for signals: ${this.config.signals.join(', ')}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Programmatically trigger shutdown.
|
|
109
|
+
* Safe to call multiple times - subsequent calls return the same promise.
|
|
110
|
+
*/
|
|
111
|
+
async shutdown(event?: Partial<ShutdownEvent>): Promise<void> {
|
|
112
|
+
if (this.shutdownPromise) {
|
|
113
|
+
return this.shutdownPromise;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.isShuttingDown = true;
|
|
117
|
+
const shutdownEvent: ShutdownEvent = {
|
|
118
|
+
signal: event?.signal,
|
|
119
|
+
reason: event?.reason,
|
|
120
|
+
timestamp: event?.timestamp ?? new Date(),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
this.shutdownPromise = this.executeShutdown(shutdownEvent);
|
|
124
|
+
return this.shutdownPromise;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Wait for shutdown to complete (for tests or manual control).
|
|
129
|
+
*/
|
|
130
|
+
async waitForShutdown(): Promise<void> {
|
|
131
|
+
if (this.shutdownPromise) {
|
|
132
|
+
await this.shutdownPromise;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// --- Private methods ---
|
|
137
|
+
|
|
138
|
+
private async executeShutdown(event: ShutdownEvent): Promise<void> {
|
|
139
|
+
this.log('info', 'Starting graceful shutdown...');
|
|
140
|
+
|
|
141
|
+
const timeout = this.createTimeout();
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
await Promise.race([
|
|
145
|
+
this.runHandlers(),
|
|
146
|
+
timeout.promise,
|
|
147
|
+
]);
|
|
148
|
+
|
|
149
|
+
this.log('info', 'Graceful shutdown completed');
|
|
150
|
+
} catch (error) {
|
|
151
|
+
this.log('error', `Shutdown error: ${error instanceof Error ? error.message : error}`);
|
|
152
|
+
} finally {
|
|
153
|
+
timeout.clear();
|
|
154
|
+
|
|
155
|
+
if (this.config.forceExitOnTimeout) {
|
|
156
|
+
// Give a moment for logs to flush
|
|
157
|
+
setTimeout(() => {
|
|
158
|
+
process.exit(event.signal === 'SIGTERM' ? 0 : 1);
|
|
159
|
+
}, 100);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private async runHandlers(): Promise<void> {
|
|
165
|
+
const handlerEntries = Array.from(this.handlers.entries());
|
|
166
|
+
const results: { name: string; success: boolean; error?: Error }[] = [];
|
|
167
|
+
|
|
168
|
+
// Run handlers in parallel
|
|
169
|
+
await Promise.all(
|
|
170
|
+
handlerEntries.map(async ([name, handler]) => {
|
|
171
|
+
const startTime = Date.now();
|
|
172
|
+
try {
|
|
173
|
+
await handler();
|
|
174
|
+
this.log('debug', `Handler '${name}' completed in ${Date.now() - startTime}ms`);
|
|
175
|
+
results.push({ name, success: true });
|
|
176
|
+
} catch (error) {
|
|
177
|
+
this.log('error', `Handler '${name}' failed: ${error instanceof Error ? error.message : error}`);
|
|
178
|
+
results.push({ name, success: false, error: error as Error });
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const failed = results.filter(r => !r.success);
|
|
184
|
+
if (failed.length > 0) {
|
|
185
|
+
this.log('warn', `${failed.length}/${results.length} handlers failed during shutdown`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private createTimeout(): { promise: Promise<never>; clear: () => void } {
|
|
190
|
+
let timeoutId: NodeJS.Timeout;
|
|
191
|
+
|
|
192
|
+
const promise = new Promise<never>((_, reject) => {
|
|
193
|
+
timeoutId = setTimeout(() => {
|
|
194
|
+
this.log('warn', `Shutdown timeout (${this.config.timeoutMs}ms) reached, forcing exit`);
|
|
195
|
+
reject(new Error('Shutdown timeout'));
|
|
196
|
+
}, this.config.timeoutMs);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
promise,
|
|
201
|
+
clear: () => clearTimeout(timeoutId),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private log(level: 'debug' | 'info' | 'warn' | 'error', message: string): void {
|
|
206
|
+
if (this.config.logger) {
|
|
207
|
+
this.config.logger[level](message);
|
|
208
|
+
} else {
|
|
209
|
+
const prefix = `[GracefulShutdown] [${level.toUpperCase()}]`;
|
|
210
|
+
if (level === 'error') {
|
|
211
|
+
console.error(prefix, message);
|
|
212
|
+
} else if (level === 'warn') {
|
|
213
|
+
console.warn(prefix, message);
|
|
214
|
+
} else {
|
|
215
|
+
console.log(prefix, message);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Create a simple shutdown manager with common defaults.
|
|
223
|
+
*/
|
|
224
|
+
export function createGracefulShutdown(
|
|
225
|
+
handlers?: Record<string, CleanupHandler>,
|
|
226
|
+
config?: GracefulShutdownConfig
|
|
227
|
+
): GracefulShutdown {
|
|
228
|
+
const shutdown = new GracefulShutdown(config);
|
|
229
|
+
|
|
230
|
+
if (handlers) {
|
|
231
|
+
for (const [name, handler] of Object.entries(handlers)) {
|
|
232
|
+
shutdown.addHandler(name, handler);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return shutdown;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Wrap an async operation with shutdown awareness.
|
|
241
|
+
* Will reject if shutdown is in progress.
|
|
242
|
+
*/
|
|
243
|
+
export function withShutdownGuard<T>(
|
|
244
|
+
shutdown: GracefulShutdown,
|
|
245
|
+
fn: () => Promise<T>
|
|
246
|
+
): Promise<T> {
|
|
247
|
+
if (shutdown.isInProgress()) {
|
|
248
|
+
return Promise.reject(new Error('Operation rejected: shutdown in progress'));
|
|
249
|
+
}
|
|
250
|
+
return fn();
|
|
251
|
+
}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health Check System - Agno-style AgentOS Production Probes
|
|
3
|
+
*
|
|
4
|
+
* Kubernetes-compatible health check system with:
|
|
5
|
+
* - Liveness probe (is the process alive)
|
|
6
|
+
* - Readiness probe (can accept traffic)
|
|
7
|
+
* - Dependency health checks (LLM, database, etc.)
|
|
8
|
+
* - Aggregated health status
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { LLMProvider } from '../llm/types.js';
|
|
12
|
+
import type { SessionStore } from '../session/types.js';
|
|
13
|
+
|
|
14
|
+
/** Health check status */
|
|
15
|
+
export enum HealthStatus {
|
|
16
|
+
/** Everything is working */
|
|
17
|
+
HEALTHY = 'HEALTHY',
|
|
18
|
+
/** Some components degraded but still functional */
|
|
19
|
+
DEGRADED = 'DEGRADED',
|
|
20
|
+
/** Critical failure, not accepting traffic */
|
|
21
|
+
UNHEALTHY = 'UNHEALTHY',
|
|
22
|
+
/** Status unknown (check not yet run) */
|
|
23
|
+
UNKNOWN = 'UNKNOWN',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Individual component health result */
|
|
27
|
+
export interface ComponentHealth {
|
|
28
|
+
readonly name: string;
|
|
29
|
+
readonly status: HealthStatus;
|
|
30
|
+
readonly message?: string;
|
|
31
|
+
readonly latencyMs?: number;
|
|
32
|
+
readonly lastCheckedAt: Date;
|
|
33
|
+
readonly metadata?: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Aggregated health check result */
|
|
37
|
+
export interface HealthCheckResult {
|
|
38
|
+
readonly status: HealthStatus;
|
|
39
|
+
readonly uptime: number;
|
|
40
|
+
readonly version?: string;
|
|
41
|
+
readonly components: ComponentHealth[];
|
|
42
|
+
readonly timestamp: Date;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Health check configuration */
|
|
46
|
+
export interface HealthCheckConfig {
|
|
47
|
+
/** Application version (optional, for display) */
|
|
48
|
+
readonly version?: string;
|
|
49
|
+
/** Timeout for individual checks in ms (default: 5000) */
|
|
50
|
+
readonly checkTimeoutMs?: number;
|
|
51
|
+
/** Components to check */
|
|
52
|
+
readonly components?: HealthComponent[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** A component that can be health-checked */
|
|
56
|
+
export interface HealthComponent {
|
|
57
|
+
readonly name: string;
|
|
58
|
+
check(): Promise<Omit<ComponentHealth, 'name' | 'lastCheckedAt'>>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Health Check Manager - aggregates component health checks.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* const health = new HealthCheckManager({
|
|
66
|
+
* version: '1.0.0',
|
|
67
|
+
* components: [
|
|
68
|
+
* createLLMHealthCheck(llmProvider),
|
|
69
|
+
* createSessionStoreHealthCheck(sessionStore),
|
|
70
|
+
* ],
|
|
71
|
+
* });
|
|
72
|
+
*
|
|
73
|
+
* // Use in HTTP endpoint
|
|
74
|
+
* app.get('/health', async (req, res) => {
|
|
75
|
+
* const result = await health.check();
|
|
76
|
+
* res.status(result.status === 'HEALTHY' ? 200 : 503).json(result);
|
|
77
|
+
* });
|
|
78
|
+
*/
|
|
79
|
+
export class HealthCheckManager {
|
|
80
|
+
private readonly config: Required<Omit<HealthCheckConfig, 'version'>> &
|
|
81
|
+
Pick<HealthCheckConfig, 'version'>;
|
|
82
|
+
private readonly startTime = Date.now();
|
|
83
|
+
private lastResult: HealthCheckResult | null = null;
|
|
84
|
+
|
|
85
|
+
constructor(config: HealthCheckConfig = {}) {
|
|
86
|
+
this.config = {
|
|
87
|
+
version: config.version,
|
|
88
|
+
checkTimeoutMs: config.checkTimeoutMs ?? 5_000,
|
|
89
|
+
components: config.components ?? [],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Add a health component */
|
|
94
|
+
addComponent(component: HealthComponent): void {
|
|
95
|
+
this.config.components.push(component);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Remove a health component by name */
|
|
99
|
+
removeComponent(name: string): void {
|
|
100
|
+
const index = this.config.components.findIndex(c => c.name === name);
|
|
101
|
+
if (index !== -1) {
|
|
102
|
+
this.config.components.splice(index, 1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Get uptime in seconds */
|
|
107
|
+
getUptime(): number {
|
|
108
|
+
return Math.floor((Date.now() - this.startTime) / 1000);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Get last health check result (if any) */
|
|
112
|
+
getLastResult(): HealthCheckResult | null {
|
|
113
|
+
return this.lastResult;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Liveness probe - is the process alive?
|
|
118
|
+
* Returns immediately, no component checks.
|
|
119
|
+
*/
|
|
120
|
+
liveness(): { status: HealthStatus; uptime: number } {
|
|
121
|
+
return {
|
|
122
|
+
status: HealthStatus.HEALTHY,
|
|
123
|
+
uptime: this.getUptime(),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Readiness probe - can the service accept traffic?
|
|
129
|
+
* Checks all components and returns aggregated status.
|
|
130
|
+
*/
|
|
131
|
+
async readiness(): Promise<HealthCheckResult> {
|
|
132
|
+
return this.check();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Full health check - check all components.
|
|
137
|
+
*/
|
|
138
|
+
async check(): Promise<HealthCheckResult> {
|
|
139
|
+
const timestamp = new Date();
|
|
140
|
+
const components: ComponentHealth[] = [];
|
|
141
|
+
|
|
142
|
+
for (const component of this.config.components) {
|
|
143
|
+
const startTime = Date.now();
|
|
144
|
+
try {
|
|
145
|
+
const result = await Promise.race([
|
|
146
|
+
component.check(),
|
|
147
|
+
this.timeout(this.config.checkTimeoutMs),
|
|
148
|
+
]);
|
|
149
|
+
|
|
150
|
+
if (result === 'TIMEOUT') {
|
|
151
|
+
components.push({
|
|
152
|
+
name: component.name,
|
|
153
|
+
status: HealthStatus.UNHEALTHY,
|
|
154
|
+
message: 'Health check timed out',
|
|
155
|
+
latencyMs: this.config.checkTimeoutMs,
|
|
156
|
+
lastCheckedAt: new Date(),
|
|
157
|
+
});
|
|
158
|
+
} else {
|
|
159
|
+
components.push({
|
|
160
|
+
...result,
|
|
161
|
+
name: component.name,
|
|
162
|
+
latencyMs: Date.now() - startTime,
|
|
163
|
+
lastCheckedAt: new Date(),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
components.push({
|
|
168
|
+
name: component.name,
|
|
169
|
+
status: HealthStatus.UNHEALTHY,
|
|
170
|
+
message: error instanceof Error ? error.message : String(error),
|
|
171
|
+
latencyMs: Date.now() - startTime,
|
|
172
|
+
lastCheckedAt: new Date(),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Aggregate status
|
|
178
|
+
const status = this.aggregateStatus(components);
|
|
179
|
+
|
|
180
|
+
this.lastResult = {
|
|
181
|
+
status,
|
|
182
|
+
uptime: this.getUptime(),
|
|
183
|
+
version: this.config.version,
|
|
184
|
+
components,
|
|
185
|
+
timestamp,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
return this.lastResult;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private aggregateStatus(components: ComponentHealth[]): HealthStatus {
|
|
192
|
+
if (components.length === 0) {
|
|
193
|
+
return HealthStatus.HEALTHY;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const hasUnhealthy = components.some(c => c.status === HealthStatus.UNHEALTHY);
|
|
197
|
+
const hasDegraded = components.some(c => c.status === HealthStatus.DEGRADED);
|
|
198
|
+
|
|
199
|
+
if (hasUnhealthy) return HealthStatus.UNHEALTHY;
|
|
200
|
+
if (hasDegraded) return HealthStatus.DEGRADED;
|
|
201
|
+
return HealthStatus.HEALTHY;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private timeout(ms: number): Promise<'TIMEOUT'> {
|
|
205
|
+
return new Promise(resolve => setTimeout(() => resolve('TIMEOUT'), ms));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// --- Built-in Health Components ---
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Create a health check for an LLM provider.
|
|
213
|
+
* Performs a minimal completion to verify connectivity.
|
|
214
|
+
*/
|
|
215
|
+
export function createLLMHealthCheck(
|
|
216
|
+
llm: LLMProvider,
|
|
217
|
+
name = 'llm'
|
|
218
|
+
): HealthComponent {
|
|
219
|
+
return {
|
|
220
|
+
name,
|
|
221
|
+
async check() {
|
|
222
|
+
try {
|
|
223
|
+
// Minimal test call
|
|
224
|
+
await llm.generateText([
|
|
225
|
+
{ role: 'user', content: 'test' },
|
|
226
|
+
], { maxTokens: 1 });
|
|
227
|
+
|
|
228
|
+
return { status: HealthStatus.HEALTHY };
|
|
229
|
+
} catch (error) {
|
|
230
|
+
return {
|
|
231
|
+
status: HealthStatus.UNHEALTHY,
|
|
232
|
+
message: error instanceof Error ? error.message : 'LLM check failed',
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Create a health check for a session store.
|
|
241
|
+
* Performs a read operation to verify connectivity.
|
|
242
|
+
*/
|
|
243
|
+
export function createSessionStoreHealthCheck(
|
|
244
|
+
store: SessionStore,
|
|
245
|
+
name = 'session-store'
|
|
246
|
+
): HealthComponent {
|
|
247
|
+
return {
|
|
248
|
+
name,
|
|
249
|
+
async check() {
|
|
250
|
+
try {
|
|
251
|
+
// Try to get a non-existent session (should return null, not error)
|
|
252
|
+
await store.get('__health_check_probe__');
|
|
253
|
+
return { status: HealthStatus.HEALTHY };
|
|
254
|
+
} catch (error) {
|
|
255
|
+
return {
|
|
256
|
+
status: HealthStatus.UNHEALTHY,
|
|
257
|
+
message: error instanceof Error ? error.message : 'Session store check failed',
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Create a custom health check from an async function.
|
|
266
|
+
*/
|
|
267
|
+
export function createCustomHealthCheck(
|
|
268
|
+
name: string,
|
|
269
|
+
checkFn: () => Promise<boolean | { status: HealthStatus; message?: string }>
|
|
270
|
+
): HealthComponent {
|
|
271
|
+
return {
|
|
272
|
+
name,
|
|
273
|
+
async check() {
|
|
274
|
+
try {
|
|
275
|
+
const result = await checkFn();
|
|
276
|
+
if (typeof result === 'boolean') {
|
|
277
|
+
return {
|
|
278
|
+
status: result ? HealthStatus.HEALTHY : HealthStatus.UNHEALTHY,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
return result;
|
|
282
|
+
} catch (error) {
|
|
283
|
+
return {
|
|
284
|
+
status: HealthStatus.UNHEALTHY,
|
|
285
|
+
message: error instanceof Error ? error.message : 'Check failed',
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Create an HTTP health check for external dependencies.
|
|
294
|
+
*/
|
|
295
|
+
export function createHttpHealthCheck(
|
|
296
|
+
name: string,
|
|
297
|
+
url: string,
|
|
298
|
+
options?: { method?: string; expectedStatus?: number; timeoutMs?: number }
|
|
299
|
+
): HealthComponent {
|
|
300
|
+
const { method = 'GET', expectedStatus = 200, timeoutMs = 5000 } = options ?? {};
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
name,
|
|
304
|
+
async check() {
|
|
305
|
+
try {
|
|
306
|
+
const controller = new AbortController();
|
|
307
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
308
|
+
|
|
309
|
+
const response = await fetch(url, {
|
|
310
|
+
method,
|
|
311
|
+
signal: controller.signal,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
clearTimeout(timeout);
|
|
315
|
+
|
|
316
|
+
if (response.status === expectedStatus) {
|
|
317
|
+
return { status: HealthStatus.HEALTHY };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
status: HealthStatus.DEGRADED,
|
|
322
|
+
message: `Unexpected status: ${response.status}`,
|
|
323
|
+
metadata: { statusCode: response.status },
|
|
324
|
+
};
|
|
325
|
+
} catch (error) {
|
|
326
|
+
return {
|
|
327
|
+
status: HealthStatus.UNHEALTHY,
|
|
328
|
+
message: error instanceof Error ? error.message : 'HTTP check failed',
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Production module: runtime, control plane, evals, resilience.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export * from './types.js';
|
|
6
|
+
export { createLatencyEval } from './latency-eval.js';
|
|
7
|
+
export type { LatencyEvalConfig } from './latency-eval.js';
|
|
8
|
+
|
|
9
|
+
// Resilience patterns
|
|
10
|
+
export {
|
|
11
|
+
CircuitBreaker,
|
|
12
|
+
CircuitState,
|
|
13
|
+
CircuitOpenError,
|
|
14
|
+
createLLMCircuitBreaker,
|
|
15
|
+
} from './circuit-breaker.js';
|
|
16
|
+
export type { CircuitBreakerConfig, CircuitBreakerResult } from './circuit-breaker.js';
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
RateLimiter,
|
|
20
|
+
RateLimitError,
|
|
21
|
+
createOpenAIRateLimiter,
|
|
22
|
+
} from './rate-limiter.js';
|
|
23
|
+
export type { RateLimiterConfig } from './rate-limiter.js';
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
HealthCheckManager,
|
|
27
|
+
HealthStatus,
|
|
28
|
+
createLLMHealthCheck,
|
|
29
|
+
createSessionStoreHealthCheck,
|
|
30
|
+
createCustomHealthCheck,
|
|
31
|
+
createHttpHealthCheck,
|
|
32
|
+
} from './health.js';
|
|
33
|
+
export type {
|
|
34
|
+
HealthCheckConfig,
|
|
35
|
+
HealthCheckResult,
|
|
36
|
+
HealthComponent,
|
|
37
|
+
ComponentHealth,
|
|
38
|
+
} from './health.js';
|
|
39
|
+
|
|
40
|
+
export {
|
|
41
|
+
GracefulShutdown,
|
|
42
|
+
createGracefulShutdown,
|
|
43
|
+
withShutdownGuard,
|
|
44
|
+
} from './graceful-shutdown.js';
|
|
45
|
+
export type { GracefulShutdownConfig, CleanupHandler, ShutdownEvent } from './graceful-shutdown.js';
|
|
46
|
+
|
|
47
|
+
// Resumable Streaming (VoltAgent-style)
|
|
48
|
+
export {
|
|
49
|
+
ResumableStreamManager,
|
|
50
|
+
formatSSE,
|
|
51
|
+
createResumableStream,
|
|
52
|
+
} from './resumable-stream.js';
|
|
53
|
+
export type {
|
|
54
|
+
StreamCheckpoint,
|
|
55
|
+
ResumableStreamConfig,
|
|
56
|
+
StreamChunkSSE,
|
|
57
|
+
} from './resumable-stream.js';
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Latency eval: measure p50/p95/p99 and throughput for agent runs.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { EvalSuite, EvalRunOptions, EvalResult, EvalSample } from './types.js';
|
|
6
|
+
import type { RunnableHighLevelAgent } from '../extensions/index.js';
|
|
7
|
+
|
|
8
|
+
export interface LatencyEvalConfig {
|
|
9
|
+
readonly suiteId?: string;
|
|
10
|
+
readonly name?: string;
|
|
11
|
+
/** Agent to eval (must have run(prompt)) */
|
|
12
|
+
readonly agent: RunnableHighLevelAgent;
|
|
13
|
+
/** Samples: input prompt per run */
|
|
14
|
+
readonly samples: EvalSample[];
|
|
15
|
+
/** Concurrency (default 1) */
|
|
16
|
+
readonly concurrency?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a latency eval suite: runs each sample, records latency, returns p50/p95/p99 and throughput.
|
|
21
|
+
*/
|
|
22
|
+
export function createLatencyEval(config: LatencyEvalConfig): EvalSuite {
|
|
23
|
+
const suiteId = config.suiteId ?? `latency-${Date.now()}`;
|
|
24
|
+
const name = config.name ?? 'Latency Eval';
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
id: suiteId,
|
|
28
|
+
name,
|
|
29
|
+
async run(options?: EvalRunOptions): Promise<EvalResult> {
|
|
30
|
+
const samples = (options?.dataset ?? config.samples).slice(0, options?.maxSamples);
|
|
31
|
+
const times: number[] = [];
|
|
32
|
+
|
|
33
|
+
for (const sample of samples) {
|
|
34
|
+
const input = typeof sample.input === 'string' ? sample.input : (sample.input?.prompt as string) ?? '';
|
|
35
|
+
const start = Date.now();
|
|
36
|
+
try {
|
|
37
|
+
await config.agent.run(input);
|
|
38
|
+
} catch {
|
|
39
|
+
// still record latency
|
|
40
|
+
}
|
|
41
|
+
times.push(Date.now() - start);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
times.sort((a, b) => a - b);
|
|
45
|
+
const n = times.length;
|
|
46
|
+
const sum = times.reduce((a, b) => a + b, 0);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
suiteId,
|
|
50
|
+
latencyMs: n > 0 ? sum / n : 0,
|
|
51
|
+
latencyP95Ms: n > 0 ? times[Math.min(Math.ceil(n * 0.95) - 1, n - 1)] : undefined,
|
|
52
|
+
latencyP99Ms: n > 0 ? times[Math.min(Math.ceil(n * 0.99) - 1, n - 1)] : undefined,
|
|
53
|
+
samplesTotal: n,
|
|
54
|
+
details: {
|
|
55
|
+
p50: n > 0 ? times[Math.floor(n * 0.5)] : undefined,
|
|
56
|
+
min: n > 0 ? times[0] : undefined,
|
|
57
|
+
max: n > 0 ? times[n - 1] : undefined,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|