@stackbilt/aegis-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/package.json +96 -0
- package/schema.sql +586 -0
- package/src/adapters/voice/cloudflare-agent.ts +34 -0
- package/src/auth.ts +124 -0
- package/src/bluesky.ts +464 -0
- package/src/claude-tools/content.ts +188 -0
- package/src/claude-tools/email.ts +69 -0
- package/src/claude-tools/github.ts +440 -0
- package/src/claude-tools/goals.ts +116 -0
- package/src/claude-tools/index.ts +353 -0
- package/src/claude-tools/web.ts +59 -0
- package/src/claude.ts +406 -0
- package/src/codebeast.ts +200 -0
- package/src/composite.ts +715 -0
- package/src/content/column.ts +80 -0
- package/src/content/hero-image.ts +47 -0
- package/src/content/index.ts +27 -0
- package/src/content/journal.ts +91 -0
- package/src/content/roundtable.ts +163 -0
- package/src/core.ts +309 -0
- package/src/dashboard.ts +620 -0
- package/src/decision-docs.ts +284 -0
- package/src/dispatch.ts +13 -0
- package/src/edge-env.ts +58 -0
- package/src/email.ts +850 -0
- package/src/exports.ts +156 -0
- package/src/github-projects.ts +312 -0
- package/src/github.ts +670 -0
- package/src/groq.ts +247 -0
- package/src/health-page.ts +578 -0
- package/src/index.ts +89 -0
- package/src/kernel/argus-actions.ts +397 -0
- package/src/kernel/argus-correlation.ts +639 -0
- package/src/kernel/board.ts +91 -0
- package/src/kernel/briefing.ts +177 -0
- package/src/kernel/classify-memory-topic.ts +166 -0
- package/src/kernel/cognition.ts +377 -0
- package/src/kernel/court-cards.ts +163 -0
- package/src/kernel/dispatch.ts +587 -0
- package/src/kernel/domain.ts +50 -0
- package/src/kernel/dynamic-tools.ts +322 -0
- package/src/kernel/executor-port.ts +45 -0
- package/src/kernel/executors/claude.ts +73 -0
- package/src/kernel/executors/direct.ts +237 -0
- package/src/kernel/executors/groq.ts +18 -0
- package/src/kernel/executors/index.ts +87 -0
- package/src/kernel/executors/tarotscript.ts +104 -0
- package/src/kernel/executors/workers-ai.ts +54 -0
- package/src/kernel/insight-cache.ts +76 -0
- package/src/kernel/memory/agenda.ts +200 -0
- package/src/kernel/memory/blocks.ts +188 -0
- package/src/kernel/memory/consolidation.ts +194 -0
- package/src/kernel/memory/episodic.ts +241 -0
- package/src/kernel/memory/goals.ts +156 -0
- package/src/kernel/memory/graph.ts +290 -0
- package/src/kernel/memory/index.ts +11 -0
- package/src/kernel/memory/insights.ts +316 -0
- package/src/kernel/memory/procedural.ts +467 -0
- package/src/kernel/memory/pruning.ts +67 -0
- package/src/kernel/memory/recall.ts +367 -0
- package/src/kernel/memory/semantic.ts +315 -0
- package/src/kernel/memory/synthesis.ts +161 -0
- package/src/kernel/memory-adapter.ts +369 -0
- package/src/kernel/memory-guardrails.ts +76 -0
- package/src/kernel/port.ts +23 -0
- package/src/kernel/resilience.ts +322 -0
- package/src/kernel/router.ts +471 -0
- package/src/kernel/scheduled/agent-dispatch.ts +252 -0
- package/src/kernel/scheduled/argus-analytics.ts +247 -0
- package/src/kernel/scheduled/argus-heartbeat.ts +320 -0
- package/src/kernel/scheduled/argus-notify.ts +348 -0
- package/src/kernel/scheduled/board-sync.ts +110 -0
- package/src/kernel/scheduled/ci-watcher.ts +125 -0
- package/src/kernel/scheduled/cognitive-metrics.ts +377 -0
- package/src/kernel/scheduled/consolidation.ts +229 -0
- package/src/kernel/scheduled/content-drip.ts +47 -0
- package/src/kernel/scheduled/content.ts +6 -0
- package/src/kernel/scheduled/conversation-facts.ts +204 -0
- package/src/kernel/scheduled/cost-report.ts +84 -0
- package/src/kernel/scheduled/curiosity.ts +219 -0
- package/src/kernel/scheduled/dev-activity.ts +44 -0
- package/src/kernel/scheduled/digest.ts +317 -0
- package/src/kernel/scheduled/dreaming/agenda-triage.ts +115 -0
- package/src/kernel/scheduled/dreaming/facts.ts +239 -0
- package/src/kernel/scheduled/dreaming/index.ts +8 -0
- package/src/kernel/scheduled/dreaming/llm.ts +33 -0
- package/src/kernel/scheduled/dreaming/pattern-synthesis.ts +124 -0
- package/src/kernel/scheduled/dreaming/persona.ts +75 -0
- package/src/kernel/scheduled/dreaming/symbolic.ts +31 -0
- package/src/kernel/scheduled/dreaming/task-proposals.ts +80 -0
- package/src/kernel/scheduled/dreaming.ts +66 -0
- package/src/kernel/scheduled/entropy.ts +149 -0
- package/src/kernel/scheduled/escalation.ts +192 -0
- package/src/kernel/scheduled/feed-watcher.ts +206 -0
- package/src/kernel/scheduled/goals.ts +214 -0
- package/src/kernel/scheduled/governance.ts +41 -0
- package/src/kernel/scheduled/heartbeat.ts +220 -0
- package/src/kernel/scheduled/inbox-processor.ts +174 -0
- package/src/kernel/scheduled/index.ts +245 -0
- package/src/kernel/scheduled/issue-proposer.ts +478 -0
- package/src/kernel/scheduled/issue-watcher.ts +128 -0
- package/src/kernel/scheduled/pr-automerge.ts +213 -0
- package/src/kernel/scheduled/product-health.ts +107 -0
- package/src/kernel/scheduled/reflection.ts +373 -0
- package/src/kernel/scheduled/self-improvement.ts +114 -0
- package/src/kernel/scheduled/social-engage.ts +175 -0
- package/src/kernel/scheduled/task-audit.ts +60 -0
- package/src/kernel/symbolic.ts +156 -0
- package/src/kernel/types.ts +145 -0
- package/src/landing.ts +1190 -0
- package/src/lib/audit-chain/chain.ts +28 -0
- package/src/lib/audit-chain/types.ts +12 -0
- package/src/lib/observability/errors.ts +55 -0
- package/src/markdown.ts +164 -0
- package/src/mcp/handlers.ts +647 -0
- package/src/mcp/server.ts +184 -0
- package/src/mcp/tools.ts +316 -0
- package/src/mcp-client.ts +275 -0
- package/src/mcp-server.ts +2 -0
- package/src/operator/config.example.ts +60 -0
- package/src/operator/config.ts +60 -0
- package/src/operator/index.ts +46 -0
- package/src/operator/persona.example.ts +34 -0
- package/src/operator/persona.ts +34 -0
- package/src/operator/prompt-builder.ts +190 -0
- package/src/operator/types.ts +43 -0
- package/src/pulse.ts +1179 -0
- package/src/routes/bluesky.ts +116 -0
- package/src/routes/cc-tasks.ts +328 -0
- package/src/routes/codebeast.ts +1 -0
- package/src/routes/content.ts +194 -0
- package/src/routes/conversations.ts +25 -0
- package/src/routes/dynamic-tools.ts +111 -0
- package/src/routes/feedback.ts +192 -0
- package/src/routes/health.ts +147 -0
- package/src/routes/messages.ts +228 -0
- package/src/routes/observability.ts +82 -0
- package/src/routes/operator-logs.ts +42 -0
- package/src/routes/pages.ts +96 -0
- package/src/routes/sessions.ts +54 -0
- package/src/sanitize.ts +73 -0
- package/src/schema-enums.ts +155 -0
- package/src/search.ts +112 -0
- package/src/task-intelligence.ts +497 -0
- package/src/types.ts +194 -0
- package/src/ui.ts +5 -0
- package/src/version.ts +3 -0
- package/src/workers-ai-chat.ts +333 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resilience primitives — circuit breakers, cost tracking, retry/fallback
|
|
3
|
+
* combinators. Delegates to @stackbilt/llm-providers for breaker + cost
|
|
4
|
+
* singletons shared across a process.
|
|
5
|
+
*
|
|
6
|
+
* Core exports primitives + a factory (`createResilience`) that instantiates
|
|
7
|
+
* a cost-tracker / ledger / circuit-registry triple with consumer-specified
|
|
8
|
+
* budgets. Module-level singletons live in consumers (e.g. the daemon), not
|
|
9
|
+
* here — core is generic and doesn't know anyone's monthly budget.
|
|
10
|
+
*
|
|
11
|
+
* Exhaustion tracking uses llm-providers' `defaultExhaustionRegistry` —
|
|
12
|
+
* one global exhaustion state per process is correct (rate limits are a
|
|
13
|
+
* global property of the upstream provider, not per-consumer).
|
|
14
|
+
*
|
|
15
|
+
* Usage (consumer):
|
|
16
|
+
* import { createResilience, withRetry, withFallback, resilient } from '@stackbilt/aegis-core/kernel/resilience';
|
|
17
|
+
* const { circuits, costs, ledger } = createResilience({
|
|
18
|
+
* budgets: [{ provider: 'anthropic', monthlyBudget: 20 }],
|
|
19
|
+
* });
|
|
20
|
+
* const result = await circuits.exec('groq', () => askGroq(...));
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
CircuitBreakerManager,
|
|
25
|
+
CircuitBreakerOpenError,
|
|
26
|
+
defaultCircuitBreakerManager,
|
|
27
|
+
CostTracker as LlmCostTracker,
|
|
28
|
+
CreditLedger,
|
|
29
|
+
QuotaExceededError,
|
|
30
|
+
RateLimitError,
|
|
31
|
+
LLMProviderError,
|
|
32
|
+
ExhaustionRegistry,
|
|
33
|
+
defaultExhaustionRegistry,
|
|
34
|
+
noopHooks,
|
|
35
|
+
composeHooks,
|
|
36
|
+
defaultLatencyHistogram,
|
|
37
|
+
} from '@stackbilt/llm-providers';
|
|
38
|
+
import type {
|
|
39
|
+
CircuitBreakerConfig as LlmCircuitBreakerConfig,
|
|
40
|
+
CircuitBreakerState,
|
|
41
|
+
LLMResponse,
|
|
42
|
+
CreditLedgerSnapshot,
|
|
43
|
+
LedgerEvent,
|
|
44
|
+
ObservabilityHooks,
|
|
45
|
+
LatencyHistogram,
|
|
46
|
+
} from '@stackbilt/llm-providers';
|
|
47
|
+
|
|
48
|
+
// ─── Re-exports ─────────────────────────────────────────────
|
|
49
|
+
// Consumers import from here, not from llm-providers directly.
|
|
50
|
+
|
|
51
|
+
export { CircuitBreakerOpenError, QuotaExceededError, RateLimitError, LLMProviderError };
|
|
52
|
+
export { ExhaustionRegistry, defaultExhaustionRegistry, noopHooks, composeHooks, defaultLatencyHistogram };
|
|
53
|
+
export type { CircuitBreakerState, ObservabilityHooks, LatencyHistogram, LedgerEvent, CreditLedgerSnapshot };
|
|
54
|
+
export { CreditLedger };
|
|
55
|
+
|
|
56
|
+
// Keep the AEGIS-native config shape. Property names differ from llm-providers
|
|
57
|
+
// (resetTimeoutMs vs resetTimeout) for readability at call sites — the adapter
|
|
58
|
+
// below maps them internally.
|
|
59
|
+
export interface CircuitBreakerConfig {
|
|
60
|
+
failureThreshold: number;
|
|
61
|
+
resetTimeoutMs: number;
|
|
62
|
+
windowMs: number;
|
|
63
|
+
minRequests: number;
|
|
64
|
+
degradationCurve: number[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type CircuitState = 'CLOSED' | 'DEGRADED' | 'OPEN' | 'RECOVERING';
|
|
68
|
+
|
|
69
|
+
function adaptConfig(cfg?: Partial<CircuitBreakerConfig>): Partial<LlmCircuitBreakerConfig> | undefined {
|
|
70
|
+
if (!cfg) return undefined;
|
|
71
|
+
return {
|
|
72
|
+
failureThreshold: cfg.failureThreshold,
|
|
73
|
+
resetTimeout: cfg.resetTimeoutMs,
|
|
74
|
+
monitoringPeriod: cfg.windowMs,
|
|
75
|
+
minRequests: cfg.minRequests,
|
|
76
|
+
degradationCurve: cfg.degradationCurve,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Backwards-compatible alias (pre-llm-providers name). */
|
|
81
|
+
export const CircuitOpenError = CircuitBreakerOpenError;
|
|
82
|
+
|
|
83
|
+
// ─── Circuit Registry ───────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
export class CircuitRegistry {
|
|
86
|
+
private readonly mgr: CircuitBreakerManager;
|
|
87
|
+
constructor(mgr: CircuitBreakerManager) { this.mgr = mgr; }
|
|
88
|
+
|
|
89
|
+
get(name: string, config?: Partial<CircuitBreakerConfig>) {
|
|
90
|
+
return this.mgr.getBreaker(name, adaptConfig(config));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async exec<T>(name: string, op: () => Promise<T>, config?: Partial<CircuitBreakerConfig>): Promise<T> {
|
|
94
|
+
return this.mgr.execute(name, op, adaptConfig(config));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async execWithFallback<T>(
|
|
98
|
+
name: string,
|
|
99
|
+
primary: () => Promise<T>,
|
|
100
|
+
fallback: () => Promise<T>,
|
|
101
|
+
config?: Partial<CircuitBreakerConfig>,
|
|
102
|
+
): Promise<T> {
|
|
103
|
+
return this.mgr.execWithFallback(name, primary, fallback, adaptConfig(config));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
allStats() {
|
|
107
|
+
return this.mgr.getHealthStatus();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
resetAll() {
|
|
111
|
+
this.mgr.resetAll();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ─── Cost Tracker ───────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
export interface ProviderCostEntry {
|
|
118
|
+
totalCost: number;
|
|
119
|
+
requestCount: number;
|
|
120
|
+
inputTokens: number;
|
|
121
|
+
outputTokens: number;
|
|
122
|
+
lastRecordedAt: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export class AegisCostTracker {
|
|
126
|
+
private readonly tracker: LlmCostTracker;
|
|
127
|
+
private readonly ledgerRef: CreditLedger;
|
|
128
|
+
|
|
129
|
+
constructor(tracker: LlmCostTracker, ledger: CreditLedger) {
|
|
130
|
+
this.tracker = tracker;
|
|
131
|
+
this.ledgerRef = ledger;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Record a cost event with raw numbers (AEGIS call pattern). */
|
|
135
|
+
record(provider: string, cost: number, inputTokens = 0, outputTokens = 0): void {
|
|
136
|
+
this.tracker.trackCost(provider, {
|
|
137
|
+
message: '',
|
|
138
|
+
usage: { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens, cost },
|
|
139
|
+
model: '',
|
|
140
|
+
provider,
|
|
141
|
+
responseTime: 0,
|
|
142
|
+
} as LLMResponse);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
breakdown(): Record<string, ProviderCostEntry> {
|
|
146
|
+
return this.tracker.breakdown();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
total(): number {
|
|
150
|
+
return this.tracker.total();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Drain and reset — for periodic reporting. */
|
|
154
|
+
drain(): Record<string, ProviderCostEntry> {
|
|
155
|
+
return this.tracker.drain();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
getLedger(): CreditLedger {
|
|
159
|
+
return this.ledgerRef;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ─── Exhaustion Tracking ────────────────────────────────────
|
|
164
|
+
// Delegates to llm-providers' defaultExhaustionRegistry — one global
|
|
165
|
+
// exhaustion state per process is correct (provider rate limits are
|
|
166
|
+
// upstream-global, not per-consumer).
|
|
167
|
+
|
|
168
|
+
export function markProviderExhausted(provider: string): void {
|
|
169
|
+
defaultExhaustionRegistry.markExhausted(provider);
|
|
170
|
+
console.warn(`[resilience] Provider ${provider} marked as quota-exhausted`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function isProviderExhausted(provider: string): boolean {
|
|
174
|
+
return defaultExhaustionRegistry.isExhausted(provider);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ─── Retry with Exponential Backoff ─────────────────────────
|
|
178
|
+
|
|
179
|
+
export interface RetryConfig {
|
|
180
|
+
maxAttempts: number;
|
|
181
|
+
baseDelayMs: number;
|
|
182
|
+
maxDelayMs: number;
|
|
183
|
+
backoffMultiplier: number;
|
|
184
|
+
jitter: number; // 0–1 fraction
|
|
185
|
+
shouldRetry?: (err: unknown, attempt: number) => boolean;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const DEFAULT_RETRY: RetryConfig = {
|
|
189
|
+
maxAttempts: 3,
|
|
190
|
+
baseDelayMs: 200,
|
|
191
|
+
maxDelayMs: 5_000,
|
|
192
|
+
backoffMultiplier: 2,
|
|
193
|
+
jitter: 0.15,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export async function withRetry<T>(
|
|
197
|
+
op: () => Promise<T>,
|
|
198
|
+
config?: Partial<RetryConfig>,
|
|
199
|
+
label?: string,
|
|
200
|
+
): Promise<T> {
|
|
201
|
+
const cfg = { ...DEFAULT_RETRY, ...config };
|
|
202
|
+
let lastErr: unknown;
|
|
203
|
+
|
|
204
|
+
for (let attempt = 1; attempt <= cfg.maxAttempts; attempt++) {
|
|
205
|
+
try {
|
|
206
|
+
return await op();
|
|
207
|
+
} catch (err) {
|
|
208
|
+
lastErr = err;
|
|
209
|
+
const retryable = cfg.shouldRetry ? cfg.shouldRetry(err, attempt) : isRetryable(err);
|
|
210
|
+
if (!retryable || attempt === cfg.maxAttempts) throw err;
|
|
211
|
+
|
|
212
|
+
const delay = Math.min(
|
|
213
|
+
cfg.baseDelayMs * Math.pow(cfg.backoffMultiplier, attempt - 1),
|
|
214
|
+
cfg.maxDelayMs,
|
|
215
|
+
);
|
|
216
|
+
const jitteredDelay = Math.floor(delay + delay * cfg.jitter * Math.random());
|
|
217
|
+
|
|
218
|
+
console.warn(`[retry] ${label ?? 'op'} attempt ${attempt}/${cfg.maxAttempts} failed, retrying in ${jitteredDelay}ms`);
|
|
219
|
+
await sleep(jitteredDelay);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
throw lastErr;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function isRetryable(err: unknown): boolean {
|
|
227
|
+
if (err instanceof CircuitBreakerOpenError) return false;
|
|
228
|
+
if (err instanceof QuotaExceededError) return false;
|
|
229
|
+
if (err instanceof LLMProviderError) return err.retryable;
|
|
230
|
+
if (!(err instanceof Error)) return false;
|
|
231
|
+
const m = err.message.toLowerCase();
|
|
232
|
+
return m.includes('timeout') || m.includes('network') || m.includes('503') || m.includes('502')
|
|
233
|
+
|| m.includes('rate') || m.includes('429') || m.includes('econnreset')
|
|
234
|
+
|| m.includes('temporarily') || m.includes('overloaded');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ─── Fallback ───────────────────────────────────────────────
|
|
238
|
+
|
|
239
|
+
export async function withFallback<T>(
|
|
240
|
+
primary: () => Promise<T>,
|
|
241
|
+
fallback: () => Promise<T>,
|
|
242
|
+
shouldFallback?: (err: unknown) => boolean,
|
|
243
|
+
): Promise<T> {
|
|
244
|
+
try {
|
|
245
|
+
return await primary();
|
|
246
|
+
} catch (err) {
|
|
247
|
+
if (shouldFallback && !shouldFallback(err)) throw err;
|
|
248
|
+
console.warn(`[fallback] primary failed, trying fallback: ${err instanceof Error ? err.message.slice(0, 120) : String(err)}`);
|
|
249
|
+
return fallback();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ─── Combined: Circuit + Retry + Fallback ───────────────────
|
|
254
|
+
|
|
255
|
+
export async function resilient<T>(
|
|
256
|
+
service: string,
|
|
257
|
+
op: () => Promise<T>,
|
|
258
|
+
opts: {
|
|
259
|
+
circuits: CircuitRegistry;
|
|
260
|
+
fallback?: () => Promise<T>;
|
|
261
|
+
retry?: Partial<RetryConfig>;
|
|
262
|
+
circuit?: Partial<CircuitBreakerConfig>;
|
|
263
|
+
},
|
|
264
|
+
): Promise<T> {
|
|
265
|
+
const retryOp = () => withRetry(op, opts.retry, service);
|
|
266
|
+
|
|
267
|
+
if (opts.fallback) {
|
|
268
|
+
return opts.circuits.execWithFallback(service, retryOp, opts.fallback, opts.circuit);
|
|
269
|
+
}
|
|
270
|
+
return opts.circuits.exec(service, retryOp, opts.circuit);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ─── Factory ────────────────────────────────────────────────
|
|
274
|
+
// Instantiates the circuit/cost/ledger triple with consumer-specified
|
|
275
|
+
// budgets. Wires the ledger event listener inside the factory so core's
|
|
276
|
+
// module load has no side effects — only consumers that call the factory
|
|
277
|
+
// subscribe to budget events.
|
|
278
|
+
|
|
279
|
+
export interface ResilienceBudget {
|
|
280
|
+
provider: string;
|
|
281
|
+
monthlyBudget: number;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export interface ResilienceConfig {
|
|
285
|
+
budgets: ResilienceBudget[];
|
|
286
|
+
/** Override behavior when emergency budget threshold is crossed. Default: markProviderExhausted. */
|
|
287
|
+
onEmergencyBudget?: (provider: string) => void;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export interface ResilienceBundle {
|
|
291
|
+
circuits: CircuitRegistry;
|
|
292
|
+
costs: AegisCostTracker;
|
|
293
|
+
ledger: CreditLedger;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function createResilience(config: ResilienceConfig): ResilienceBundle {
|
|
297
|
+
const ledger = new CreditLedger({ budgets: config.budgets });
|
|
298
|
+
const costs = new AegisCostTracker(new LlmCostTracker({}, ledger), ledger);
|
|
299
|
+
const circuits = new CircuitRegistry(defaultCircuitBreakerManager);
|
|
300
|
+
|
|
301
|
+
const onEmergency = config.onEmergencyBudget ?? markProviderExhausted;
|
|
302
|
+
|
|
303
|
+
ledger.on((event: LedgerEvent) => {
|
|
304
|
+
if (event.type === 'threshold_crossed') {
|
|
305
|
+
const pct = (event.utilizationPct * 100).toFixed(0);
|
|
306
|
+
console.warn(`[ledger] ${event.provider} budget ${event.tier}: ${pct}% utilized ($${event.spend.toFixed(2)}/$${event.budget.toFixed(2)})`);
|
|
307
|
+
if (event.tier === 'emergency') {
|
|
308
|
+
onEmergency(event.provider);
|
|
309
|
+
}
|
|
310
|
+
} else if (event.type === 'depletion_projected') {
|
|
311
|
+
console.warn(`[ledger] ${event.provider} depletion ${event.tier}: ~${event.daysRemaining.toFixed(1)} days remaining at $${event.burnRate.costPerDay.toFixed(2)}/day`);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
return { circuits, costs, ledger };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ─── Util ───────────────────────────────────────────────────
|
|
319
|
+
|
|
320
|
+
function sleep(ms: number): Promise<void> {
|
|
321
|
+
return new Promise(r => setTimeout(r, ms));
|
|
322
|
+
}
|