@wrongstack/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.
@@ -0,0 +1,1055 @@
1
+ /**
2
+ * Container — dependency injection with explicit bind / override / decorate.
3
+ *
4
+ * Invariants:
5
+ * bind() — throws if token already bound
6
+ * override() — throws if nothing to replace
7
+ * decorate() — stacks; cached value cleared on register
8
+ */
9
+ type Token<T> = symbol & {
10
+ readonly __type?: T;
11
+ };
12
+ type Factory<T> = (c: Container) => T;
13
+ type Decorator<T> = (inner: T, c: Container) => T;
14
+ interface BindOptions {
15
+ singleton?: boolean;
16
+ owner?: string;
17
+ }
18
+ declare class Container {
19
+ private readonly entries;
20
+ bind<T>(token: Token<T>, factory: Factory<T>, opts?: BindOptions): void;
21
+ override<T>(token: Token<T>, factory: Factory<T>, opts?: BindOptions): void;
22
+ decorate<T>(token: Token<T>, decorator: Decorator<T>, owner?: string): void;
23
+ resolve<T>(token: Token<T>): T;
24
+ has<T>(token: Token<T>): boolean;
25
+ ownerOf<T>(token: Token<T>): string | undefined;
26
+ /**
27
+ * Remove a token's binding (along with any decorators stacked on it).
28
+ * Returns true if the token existed. Use this to withdraw temporary
29
+ * bindings installed by a short-lived run or plugin — without it, the
30
+ * entry persists in the map forever.
31
+ */
32
+ unbind<T>(token: Token<T>): boolean;
33
+ /**
34
+ * Drop every binding. Intended for tests and short-lived CLI invocations
35
+ * that rebuild the container from scratch. Production code should prefer
36
+ * `unbind` on the specific tokens it owns.
37
+ */
38
+ clear(): void;
39
+ list(): Array<{
40
+ token: symbol;
41
+ owner: string;
42
+ }>;
43
+ /**
44
+ * Inspect a binding's full shape, including decorator count and whether
45
+ * a singleton value is cached. Returns null if the token is unbound.
46
+ * Decorator and factory function references are not exposed — only counts
47
+ * and metadata, to keep internal state hidden.
48
+ */
49
+ inspect<T>(token: Token<T>): {
50
+ owner: string;
51
+ singleton: boolean;
52
+ decoratorCount: number;
53
+ cached: boolean;
54
+ } | null;
55
+ }
56
+
57
+ /**
58
+ * Pipeline — Koa-style middleware chain with named middleware
59
+ * and position-aware insertion. Generic over input type T.
60
+ */
61
+ type NextFn<T> = (value: T) => Promise<T>;
62
+ type MiddlewareHandler<T> = (value: T, next: NextFn<T>) => Promise<T>;
63
+ interface Middleware<T> {
64
+ name: string;
65
+ handler: MiddlewareHandler<T>;
66
+ owner?: string;
67
+ }
68
+ interface PipelineOptions {
69
+ /** When true and the target middleware is not found, operations silently no-op instead of throwing. */
70
+ optional?: boolean;
71
+ }
72
+ /**
73
+ * Read-only view of a pipeline. Returned to consumers (plugins, hooks)
74
+ * so they can inspect but not mutate the chain.
75
+ */
76
+ interface ReadonlyPipeline<T> {
77
+ readonly size: number;
78
+ list(): readonly string[];
79
+ run(input: T): Promise<T>;
80
+ }
81
+ declare class Pipeline<T> {
82
+ private readonly chain;
83
+ use(mw: Middleware<T> | Middleware<unknown>): this;
84
+ prepend(mw: Middleware<T>): this;
85
+ /**
86
+ * Insert middleware at an explicit index. Out-of-range indices are clamped.
87
+ * Use this when insertBefore/insertAfter are insufficient (e.g. to place
88
+ * a middleware at a known position regardless of named targets).
89
+ */
90
+ insertAt(index: number, mw: Middleware<T>): this;
91
+ /**
92
+ * Insert mw immediately before the first occurrence of target.
93
+ * If called multiple times with the same target, each call inserts
94
+ * before the target's current position — so after insertBefore('B', X)
95
+ * then insertBefore('B', Y), the order is Y → X → B.
96
+ */
97
+ insertBefore(target: string, mw: Middleware<T>, opts?: PipelineOptions): this;
98
+ /**
99
+ * Insert mw immediately after the first occurrence of target.
100
+ * If called multiple times with the same target, each call inserts
101
+ * after the target's current position — so after insertAfter('B', X)
102
+ * then insertAfter('B', Y), the order is B → X → Y.
103
+ */
104
+ insertAfter(target: string, mw: Middleware<T>, opts?: PipelineOptions): this;
105
+ replace(target: string, mw: Middleware<T>, opts?: PipelineOptions): this;
106
+ remove(name: string, opts?: PipelineOptions): this;
107
+ list(): readonly string[];
108
+ size(): number;
109
+ /** Return a read-only view suitable for passing to plugins. */
110
+ asReadonly(): ReadonlyPipeline<T>;
111
+ run(input: T): Promise<T>;
112
+ private indexOf;
113
+ private ensureUnique;
114
+ }
115
+
116
+ interface TextBlock {
117
+ type: 'text';
118
+ text: string;
119
+ cache_control?: {
120
+ type: 'ephemeral';
121
+ };
122
+ }
123
+ interface ToolUseBlock {
124
+ type: 'tool_use';
125
+ id: string;
126
+ name: string;
127
+ input: Record<string, unknown>;
128
+ }
129
+ interface ToolResultBlock {
130
+ type: 'tool_result';
131
+ tool_use_id: string;
132
+ /**
133
+ * The original tool name. Useful for providers like Google Gemini that
134
+ * need the tool name in `functionResponse.name` — the tool_use_id is
135
+ * only a session-local identifier and is not stable across replays.
136
+ * Always set by ToolExecutor; may be absent on manually-constructed blocks.
137
+ */
138
+ name?: string;
139
+ content: string;
140
+ is_error?: boolean;
141
+ }
142
+ interface ImageBlock {
143
+ type: 'image';
144
+ source: {
145
+ type: 'base64' | 'url';
146
+ media_type?: string;
147
+ data?: string;
148
+ url?: string;
149
+ };
150
+ }
151
+ type ContentBlock = TextBlock | ToolUseBlock | ToolResultBlock | ImageBlock;
152
+ declare function isTextBlock(b: ContentBlock): b is TextBlock;
153
+ declare function isToolUseBlock(b: ContentBlock): b is ToolUseBlock;
154
+ declare function isToolResultBlock(b: ContentBlock): b is ToolResultBlock;
155
+ declare function isImageBlock(b: ContentBlock): b is ImageBlock;
156
+
157
+ type MessageRole = 'user' | 'assistant' | 'system';
158
+ interface Message {
159
+ role: MessageRole;
160
+ content: string | ContentBlock[];
161
+ }
162
+ declare function asBlocks(content: string | ContentBlock[]): ContentBlock[];
163
+ declare function asText(content: string | ContentBlock[]): string;
164
+
165
+ interface SessionMetadata {
166
+ id: string;
167
+ title?: string;
168
+ model?: string;
169
+ provider?: string;
170
+ startedAt: string;
171
+ endedAt?: string;
172
+ }
173
+ type SessionEvent = {
174
+ type: 'session_start';
175
+ ts: string;
176
+ id: string;
177
+ model: string;
178
+ provider: string;
179
+ } | {
180
+ type: 'session_resumed';
181
+ ts: string;
182
+ id: string;
183
+ model: string;
184
+ provider: string;
185
+ } | {
186
+ type: 'user_input';
187
+ ts: string;
188
+ content: string | ContentBlock[];
189
+ } | {
190
+ type: 'llm_request';
191
+ ts: string;
192
+ model: string;
193
+ messageCount: number;
194
+ } | {
195
+ type: 'llm_response';
196
+ ts: string;
197
+ content: ContentBlock[];
198
+ stopReason: string;
199
+ usage: Usage;
200
+ } | {
201
+ type: 'tool_use';
202
+ ts: string;
203
+ name: string;
204
+ id: string;
205
+ input: unknown;
206
+ } | {
207
+ type: 'tool_result';
208
+ ts: string;
209
+ id: string;
210
+ content: unknown;
211
+ isError: boolean;
212
+ } | {
213
+ type: 'compaction';
214
+ ts: string;
215
+ before: number;
216
+ after: number;
217
+ } | {
218
+ type: 'error';
219
+ ts: string;
220
+ message: string;
221
+ phase: string;
222
+ } | {
223
+ type: 'session_end';
224
+ ts: string;
225
+ usage: Usage;
226
+ } | {
227
+ type: 'mode_changed';
228
+ ts: string;
229
+ from: string;
230
+ to: string;
231
+ } | {
232
+ type: 'task_created';
233
+ ts: string;
234
+ taskId: string;
235
+ title: string;
236
+ } | {
237
+ type: 'task_updated';
238
+ ts: string;
239
+ taskId: string;
240
+ status: string;
241
+ } | {
242
+ type: 'task_completed';
243
+ ts: string;
244
+ taskId: string;
245
+ title: string;
246
+ } | {
247
+ type: 'task_failed';
248
+ ts: string;
249
+ taskId: string;
250
+ title: string;
251
+ error: string;
252
+ } | {
253
+ type: 'agent_spawned';
254
+ ts: string;
255
+ agentId: string;
256
+ role: string;
257
+ } | {
258
+ type: 'agent_stopped';
259
+ ts: string;
260
+ agentId: string;
261
+ } | {
262
+ type: 'agent_error';
263
+ ts: string;
264
+ agentId: string;
265
+ error: string;
266
+ } | {
267
+ type: 'spec_parsed';
268
+ ts: string;
269
+ specId: string;
270
+ title: string;
271
+ completeness: number;
272
+ } | {
273
+ type: 'spec_analyzed';
274
+ ts: string;
275
+ specId: string;
276
+ gaps: string[];
277
+ } | {
278
+ type: 'skill_activated';
279
+ ts: string;
280
+ skillName: string;
281
+ } | {
282
+ type: 'skill_deactivated';
283
+ ts: string;
284
+ skillName: string;
285
+ } | {
286
+ type: 'tool_call_start';
287
+ ts: string;
288
+ name: string;
289
+ id: string;
290
+ input: unknown;
291
+ } | {
292
+ type: 'tool_call_end';
293
+ ts: string;
294
+ name: string;
295
+ id: string;
296
+ durationMs: number;
297
+ outputSize: number;
298
+ } | {
299
+ type: 'message_truncated';
300
+ ts: string;
301
+ before: number;
302
+ after: number;
303
+ };
304
+ interface SessionSummary {
305
+ id: string;
306
+ title: string;
307
+ startedAt: string;
308
+ model: string;
309
+ provider: string;
310
+ tokenTotal: number;
311
+ }
312
+ interface SessionData {
313
+ metadata: SessionMetadata;
314
+ events: SessionEvent[];
315
+ messages: Message[];
316
+ usage: Usage;
317
+ }
318
+ interface ResumedSession {
319
+ writer: SessionWriter;
320
+ data: SessionData;
321
+ }
322
+ interface SessionStore {
323
+ create(meta: Omit<SessionMetadata, 'startedAt'>): Promise<SessionWriter>;
324
+ load(id: string): Promise<SessionData>;
325
+ /**
326
+ * Open an existing session for append, returning both a writer that
327
+ * continues writing to the same JSONL file and the replayed state
328
+ * (messages + usage) so the caller can hydrate a Context. A
329
+ * `session_resumed` marker is appended for audit.
330
+ */
331
+ resume(id: string): Promise<ResumedSession>;
332
+ list(limit?: number): Promise<SessionSummary[]>;
333
+ delete(id: string): Promise<void>;
334
+ }
335
+ interface SessionWriter {
336
+ readonly id: string;
337
+ append(event: SessionEvent): Promise<void>;
338
+ close(): Promise<void>;
339
+ }
340
+
341
+ interface CacheStats {
342
+ /** Tokens served from cache (cheaper). */
343
+ readTokens: number;
344
+ /** Tokens written into the cache (more expensive than input on first hit). */
345
+ writeTokens: number;
346
+ /** Hit ratio: cacheRead / (cacheRead + input). 0 when nothing cached. */
347
+ hitRatio: number;
348
+ }
349
+ interface TokenCounter {
350
+ account(usage: Usage, model?: string): void;
351
+ total(): Usage;
352
+ estimateCost(): {
353
+ input: number;
354
+ output: number;
355
+ total: number;
356
+ currency: 'USD';
357
+ };
358
+ cacheStats(): CacheStats;
359
+ reset(): void;
360
+ }
361
+
362
+ interface TodoItem {
363
+ id: string;
364
+ content: string;
365
+ status: 'pending' | 'in_progress' | 'completed';
366
+ activeForm?: string;
367
+ }
368
+ interface RunOptions {
369
+ signal?: AbortSignal;
370
+ model?: string;
371
+ executionStrategy?: 'parallel' | 'sequential' | 'smart';
372
+ maxIterations?: number;
373
+ }
374
+ interface ContextInit {
375
+ systemPrompt: TextBlock[];
376
+ provider: Provider;
377
+ session: SessionWriter;
378
+ signal: AbortSignal;
379
+ tokenCounter: TokenCounter;
380
+ cwd: string;
381
+ projectRoot: string;
382
+ model: string;
383
+ tools?: Tool[];
384
+ }
385
+ declare class Context {
386
+ messages: Message[];
387
+ todos: TodoItem[];
388
+ readFiles: Set<string>;
389
+ fileMtimes: Map<string, number>;
390
+ systemPrompt: TextBlock[];
391
+ provider: Provider;
392
+ session: SessionWriter;
393
+ signal: AbortSignal;
394
+ tokenCounter: TokenCounter;
395
+ cwd: string;
396
+ projectRoot: string;
397
+ model: string;
398
+ tools: Tool[];
399
+ meta: Record<string, unknown>;
400
+ constructor(init: ContextInit);
401
+ /**
402
+ * Register a teardown hook tied to the current run's abort signal. The
403
+ * hook fires when the run aborts OR ends normally — Agent.run wires
404
+ * this through a RunController. When no run is active the hook fires
405
+ * immediately so callers don't leak resources.
406
+ */
407
+ private abortHooks;
408
+ registerAbortHook(fn: () => void | Promise<void>): () => void;
409
+ drainAbortHooks(): Promise<void>;
410
+ recordRead(absPath: string, mtimeMs: number): void;
411
+ hasRead(absPath: string): boolean;
412
+ lastReadMtime(absPath: string): number | undefined;
413
+ usage(): Usage;
414
+ }
415
+
416
+ type Permission = 'auto' | 'confirm' | 'deny';
417
+ interface JSONSchema {
418
+ type?: string;
419
+ properties?: Record<string, JSONSchema>;
420
+ required?: string[];
421
+ items?: JSONSchema;
422
+ enum?: unknown[];
423
+ description?: string;
424
+ [k: string]: unknown;
425
+ }
426
+ interface Tool<I = unknown, O = unknown> {
427
+ name: string;
428
+ description: string;
429
+ usageHint?: string;
430
+ inputSchema: JSONSchema;
431
+ permission: Permission;
432
+ mutating: boolean;
433
+ maxOutputBytes?: number;
434
+ timeoutMs?: number;
435
+ execute(input: I, ctx: Context, opts: {
436
+ signal: AbortSignal;
437
+ }): Promise<O>;
438
+ }
439
+ interface ToolCallContext {
440
+ tool: Tool;
441
+ input: unknown;
442
+ callId: string;
443
+ ctx: Context;
444
+ signal: AbortSignal;
445
+ }
446
+
447
+ interface Usage {
448
+ input: number;
449
+ output: number;
450
+ cacheRead?: number;
451
+ cacheWrite?: number;
452
+ }
453
+ interface Capabilities {
454
+ tools: boolean;
455
+ parallelTools: boolean;
456
+ vision: boolean;
457
+ streaming: boolean;
458
+ promptCache: boolean;
459
+ systemPrompt: boolean;
460
+ jsonMode: boolean;
461
+ maxContext: number;
462
+ cacheControl: 'native' | 'auto' | 'none';
463
+ }
464
+ interface Request {
465
+ model: string;
466
+ system?: TextBlock[];
467
+ messages: Message[];
468
+ tools?: Tool[];
469
+ maxTokens: number;
470
+ temperature?: number;
471
+ topP?: number;
472
+ stopSequences?: string[];
473
+ toolChoice?: 'auto' | 'required' | 'none' | {
474
+ type: 'tool';
475
+ name: string;
476
+ };
477
+ }
478
+ type StopReason = 'end_turn' | 'tool_use' | 'max_tokens' | 'stop_sequence' | 'refusal';
479
+ interface Response {
480
+ content: ContentBlock[];
481
+ stopReason: StopReason;
482
+ usage: Usage;
483
+ model: string;
484
+ }
485
+ type StreamEvent = {
486
+ type: 'message_start';
487
+ model: string;
488
+ } | {
489
+ type: 'content_block_start';
490
+ kind: 'text' | 'tool_use';
491
+ id?: string;
492
+ name?: string;
493
+ } | {
494
+ type: 'content_block_stop';
495
+ index: number;
496
+ } | {
497
+ type: 'text_delta';
498
+ text: string;
499
+ } | {
500
+ type: 'tool_use_start';
501
+ id: string;
502
+ name: string;
503
+ } | {
504
+ type: 'tool_use_input_delta';
505
+ id: string;
506
+ partial: string;
507
+ } | {
508
+ type: 'tool_use_stop';
509
+ id: string;
510
+ input: unknown;
511
+ } | {
512
+ type: 'message_stop';
513
+ stopReason: StopReason;
514
+ usage: Usage;
515
+ };
516
+ interface Provider {
517
+ readonly id: string;
518
+ readonly capabilities: Capabilities;
519
+ /** Canonical streaming entry point. `complete()` defaults to a wrapper that
520
+ * aggregates this stream — providers may override for non-streaming wires. */
521
+ stream(req: Request, opts: {
522
+ signal: AbortSignal;
523
+ }): AsyncIterable<StreamEvent>;
524
+ complete(req: Request, opts: {
525
+ signal: AbortSignal;
526
+ }): Promise<Response>;
527
+ }
528
+ /**
529
+ * Structured body parsed from a provider's HTTP error response. Populated
530
+ * best-effort: providers return JSON shaped differently (Anthropic uses
531
+ * `{error: {type, message}}`, OpenAI uses `{error: {message, code}}`,
532
+ * Google uses `{error: {status, message}}`), so the fields here are the
533
+ * intersection that's usable for rendering and routing.
534
+ */
535
+ interface ProviderErrorBody {
536
+ /** Provider-specific kind, e.g. "overloaded_error", "rate_limit_error", "invalid_request_error". */
537
+ type?: string;
538
+ /** Human-readable explanation from the provider. */
539
+ message?: string;
540
+ /** Provider request id, when present in the body or headers. */
541
+ requestId?: string;
542
+ /** Parsed Retry-After header (or equivalent body hint) in milliseconds. */
543
+ retryAfterMs?: number;
544
+ /** The raw response body (truncated), kept for debugging. */
545
+ raw?: string;
546
+ }
547
+ declare class ProviderError extends Error {
548
+ readonly status: number;
549
+ readonly retryable: boolean;
550
+ readonly providerId: string;
551
+ readonly body?: ProviderErrorBody;
552
+ readonly cause?: unknown;
553
+ constructor(message: string, status: number, retryable: boolean, providerId: string, opts?: {
554
+ body?: ProviderErrorBody;
555
+ cause?: unknown;
556
+ });
557
+ /**
558
+ * Render a one-line, user-facing description. Designed for the CLI/TUI
559
+ * status line and the agent's retry warning. Avoids dumping raw JSON
560
+ * (which is what users see today when a 529 lands and the log message
561
+ * includes the full `{"type":"error",...}` body).
562
+ *
563
+ * Examples:
564
+ * "minimax-coding-plan overloaded (529): High traffic detected. Upgrade for highspeed model. [req 06534785201de9c0…]"
565
+ * "openai rate limited (429): Retry after 12s"
566
+ * "anthropic invalid request (400): messages.0.role must be one of 'user'|'assistant'"
567
+ * "groq HTTP 500 (server error)"
568
+ */
569
+ describe(): string;
570
+ }
571
+
572
+ /**
573
+ * EventBus — observe-only typed event bus.
574
+ * Subscribers cannot modify or cancel. Subscriber exceptions are caught.
575
+ */
576
+
577
+ interface EventMap {
578
+ 'session.started': {
579
+ id: string;
580
+ };
581
+ 'session.ended': {
582
+ id: string;
583
+ usage: Usage;
584
+ };
585
+ 'session.damaged': {
586
+ sessionId: string;
587
+ detail: string;
588
+ };
589
+ 'iteration.started': {
590
+ ctx: Context;
591
+ index: number;
592
+ };
593
+ 'iteration.completed': {
594
+ ctx: Context;
595
+ index: number;
596
+ };
597
+ /**
598
+ * Fired when the agent hits its iteration limit. Listeners (CLI/TUI) can
599
+ * call `grant(extra)` to allow more iterations, or `deny()` to stop.
600
+ * If no listener responds within 30s the run ends with 'max_iterations'.
601
+ */
602
+ 'iteration.limit_reached': {
603
+ currentIterations: number;
604
+ currentLimit: number;
605
+ grant: (extraIterations: number) => void;
606
+ deny: () => void;
607
+ };
608
+ 'provider.response': {
609
+ ctx: Context;
610
+ usage: Usage;
611
+ stopReason: string;
612
+ };
613
+ 'provider.text_delta': {
614
+ ctx: Context;
615
+ text: string;
616
+ };
617
+ 'provider.tool_use_start': {
618
+ ctx: Context;
619
+ id: string;
620
+ name: string;
621
+ };
622
+ 'provider.tool_use_stop': {
623
+ ctx: Context;
624
+ id: string;
625
+ };
626
+ /**
627
+ * Fired before each retry of a failed provider call. `attempt` is 1-based
628
+ * (the first retry is attempt 1, etc.). `description` is the human-readable
629
+ * one-liner from `ProviderError.describe()` — render this in the CLI/TUI
630
+ * instead of grepping logger output for the raw JSON body.
631
+ */
632
+ 'provider.retry': {
633
+ providerId: string;
634
+ attempt: number;
635
+ delayMs: number;
636
+ status: number;
637
+ description: string;
638
+ };
639
+ /**
640
+ * Fired once when a provider call ultimately fails (retries exhausted, or
641
+ * non-retryable error). Same shape as `provider.retry` minus the delay.
642
+ */
643
+ 'provider.error': {
644
+ providerId: string;
645
+ status: number;
646
+ description: string;
647
+ retryable: boolean;
648
+ };
649
+ 'tool.started': {
650
+ name: string;
651
+ id: string;
652
+ input?: unknown;
653
+ };
654
+ /**
655
+ * `output` is a truncated preview of the tool's serialized result text
656
+ * (capped at ~400 chars by the emitter). UIs render this inline in the
657
+ * tool history line without re-fetching from the session log.
658
+ */
659
+ 'tool.executed': {
660
+ name: string;
661
+ durationMs: number;
662
+ ok: boolean;
663
+ input?: unknown;
664
+ output?: string;
665
+ };
666
+ 'token.threshold': {
667
+ used: number;
668
+ limit: number;
669
+ };
670
+ 'compaction.fired': {
671
+ before: number;
672
+ after: number;
673
+ };
674
+ 'mcp.server.connected': {
675
+ name: string;
676
+ toolCount: number;
677
+ };
678
+ 'mcp.server.reconnected': {
679
+ name: string;
680
+ toolCount: number;
681
+ };
682
+ 'mcp.server.disconnected': {
683
+ name: string;
684
+ reason: string;
685
+ };
686
+ 'token.cost_estimate_unavailable': {
687
+ model: string;
688
+ };
689
+ error: {
690
+ err: Error;
691
+ phase: string;
692
+ };
693
+ }
694
+ type EventName = keyof EventMap;
695
+ type Listener<E extends EventName> = (payload: EventMap[E]) => void;
696
+ interface EventLogger {
697
+ error(msg: string, ctx?: unknown): void;
698
+ }
699
+ declare class EventBus {
700
+ private readonly listeners;
701
+ private logger?;
702
+ setLogger(logger: EventLogger): void;
703
+ on<E extends EventName>(event: E, fn: Listener<E>): () => void;
704
+ off<E extends EventName>(event: E, fn: Listener<E>): void;
705
+ once<E extends EventName>(event: E, fn: Listener<E>): () => void;
706
+ emit<E extends EventName>(event: E, payload: EventMap[E]): void;
707
+ clear(): void;
708
+ }
709
+
710
+ type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace';
711
+ interface Logger {
712
+ level: LogLevel;
713
+ error(msg: string, ctx?: unknown): void;
714
+ warn(msg: string, ctx?: unknown): void;
715
+ info(msg: string, ctx?: unknown): void;
716
+ debug(msg: string, ctx?: unknown): void;
717
+ trace(msg: string, ctx?: unknown): void;
718
+ child(bindings: Record<string, unknown>): Logger;
719
+ }
720
+
721
+ type MemoryScope = 'project-agents' | 'project-memory' | 'user-memory';
722
+ interface MemoryEntry {
723
+ scope: MemoryScope;
724
+ text: string;
725
+ ts: string;
726
+ }
727
+ interface MemoryStore {
728
+ readAll(): Promise<string>;
729
+ read(scope: MemoryScope): Promise<string>;
730
+ remember(text: string, scope?: MemoryScope): Promise<void>;
731
+ forget(query: string, scope?: MemoryScope): Promise<number>;
732
+ consolidate(scope: MemoryScope): Promise<void>;
733
+ clear(scope?: MemoryScope): Promise<void>;
734
+ }
735
+
736
+ interface TrustPolicy {
737
+ [toolNameOrPattern: string]: {
738
+ allow?: string[];
739
+ deny?: string[];
740
+ auto?: boolean;
741
+ trustWorkdir?: boolean;
742
+ denyPrivate?: boolean;
743
+ };
744
+ }
745
+ interface PermissionDecision {
746
+ permission: Permission;
747
+ reason?: string;
748
+ source: 'default' | 'trust' | 'yolo' | 'user' | 'deny';
749
+ }
750
+ interface PermissionPolicy {
751
+ evaluate(tool: Tool, input: unknown, ctx: Context): Promise<PermissionDecision>;
752
+ trust(rule: {
753
+ tool: string;
754
+ pattern: string;
755
+ }): Promise<void>;
756
+ reload(): Promise<void>;
757
+ }
758
+
759
+ interface CompactReport {
760
+ before: number;
761
+ after: number;
762
+ reductions: {
763
+ phase: 'elision' | 'summary' | 'selective';
764
+ saved: number;
765
+ }[];
766
+ }
767
+ interface Compactor {
768
+ compact(ctx: Context, opts?: {
769
+ aggressive?: boolean;
770
+ }): Promise<CompactReport>;
771
+ }
772
+
773
+ interface PathResolver {
774
+ readonly projectRoot: string;
775
+ readonly cwd: string;
776
+ resolve(input: string): string;
777
+ isInsideRoot(absPath: string): boolean;
778
+ ensureInsideRoot(absPath: string): string;
779
+ detectProjectRoot(start: string): string;
780
+ }
781
+
782
+ /**
783
+ * Mirror of the models.dev/api.json schema. Top-level is keyed by provider id.
784
+ * We keep `unknown` for fields we don't read so the cached payload stays faithful.
785
+ */
786
+ interface ModelsDevModel {
787
+ id: string;
788
+ name: string;
789
+ family?: string;
790
+ attachment?: boolean;
791
+ reasoning?: boolean;
792
+ tool_call?: boolean;
793
+ temperature?: boolean;
794
+ knowledge?: string;
795
+ release_date?: string;
796
+ last_updated?: string;
797
+ open_weights?: boolean;
798
+ modalities?: {
799
+ input?: string[];
800
+ output?: string[];
801
+ };
802
+ cost?: {
803
+ input?: number;
804
+ output?: number;
805
+ cache_read?: number;
806
+ cache_write?: number;
807
+ [k: string]: number | undefined;
808
+ };
809
+ limit?: {
810
+ context?: number;
811
+ output?: number;
812
+ };
813
+ [k: string]: unknown;
814
+ }
815
+ interface ModelsDevProvider {
816
+ id: string;
817
+ name: string;
818
+ /** Env vars that hold the API key, in priority order. */
819
+ env?: string[];
820
+ /** Identifies the wire format family (e.g. @ai-sdk/anthropic). */
821
+ npm?: string;
822
+ /** Default base URL when not provided by SDK defaults. */
823
+ api?: string;
824
+ /** Documentation URL. */
825
+ doc?: string;
826
+ models: Record<string, ModelsDevModel>;
827
+ }
828
+ type ModelsDevPayload = Record<string, ModelsDevProvider>;
829
+ /**
830
+ * Canonical wire-format families WrongStack knows how to speak natively.
831
+ * Used by the provider registry to pick a transport.
832
+ */
833
+ type WireFamily = 'anthropic' | 'openai' | 'openai-compatible' | 'google' | 'unsupported';
834
+ interface ResolvedProvider {
835
+ id: string;
836
+ name: string;
837
+ family: WireFamily;
838
+ apiBase?: string;
839
+ envVars: string[];
840
+ doc?: string;
841
+ models: ModelsDevModel[];
842
+ npm?: string;
843
+ }
844
+ interface ResolvedModel {
845
+ providerId: string;
846
+ modelId: string;
847
+ capabilities: {
848
+ tools: boolean;
849
+ vision: boolean;
850
+ reasoning: boolean;
851
+ maxContext: number;
852
+ maxOutput?: number;
853
+ knowledge?: string;
854
+ };
855
+ cost?: ModelsDevModel['cost'];
856
+ }
857
+ interface ModelsRegistry {
858
+ /** Load (from cache or network). Idempotent; second call returns cached value. */
859
+ load(opts?: {
860
+ force?: boolean;
861
+ }): Promise<ModelsDevPayload>;
862
+ /** Force-refresh from network and overwrite cache. */
863
+ refresh(): Promise<ModelsDevPayload>;
864
+ /** All providers, classified by wire family. */
865
+ listProviders(): Promise<ResolvedProvider[]>;
866
+ /** A single provider by id, or undefined. */
867
+ getProvider(id: string): Promise<ResolvedProvider | undefined>;
868
+ /** A model lookup with capabilities + cost. */
869
+ getModel(providerId: string, modelId: string): Promise<ResolvedModel | undefined>;
870
+ /** Suggest a default model for the given provider (latest by release_date). */
871
+ suggestModel(providerId: string): Promise<string | undefined>;
872
+ /** Cache freshness in seconds since last successful network fetch (Infinity if never). */
873
+ ageSeconds(): Promise<number>;
874
+ }
875
+
876
+ interface ContextConfig {
877
+ warnThreshold: number;
878
+ softThreshold: number;
879
+ hardThreshold: number;
880
+ /** Enable automatic compaction when thresholds are crossed (default: true). */
881
+ autoCompact?: boolean;
882
+ /**
883
+ * Model used for LLM-assisted summarization in IntelligentCompactor.
884
+ * Falls back to the main model when omitted.
885
+ */
886
+ summarizerModel?: string;
887
+ /**
888
+ * Override the effective context window size (in tokens). Use this when
889
+ * you want the compactor to trigger earlier than the provider's actual
890
+ * maxContext. Defaults to the provider's reported maxContext.
891
+ */
892
+ effectiveMaxContext?: number;
893
+ maxSessionTokens?: number;
894
+ maxDailyTokens?: number;
895
+ preserveK: number;
896
+ eliseThreshold: number;
897
+ /** Compactor strategy: 'hybrid' (default, fast rules), 'intelligent' (LLM summarization), 'selective' (LLM-driven selection). */
898
+ strategy?: 'hybrid' | 'intelligent' | 'selective';
899
+ /** Enable LLM-driven selective compaction (default: false for backward compat). */
900
+ llmSelector?: boolean;
901
+ }
902
+ interface ToolsConfig {
903
+ defaultExecutionStrategy: 'parallel' | 'sequential' | 'smart';
904
+ maxIterations: number;
905
+ iterationTimeoutMs: number;
906
+ sessionTimeoutMs: number;
907
+ perIterationOutputCapBytes: number;
908
+ /**
909
+ * When true (default), the agent automatically extends its iteration
910
+ * limit by 100 when hit. Set to false to require user confirmation.
911
+ */
912
+ autoExtendLimit?: boolean;
913
+ }
914
+ interface ProviderConfig {
915
+ type: string;
916
+ apiKey?: string;
917
+ baseUrl?: string;
918
+ headers?: Record<string, string>;
919
+ model?: string;
920
+ quirks?: Record<string, unknown>;
921
+ capabilities?: Record<string, unknown>;
922
+ /**
923
+ * Optional wire-family override. When present, the provider can be
924
+ * constructed without consulting the models.dev catalog — useful for
925
+ * self-hosted endpoints, internal proxies, or for working offline.
926
+ */
927
+ family?: WireFamily;
928
+ /** Custom env var names to probe when `apiKey` is missing. */
929
+ envVars?: string[];
930
+ /** Optional list of models the user wants visible for this provider. */
931
+ models?: string[];
932
+ }
933
+ interface MCPServerConfig {
934
+ name: string;
935
+ transport: 'stdio' | 'sse' | 'streamable-http';
936
+ command?: string;
937
+ args?: string[];
938
+ env?: Record<string, string>;
939
+ url?: string;
940
+ headers?: Record<string, string>;
941
+ enabled?: boolean;
942
+ allowedTools?: string[];
943
+ permission?: Permission;
944
+ startupTimeoutMs?: number;
945
+ }
946
+ interface LogConfig {
947
+ level: 'error' | 'warn' | 'info' | 'debug' | 'trace';
948
+ file?: string;
949
+ }
950
+ interface PluginConfig {
951
+ name: string;
952
+ enabled?: boolean;
953
+ options?: Record<string, unknown>;
954
+ }
955
+ /**
956
+ * Optional subsystems that the CLI can boot without. The core flow
957
+ * (provider + agent loop + bundled tools + session) always works; these
958
+ * just add capabilities. `--no-features` flips all of these off, which
959
+ * is the minimum viable WrongStack: a single provider, a fixed config,
960
+ * no network calls at startup.
961
+ */
962
+ interface FeaturesConfig {
963
+ /** Load MCP servers declared in `mcpServers`. */
964
+ mcp: boolean;
965
+ /** Load + initialise npm plugins declared in `plugins`. */
966
+ plugins: boolean;
967
+ /** Register `remember` / `forget` tools backed by memory store. */
968
+ memory: boolean;
969
+ /** Fetch the models.dev catalog at startup. When false, the provider
970
+ * must declare its `family` explicitly in `providers[<id>]`. */
971
+ modelsRegistry: boolean;
972
+ /** Discover + load skills from disk. */
973
+ skills: boolean;
974
+ }
975
+ interface Config {
976
+ version: 1;
977
+ provider: string;
978
+ model: string;
979
+ apiKey?: string;
980
+ baseUrl?: string;
981
+ providers?: Record<string, ProviderConfig>;
982
+ context: ContextConfig;
983
+ tools: ToolsConfig;
984
+ mcpServers?: Record<string, MCPServerConfig>;
985
+ plugins?: (string | PluginConfig)[];
986
+ log: LogConfig;
987
+ features: FeaturesConfig;
988
+ yolo?: boolean;
989
+ cwd?: string;
990
+ }
991
+ interface ConfigLoader {
992
+ load(opts?: {
993
+ cliFlags?: Partial<Config>;
994
+ cwd?: string;
995
+ }): Promise<Config>;
996
+ }
997
+
998
+ interface Renderer {
999
+ write(text: string | TextBlock): void;
1000
+ writeLine(text?: string): void;
1001
+ writeBlock(block: ContentBlock): void;
1002
+ writeToolCall(name: string, input: unknown): void;
1003
+ writeToolResult(name: string, content: unknown, isError: boolean): void;
1004
+ writeDiff(unifiedDiff: string): void;
1005
+ writeWarning(text: string): void;
1006
+ writeError(text: string): void;
1007
+ writeInfo(text: string): void;
1008
+ clear(): void;
1009
+ }
1010
+
1011
+ interface PromptOption {
1012
+ key: string;
1013
+ label: string;
1014
+ value: string;
1015
+ }
1016
+ interface InputReader {
1017
+ readLine(prompt?: string): Promise<string>;
1018
+ readKey(prompt: string, options: PromptOption[]): Promise<string>;
1019
+ close(): Promise<void>;
1020
+ }
1021
+
1022
+ interface ErrorHandler {
1023
+ recover(err: unknown, ctx: Context): Promise<Response | null>;
1024
+ classify(err: unknown): {
1025
+ kind: 'rate_limit' | 'overloaded' | 'server' | 'client' | 'network' | 'abort' | 'context_overflow' | 'unknown';
1026
+ retryable: boolean;
1027
+ };
1028
+ }
1029
+
1030
+ interface RetryPolicy {
1031
+ shouldRetry(err: ProviderError | Error, attempt: number): boolean;
1032
+ delayMs(attempt: number): number;
1033
+ maxAttempts(err: ProviderError | Error): number;
1034
+ }
1035
+
1036
+ interface SkillManifest {
1037
+ name: string;
1038
+ description: string;
1039
+ version?: string;
1040
+ path: string;
1041
+ source: 'project' | 'user' | 'bundled';
1042
+ }
1043
+ interface SkillLoader {
1044
+ list(): Promise<SkillManifest[]>;
1045
+ find(name: string): Promise<SkillManifest | undefined>;
1046
+ manifestText(): Promise<string>;
1047
+ readBody(name: string): Promise<string>;
1048
+ }
1049
+
1050
+ interface SecretScrubber {
1051
+ scrub(text: string): string;
1052
+ scrubObject<T>(obj: T): T;
1053
+ }
1054
+
1055
+ export { type StreamEvent as $, ProviderError as A, type ProviderErrorBody as B, type CacheStats as C, type Request as D, type ErrorHandler as E, type FeaturesConfig as F, type ResolvedModel as G, type ResolvedProvider as H, type ImageBlock as I, type JSONSchema as J, type Response as K, type LogConfig as L, type MCPServerConfig as M, type ResumedSession as N, type RetryPolicy as O, type PathResolver as P, type SessionData as Q, type Renderer as R, type SecretScrubber as S, type SessionEvent as T, type SessionMetadata as U, type SessionStore as V, type SessionSummary as W, type SessionWriter as X, type SkillLoader as Y, type SkillManifest as Z, type StopReason as _, type Capabilities as a, type TextBlock as a0, type TokenCounter as a1, type Tool as a2, type ToolCallContext as a3, type ToolResultBlock as a4, type ToolUseBlock as a5, type ToolsConfig as a6, type TrustPolicy as a7, type Usage as a8, type WireFamily as a9, asBlocks as aa, asText as ab, isImageBlock as ac, isTextBlock as ad, isToolResultBlock as ae, isToolUseBlock as af, Context as ag, Container as ah, EventBus as ai, type EventName as aj, type Listener as ak, type BindOptions as al, type ContextInit as am, type Decorator as an, type EventLogger as ao, type EventMap as ap, type Factory as aq, type Middleware as ar, type MiddlewareHandler as as, type NextFn as at, Pipeline as au, type PipelineOptions as av, type RunOptions as aw, type TodoItem as ax, type Token as ay, type ReadonlyPipeline as az, type CompactReport as b, type Compactor as c, type Config as d, type ConfigLoader as e, type ContentBlock as f, type ContextConfig as g, type InputReader as h, type LogLevel as i, type Logger as j, type MemoryEntry as k, type MemoryScope as l, type MemoryStore as m, type Message as n, type MessageRole as o, type ModelsDevModel as p, type ModelsDevPayload as q, type ModelsDevProvider as r, type ModelsRegistry as s, type Permission as t, type PermissionDecision as u, type PermissionPolicy as v, type PluginConfig as w, type PromptOption as x, type Provider as y, type ProviderConfig as z };