@wrongstack/core 0.1.1 → 0.1.3
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/dist/defaults/index.d.ts +738 -138
- package/dist/defaults/index.js +2507 -975
- package/dist/defaults/index.js.map +1 -1
- package/dist/index.d.ts +28 -9
- package/dist/index.js +3272 -1058
- package/dist/index.js.map +1 -1
- package/dist/kernel/index.d.ts +5 -3
- package/dist/kernel/index.js +89 -11
- package/dist/kernel/index.js.map +1 -1
- package/dist/provider-DovtyuM8.d.ts +813 -0
- package/dist/{secret-scrubber-Dax_Ou_o.d.ts → secret-scrubber-qU3AwEiI.d.ts} +126 -457
- package/dist/{tool-executor-DjnMELMV.d.ts → session-reader-DR4u3bu9.d.ts} +445 -59
- package/dist/{system-prompt-BG3nks8P.d.ts → system-prompt--mzZnenv.d.ts} +1 -1
- package/dist/types/index.d.ts +4 -3
- package/dist/types/index.js +153 -5
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +42 -1
- package/dist/utils/index.js +122 -3
- package/dist/utils/index.js.map +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
interface TextBlock {
|
|
2
|
+
type: 'text';
|
|
3
|
+
text: string;
|
|
4
|
+
cache_control?: {
|
|
5
|
+
type: 'ephemeral';
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
interface ToolUseBlock {
|
|
9
|
+
type: 'tool_use';
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
input: Record<string, unknown>;
|
|
13
|
+
/**
|
|
14
|
+
* Provider-specific opaque metadata captured from the wire response.
|
|
15
|
+
* Echoed back verbatim in the next request so providers that bind
|
|
16
|
+
* extra state to function calls keep working. Example: Gemini's
|
|
17
|
+
* `thoughtSignature` — required for tool-use turns with thinking
|
|
18
|
+
* models, otherwise the next request fails with 400 "Function call
|
|
19
|
+
* is missing a thought_signature in functionCall parts".
|
|
20
|
+
*
|
|
21
|
+
* Keys are namespaced by intent so multiple wires can coexist:
|
|
22
|
+
* - `google.thoughtSignature` — Gemini signed-thought blob
|
|
23
|
+
* Other providers can add their own keys without colliding.
|
|
24
|
+
*/
|
|
25
|
+
providerMeta?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
interface ToolResultBlock {
|
|
28
|
+
type: 'tool_result';
|
|
29
|
+
tool_use_id: string;
|
|
30
|
+
/**
|
|
31
|
+
* The original tool name. Useful for providers like Google Gemini that
|
|
32
|
+
* need the tool name in `functionResponse.name` — the tool_use_id is
|
|
33
|
+
* only a session-local identifier and is not stable across replays.
|
|
34
|
+
* Always set by ToolExecutor; may be absent on manually-constructed blocks.
|
|
35
|
+
*/
|
|
36
|
+
name?: string;
|
|
37
|
+
content: string;
|
|
38
|
+
is_error?: boolean;
|
|
39
|
+
}
|
|
40
|
+
interface ImageBlock {
|
|
41
|
+
type: 'image';
|
|
42
|
+
source: {
|
|
43
|
+
type: 'base64' | 'url';
|
|
44
|
+
media_type?: string;
|
|
45
|
+
data?: string;
|
|
46
|
+
url?: string;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
type ContentBlock = TextBlock | ToolUseBlock | ToolResultBlock | ImageBlock;
|
|
50
|
+
declare function isTextBlock(b: ContentBlock): b is TextBlock;
|
|
51
|
+
declare function isToolUseBlock(b: ContentBlock): b is ToolUseBlock;
|
|
52
|
+
declare function isToolResultBlock(b: ContentBlock): b is ToolResultBlock;
|
|
53
|
+
declare function isImageBlock(b: ContentBlock): b is ImageBlock;
|
|
54
|
+
|
|
55
|
+
type MessageRole = 'user' | 'assistant' | 'system';
|
|
56
|
+
interface Message {
|
|
57
|
+
role: MessageRole;
|
|
58
|
+
content: string | ContentBlock[];
|
|
59
|
+
}
|
|
60
|
+
declare function asBlocks(content: string | ContentBlock[]): ContentBlock[];
|
|
61
|
+
declare function asText(content: string | ContentBlock[]): string;
|
|
62
|
+
|
|
63
|
+
interface SessionMetadata {
|
|
64
|
+
id: string;
|
|
65
|
+
title?: string;
|
|
66
|
+
model?: string;
|
|
67
|
+
provider?: string;
|
|
68
|
+
startedAt: string;
|
|
69
|
+
endedAt?: string;
|
|
70
|
+
}
|
|
71
|
+
type SessionEvent = {
|
|
72
|
+
type: 'session_start';
|
|
73
|
+
ts: string;
|
|
74
|
+
id: string;
|
|
75
|
+
model: string;
|
|
76
|
+
provider: string;
|
|
77
|
+
} | {
|
|
78
|
+
type: 'session_resumed';
|
|
79
|
+
ts: string;
|
|
80
|
+
id: string;
|
|
81
|
+
model: string;
|
|
82
|
+
provider: string;
|
|
83
|
+
} | {
|
|
84
|
+
type: 'user_input';
|
|
85
|
+
ts: string;
|
|
86
|
+
content: string | ContentBlock[];
|
|
87
|
+
} | {
|
|
88
|
+
type: 'llm_request';
|
|
89
|
+
ts: string;
|
|
90
|
+
model: string;
|
|
91
|
+
messageCount: number;
|
|
92
|
+
} | {
|
|
93
|
+
type: 'llm_response';
|
|
94
|
+
ts: string;
|
|
95
|
+
content: ContentBlock[];
|
|
96
|
+
stopReason: string;
|
|
97
|
+
usage: Usage;
|
|
98
|
+
} | {
|
|
99
|
+
type: 'tool_use';
|
|
100
|
+
ts: string;
|
|
101
|
+
name: string;
|
|
102
|
+
id: string;
|
|
103
|
+
input: unknown;
|
|
104
|
+
} | {
|
|
105
|
+
type: 'tool_result';
|
|
106
|
+
ts: string;
|
|
107
|
+
id: string;
|
|
108
|
+
content: unknown;
|
|
109
|
+
isError: boolean;
|
|
110
|
+
} | {
|
|
111
|
+
type: 'compaction';
|
|
112
|
+
ts: string;
|
|
113
|
+
before: number;
|
|
114
|
+
after: number;
|
|
115
|
+
} | {
|
|
116
|
+
type: 'error';
|
|
117
|
+
ts: string;
|
|
118
|
+
message: string;
|
|
119
|
+
phase: string;
|
|
120
|
+
} | {
|
|
121
|
+
type: 'session_end';
|
|
122
|
+
ts: string;
|
|
123
|
+
usage: Usage;
|
|
124
|
+
} | {
|
|
125
|
+
type: 'mode_changed';
|
|
126
|
+
ts: string;
|
|
127
|
+
from: string;
|
|
128
|
+
to: string;
|
|
129
|
+
} | {
|
|
130
|
+
type: 'task_created';
|
|
131
|
+
ts: string;
|
|
132
|
+
taskId: string;
|
|
133
|
+
title: string;
|
|
134
|
+
} | {
|
|
135
|
+
type: 'task_updated';
|
|
136
|
+
ts: string;
|
|
137
|
+
taskId: string;
|
|
138
|
+
status: string;
|
|
139
|
+
} | {
|
|
140
|
+
type: 'task_completed';
|
|
141
|
+
ts: string;
|
|
142
|
+
taskId: string;
|
|
143
|
+
title: string;
|
|
144
|
+
} | {
|
|
145
|
+
type: 'task_failed';
|
|
146
|
+
ts: string;
|
|
147
|
+
taskId: string;
|
|
148
|
+
title: string;
|
|
149
|
+
error: string;
|
|
150
|
+
} | {
|
|
151
|
+
type: 'agent_spawned';
|
|
152
|
+
ts: string;
|
|
153
|
+
agentId: string;
|
|
154
|
+
role: string;
|
|
155
|
+
} | {
|
|
156
|
+
type: 'agent_stopped';
|
|
157
|
+
ts: string;
|
|
158
|
+
agentId: string;
|
|
159
|
+
} | {
|
|
160
|
+
type: 'agent_error';
|
|
161
|
+
ts: string;
|
|
162
|
+
agentId: string;
|
|
163
|
+
error: string;
|
|
164
|
+
} | {
|
|
165
|
+
type: 'spec_parsed';
|
|
166
|
+
ts: string;
|
|
167
|
+
specId: string;
|
|
168
|
+
title: string;
|
|
169
|
+
completeness: number;
|
|
170
|
+
} | {
|
|
171
|
+
type: 'spec_analyzed';
|
|
172
|
+
ts: string;
|
|
173
|
+
specId: string;
|
|
174
|
+
gaps: string[];
|
|
175
|
+
} | {
|
|
176
|
+
type: 'skill_activated';
|
|
177
|
+
ts: string;
|
|
178
|
+
skillName: string;
|
|
179
|
+
} | {
|
|
180
|
+
type: 'skill_deactivated';
|
|
181
|
+
ts: string;
|
|
182
|
+
skillName: string;
|
|
183
|
+
} | {
|
|
184
|
+
type: 'tool_call_start';
|
|
185
|
+
ts: string;
|
|
186
|
+
name: string;
|
|
187
|
+
id: string;
|
|
188
|
+
input: unknown;
|
|
189
|
+
} | {
|
|
190
|
+
type: 'tool_call_end';
|
|
191
|
+
ts: string;
|
|
192
|
+
name: string;
|
|
193
|
+
id: string;
|
|
194
|
+
durationMs: number;
|
|
195
|
+
outputSize: number;
|
|
196
|
+
} | {
|
|
197
|
+
type: 'message_truncated';
|
|
198
|
+
ts: string;
|
|
199
|
+
before: number;
|
|
200
|
+
after: number;
|
|
201
|
+
};
|
|
202
|
+
interface SessionSummary {
|
|
203
|
+
id: string;
|
|
204
|
+
title: string;
|
|
205
|
+
startedAt: string;
|
|
206
|
+
model: string;
|
|
207
|
+
provider: string;
|
|
208
|
+
tokenTotal: number;
|
|
209
|
+
}
|
|
210
|
+
interface SessionData {
|
|
211
|
+
metadata: SessionMetadata;
|
|
212
|
+
events: SessionEvent[];
|
|
213
|
+
messages: Message[];
|
|
214
|
+
usage: Usage;
|
|
215
|
+
}
|
|
216
|
+
interface ResumedSession {
|
|
217
|
+
writer: SessionWriter;
|
|
218
|
+
data: SessionData;
|
|
219
|
+
}
|
|
220
|
+
interface SessionStore {
|
|
221
|
+
create(meta: Omit<SessionMetadata, 'startedAt'>): Promise<SessionWriter>;
|
|
222
|
+
load(id: string): Promise<SessionData>;
|
|
223
|
+
/**
|
|
224
|
+
* Open an existing session for append, returning both a writer that
|
|
225
|
+
* continues writing to the same JSONL file and the replayed state
|
|
226
|
+
* (messages + usage) so the caller can hydrate a Context. A
|
|
227
|
+
* `session_resumed` marker is appended for audit.
|
|
228
|
+
*/
|
|
229
|
+
resume(id: string): Promise<ResumedSession>;
|
|
230
|
+
list(limit?: number): Promise<SessionSummary[]>;
|
|
231
|
+
delete(id: string): Promise<void>;
|
|
232
|
+
}
|
|
233
|
+
interface SessionWriter {
|
|
234
|
+
readonly id: string;
|
|
235
|
+
append(event: SessionEvent): Promise<void>;
|
|
236
|
+
close(): Promise<void>;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
interface CacheStats {
|
|
240
|
+
/** Tokens served from cache (cheaper). */
|
|
241
|
+
readTokens: number;
|
|
242
|
+
/** Tokens written into the cache (more expensive than input on first hit). */
|
|
243
|
+
writeTokens: number;
|
|
244
|
+
/** Hit ratio: cacheRead / (cacheRead + input). 0 when nothing cached. */
|
|
245
|
+
hitRatio: number;
|
|
246
|
+
}
|
|
247
|
+
interface TokenCounter {
|
|
248
|
+
account(usage: Usage, model?: string): void;
|
|
249
|
+
total(): Usage;
|
|
250
|
+
estimateCost(): {
|
|
251
|
+
input: number;
|
|
252
|
+
output: number;
|
|
253
|
+
total: number;
|
|
254
|
+
currency: 'USD';
|
|
255
|
+
};
|
|
256
|
+
cacheStats(): CacheStats;
|
|
257
|
+
reset(): void;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Immutable run environment — the set-once dependencies for an agent run.
|
|
262
|
+
*
|
|
263
|
+
* `Context` today doubles as both a DI bag (provider, session, tokenCounter,
|
|
264
|
+
* cwd, …) and a mutable state container (messages, todos, meta). That makes
|
|
265
|
+
* it hard to test (every test reconstructs the full bag) and easy to abuse
|
|
266
|
+
* (any tool can swap the provider mid-run).
|
|
267
|
+
*
|
|
268
|
+
* `RunEnv` is the immutable half: a read-only projection that subsystems
|
|
269
|
+
* can hold instead of the whole `Context`. It's a view, not a copy — pulling
|
|
270
|
+
* a `RunEnv` from a `Context` is O(1) and reflects the same underlying
|
|
271
|
+
* references. The opposite direction (set things on Context) still works,
|
|
272
|
+
* and `extractRunEnv` rebuilds the view if you need a snapshot.
|
|
273
|
+
*
|
|
274
|
+
* Migration path: new APIs accept `RunEnv` instead of `Context` when they
|
|
275
|
+
* only need read access. Existing APIs continue to accept `Context` until
|
|
276
|
+
* a full split is scheduled.
|
|
277
|
+
*/
|
|
278
|
+
interface RunEnv {
|
|
279
|
+
readonly provider: Provider;
|
|
280
|
+
readonly session: SessionWriter;
|
|
281
|
+
readonly signal: AbortSignal;
|
|
282
|
+
readonly tokenCounter: TokenCounter;
|
|
283
|
+
readonly cwd: string;
|
|
284
|
+
readonly projectRoot: string;
|
|
285
|
+
readonly model: string;
|
|
286
|
+
readonly systemPrompt: readonly TextBlock[];
|
|
287
|
+
readonly tools: readonly Tool[];
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Build a `RunEnv` view from a Context. The returned object is a shallow
|
|
291
|
+
* frozen view — mutations to `Context` are visible (it's the same
|
|
292
|
+
* references), but the view itself can't be mutated.
|
|
293
|
+
*
|
|
294
|
+
* Use this in subsystems that want to declare "I only need read access to
|
|
295
|
+
* the env" without rewriting their signature to accept the full Context.
|
|
296
|
+
*/
|
|
297
|
+
declare function extractRunEnv(ctx: Context): RunEnv;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Observable wrapper for the mutable conversation state. Provides snapshot
|
|
301
|
+
* and change-notification semantics on top of the existing `Context`
|
|
302
|
+
* mutable fields (messages, todos, meta), without forcing every call site
|
|
303
|
+
* to migrate.
|
|
304
|
+
*
|
|
305
|
+
* Design notes:
|
|
306
|
+
*
|
|
307
|
+
* - This is a **wrapper**, not a replacement. The underlying `Context`
|
|
308
|
+
* fields are still mutated directly by tools and middleware that don't
|
|
309
|
+
* know about ConversationState. The wrapper observes those mutations by
|
|
310
|
+
* snapshotting on each accessor call, comparing length/identity, and
|
|
311
|
+
* firing onChange. This is intentional during migration: it lets new
|
|
312
|
+
* code subscribe to changes without breaking the unaware-existing code.
|
|
313
|
+
*
|
|
314
|
+
* - For full decoupling (the dev-plan #1 target), every mutation must go
|
|
315
|
+
* through `ConversationState.appendMessage()` etc. instead of
|
|
316
|
+
* `ctx.messages.push(...)`. That's a follow-up refactor — the API shape
|
|
317
|
+
* here is designed to support it.
|
|
318
|
+
*
|
|
319
|
+
* - `meta` is a free-form bag; we shallow-watch its keys. Deep mutations
|
|
320
|
+
* inside `meta.foo` won't trigger onChange. Use immutable replacement
|
|
321
|
+
* (`setMeta('foo', newValue)`) if you need notification.
|
|
322
|
+
*/
|
|
323
|
+
type StateChange = {
|
|
324
|
+
kind: 'message_appended';
|
|
325
|
+
message: Message;
|
|
326
|
+
} | {
|
|
327
|
+
kind: 'messages_replaced';
|
|
328
|
+
messages: readonly Message[];
|
|
329
|
+
} | {
|
|
330
|
+
kind: 'todos_replaced';
|
|
331
|
+
todos: readonly TodoItem[];
|
|
332
|
+
} | {
|
|
333
|
+
kind: 'meta_set';
|
|
334
|
+
key: string;
|
|
335
|
+
value: unknown;
|
|
336
|
+
} | {
|
|
337
|
+
kind: 'meta_deleted';
|
|
338
|
+
key: string;
|
|
339
|
+
};
|
|
340
|
+
type StateChangeHandler = (change: StateChange, state: ConversationState) => void;
|
|
341
|
+
interface ReadonlyConversationState {
|
|
342
|
+
readonly messages: readonly Message[];
|
|
343
|
+
readonly todos: readonly TodoItem[];
|
|
344
|
+
readonly meta: Readonly<Record<string, unknown>>;
|
|
345
|
+
}
|
|
346
|
+
declare class ConversationState {
|
|
347
|
+
private readonly ctx;
|
|
348
|
+
private readonly listeners;
|
|
349
|
+
constructor(ctx: Context);
|
|
350
|
+
get messages(): readonly Message[];
|
|
351
|
+
get todos(): readonly TodoItem[];
|
|
352
|
+
get meta(): Readonly<Record<string, unknown>>;
|
|
353
|
+
/**
|
|
354
|
+
* Cheap immutable snapshot. Useful for tests and for compaction passes
|
|
355
|
+
* that need a stable view across an async boundary.
|
|
356
|
+
*/
|
|
357
|
+
snapshot(): ReadonlyConversationState;
|
|
358
|
+
appendMessage(message: Message): void;
|
|
359
|
+
replaceMessages(messages: Message[]): void;
|
|
360
|
+
replaceTodos(todos: TodoItem[]): void;
|
|
361
|
+
setMeta(key: string, value: unknown): void;
|
|
362
|
+
deleteMeta(key: string): void;
|
|
363
|
+
/**
|
|
364
|
+
* Subscribe to mutations that go through this wrapper. Note: mutations
|
|
365
|
+
* that bypass the wrapper (e.g. `ctx.messages.push(...)` directly) are
|
|
366
|
+
* NOT observed — by design during migration, since we don't want to
|
|
367
|
+
* monkey-patch arrays. Migrating call sites to use this API is the
|
|
368
|
+
* dev-plan #1 work.
|
|
369
|
+
*/
|
|
370
|
+
onChange(listener: StateChangeHandler): () => void;
|
|
371
|
+
private emit;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Convenience constructor — creates a ConversationState bound to the
|
|
375
|
+
* given Context. The wrapper holds a reference, not a copy.
|
|
376
|
+
*/
|
|
377
|
+
declare function wrapAsState(ctx: Context): ConversationState;
|
|
378
|
+
|
|
379
|
+
interface TodoItem {
|
|
380
|
+
id: string;
|
|
381
|
+
content: string;
|
|
382
|
+
status: 'pending' | 'in_progress' | 'completed';
|
|
383
|
+
activeForm?: string;
|
|
384
|
+
}
|
|
385
|
+
interface RunOptions {
|
|
386
|
+
signal?: AbortSignal;
|
|
387
|
+
model?: string;
|
|
388
|
+
executionStrategy?: 'parallel' | 'sequential' | 'smart';
|
|
389
|
+
maxIterations?: number;
|
|
390
|
+
}
|
|
391
|
+
interface ContextInit {
|
|
392
|
+
systemPrompt: TextBlock[];
|
|
393
|
+
provider: Provider;
|
|
394
|
+
session: SessionWriter;
|
|
395
|
+
signal: AbortSignal;
|
|
396
|
+
tokenCounter: TokenCounter;
|
|
397
|
+
cwd: string;
|
|
398
|
+
projectRoot: string;
|
|
399
|
+
model: string;
|
|
400
|
+
tools?: Tool[];
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* L1-A: `Context` is the live agent-run object. Its read-only environment
|
|
404
|
+
* shape is exposed by the `RunEnv` interface (every field below the
|
|
405
|
+
* conversation state) and its mutable shape by `ConversationState` (the
|
|
406
|
+
* `state` accessor). New code should declare the narrower type at its
|
|
407
|
+
* parameter — pass `ctx` for it. Existing tools that accept `Context`
|
|
408
|
+
* still work because `Context` structurally satisfies both.
|
|
409
|
+
*/
|
|
410
|
+
declare class Context implements RunEnv {
|
|
411
|
+
messages: Message[];
|
|
412
|
+
todos: TodoItem[];
|
|
413
|
+
readFiles: Set<string>;
|
|
414
|
+
fileMtimes: Map<string, number>;
|
|
415
|
+
systemPrompt: TextBlock[];
|
|
416
|
+
provider: Provider;
|
|
417
|
+
session: SessionWriter;
|
|
418
|
+
signal: AbortSignal;
|
|
419
|
+
tokenCounter: TokenCounter;
|
|
420
|
+
cwd: string;
|
|
421
|
+
projectRoot: string;
|
|
422
|
+
model: string;
|
|
423
|
+
tools: Tool[];
|
|
424
|
+
meta: Record<string, unknown>;
|
|
425
|
+
constructor(init: ContextInit);
|
|
426
|
+
/**
|
|
427
|
+
* Observable wrapper over the mutable conversation state. Lazy so
|
|
428
|
+
* subsystems that don't subscribe pay nothing. Mutations made directly
|
|
429
|
+
* on `ctx.messages` / `ctx.todos` are still visible through this
|
|
430
|
+
* wrapper's read API (it holds a reference, not a copy) but only
|
|
431
|
+
* mutations that go through `state.appendMessage()` etc. fire
|
|
432
|
+
* `onChange`. New code should prefer the wrapper API.
|
|
433
|
+
*/
|
|
434
|
+
private _state;
|
|
435
|
+
get state(): ConversationState;
|
|
436
|
+
/**
|
|
437
|
+
* Register a teardown hook tied to the current run's abort signal. The
|
|
438
|
+
* hook fires when the run aborts OR ends normally — Agent.run wires
|
|
439
|
+
* this through a RunController. When no run is active the hook fires
|
|
440
|
+
* immediately so callers don't leak resources.
|
|
441
|
+
*
|
|
442
|
+
* **Scope:** these hooks fire on the **whole agent run's** abort, not on
|
|
443
|
+
* an individual tool call. For per-tool teardown of resources owned by
|
|
444
|
+
* the tool author (child processes, handles), prefer `Tool.cleanup` —
|
|
445
|
+
* see its JSDoc for the full rule.
|
|
446
|
+
*/
|
|
447
|
+
private abortHooks;
|
|
448
|
+
registerAbortHook(fn: () => void | Promise<void>): () => void;
|
|
449
|
+
drainAbortHooks(): Promise<void>;
|
|
450
|
+
recordRead(absPath: string, mtimeMs: number): void;
|
|
451
|
+
hasRead(absPath: string): boolean;
|
|
452
|
+
lastReadMtime(absPath: string): number | undefined;
|
|
453
|
+
usage(): Usage;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
type Permission = 'auto' | 'confirm' | 'deny';
|
|
457
|
+
interface JSONSchema {
|
|
458
|
+
type?: string;
|
|
459
|
+
properties?: Record<string, JSONSchema>;
|
|
460
|
+
required?: string[];
|
|
461
|
+
items?: JSONSchema;
|
|
462
|
+
enum?: unknown[];
|
|
463
|
+
description?: string;
|
|
464
|
+
[k: string]: unknown;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Tool progress event — yielded by `Tool.executeStream` to give the UI
|
|
468
|
+
* something to render while a long-running tool works. The executor
|
|
469
|
+
* publishes each event via EventBus as `tool.progress` so the TUI, logger,
|
|
470
|
+
* and observability layer can consume them uniformly.
|
|
471
|
+
*
|
|
472
|
+
* Keep events small. They are buffered through the EventBus synchronously
|
|
473
|
+
* and rendered on the main thread.
|
|
474
|
+
*/
|
|
475
|
+
interface ToolProgressEvent {
|
|
476
|
+
/**
|
|
477
|
+
* - `log` — verbose informational message (e.g. "scanning…")
|
|
478
|
+
* - `warning` — non-fatal issue (e.g. "skipped X due to ENOENT")
|
|
479
|
+
* - `metric` — numeric data (e.g. files scanned so far)
|
|
480
|
+
* - `file_changed` — a tool that mutates the workspace announces a write
|
|
481
|
+
* - `partial_output` — stream of textual output (bash stdout, fetch body)
|
|
482
|
+
*/
|
|
483
|
+
type: 'log' | 'warning' | 'metric' | 'file_changed' | 'partial_output';
|
|
484
|
+
text?: string;
|
|
485
|
+
data?: Record<string, unknown>;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Terminal event for `executeStream`. The output must match the tool's
|
|
489
|
+
* declared output type — the executor unwraps `output` and treats it like
|
|
490
|
+
* a normal `execute` return value.
|
|
491
|
+
*/
|
|
492
|
+
interface ToolFinalEvent<O> {
|
|
493
|
+
type: 'final';
|
|
494
|
+
output: O;
|
|
495
|
+
}
|
|
496
|
+
type ToolStreamEvent<O = unknown> = ToolProgressEvent | ToolFinalEvent<O>;
|
|
497
|
+
interface Tool<I = unknown, O = unknown> {
|
|
498
|
+
name: string;
|
|
499
|
+
description: string;
|
|
500
|
+
usageHint?: string;
|
|
501
|
+
inputSchema: JSONSchema;
|
|
502
|
+
permission: Permission;
|
|
503
|
+
mutating: boolean;
|
|
504
|
+
maxOutputBytes?: number;
|
|
505
|
+
timeoutMs?: number;
|
|
506
|
+
/**
|
|
507
|
+
* Hint for the TUI spinner — does NOT affect actual timeout enforcement.
|
|
508
|
+
* Use `timeoutMs` for hard limits. Leave undefined when duration varies
|
|
509
|
+
* unpredictably.
|
|
510
|
+
*/
|
|
511
|
+
estimatedDurationMs?: number;
|
|
512
|
+
execute(input: I, ctx: Context, opts: {
|
|
513
|
+
signal: AbortSignal;
|
|
514
|
+
}): Promise<O>;
|
|
515
|
+
/**
|
|
516
|
+
* Optional streaming variant. When defined, the executor prefers this
|
|
517
|
+
* over `execute` — yielded events become `tool.progress` EventBus events
|
|
518
|
+
* and the terminal `final` event provides the output. Tools that don't
|
|
519
|
+
* have intermediate state shouldn't implement this; the default `execute`
|
|
520
|
+
* path is more efficient.
|
|
521
|
+
*/
|
|
522
|
+
executeStream?(input: I, ctx: Context, opts: {
|
|
523
|
+
signal: AbortSignal;
|
|
524
|
+
}): AsyncIterable<ToolStreamEvent<O>>;
|
|
525
|
+
/**
|
|
526
|
+
* Optional teardown hook fired by the executor when the tool's run is
|
|
527
|
+
* aborted (signal triggered). Errors thrown here are swallowed so they
|
|
528
|
+
* never mask the originating failure.
|
|
529
|
+
*
|
|
530
|
+
* **When to use `cleanup` vs `ctx.registerAbortHook`:**
|
|
531
|
+
*
|
|
532
|
+
* - Use `cleanup` for resources **owned by the tool author** that are
|
|
533
|
+
* established at execute-time: child processes spawned by the tool,
|
|
534
|
+
* file handles opened by the tool, network connections initiated by
|
|
535
|
+
* the tool. The lifecycle is co-located with the tool definition, so
|
|
536
|
+
* readers see the resource and its teardown in one place.
|
|
537
|
+
*
|
|
538
|
+
* ```ts
|
|
539
|
+
* async execute(input, ctx, opts) {
|
|
540
|
+
* const child = spawn(...);
|
|
541
|
+
* // … tool work …
|
|
542
|
+
* },
|
|
543
|
+
* async cleanup(_input, _ctx) {
|
|
544
|
+
* // best-effort kill of any child still running
|
|
545
|
+
* }
|
|
546
|
+
* ```
|
|
547
|
+
*
|
|
548
|
+
* - Use `ctx.registerAbortHook` for **context-scoped teardown** registered
|
|
549
|
+
* dynamically inside `execute`: when the tool delegates to a library
|
|
550
|
+
* that needs cancellation, or when the resource is created lazily
|
|
551
|
+
* somewhere down the call stack and the natural cleanup point isn't
|
|
552
|
+
* at the tool boundary. The hook fires when the **agent run** ends,
|
|
553
|
+
* not when this specific tool call aborts.
|
|
554
|
+
*
|
|
555
|
+
* ```ts
|
|
556
|
+
* async execute(input, ctx, opts) {
|
|
557
|
+
* const handle = openHelper();
|
|
558
|
+
* ctx.registerAbortHook(() => handle.dispose());
|
|
559
|
+
* // … work …
|
|
560
|
+
* }
|
|
561
|
+
* ```
|
|
562
|
+
*
|
|
563
|
+
* If both are registered for the same resource, `cleanup` fires first
|
|
564
|
+
* (on tool abort) and the abort-hook fires after on the wider run abort.
|
|
565
|
+
* Avoid double-free by gating one on the other's effect, or pick a single
|
|
566
|
+
* teardown channel per resource.
|
|
567
|
+
*/
|
|
568
|
+
cleanup?(input: I, ctx: Context): Promise<void>;
|
|
569
|
+
}
|
|
570
|
+
interface ToolCallContext {
|
|
571
|
+
tool: Tool;
|
|
572
|
+
input: unknown;
|
|
573
|
+
callId: string;
|
|
574
|
+
ctx: Context;
|
|
575
|
+
signal: AbortSignal;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* WrongStack error hierarchy.
|
|
580
|
+
*
|
|
581
|
+
* Every error thrown by the framework is a `WrongStackError` with a
|
|
582
|
+
* machine-readable `code`, a `subsystem` tag, and a `severity` level.
|
|
583
|
+
* This lets consumers (CLI, TUI, plugins, tests) branch on structured
|
|
584
|
+
* data instead of parsing error messages.
|
|
585
|
+
*/
|
|
586
|
+
type ErrorCode = 'PROVIDER_RATE_LIMITED' | 'PROVIDER_AUTH_FAILED' | 'PROVIDER_OVERLOADED' | 'PROVIDER_INVALID_REQUEST' | 'PROVIDER_SERVER_ERROR' | 'PROVIDER_NETWORK_ERROR' | 'PROVIDER_CONTEXT_OVERFLOW' | 'TOOL_NOT_FOUND' | 'TOOL_PERMISSION_DENIED' | 'TOOL_EXECUTION_FAILED' | 'TOOL_TIMEOUT' | 'TOOL_INPUT_INVALID' | 'CONFIG_INVALID' | 'CONFIG_NOT_FOUND' | 'CONFIG_PARSE_FAILED' | 'CONFIG_MIGRATION_NEEDED' | 'PLUGIN_LOAD_FAILED' | 'PLUGIN_API_MISMATCH' | 'PLUGIN_MISSING_DEPENDENCY' | 'AGENT_ITERATION_LIMIT' | 'AGENT_CONTEXT_OVERFLOW' | 'AGENT_ABORTED' | 'AGENT_RUN_FAILED' | 'SESSION_NOT_FOUND' | 'SESSION_CORRUPTED' | 'SESSION_WRITE_FAILED' | 'CONTAINER_TOKEN_ALREADY_BOUND' | 'CONTAINER_TOKEN_NOT_BOUND' | 'REGISTRY_DUPLICATE' | 'REGISTRY_NOT_FOUND' | 'UNKNOWN';
|
|
587
|
+
type ErrorSubsystem = 'provider' | 'tool' | 'config' | 'plugin' | 'agent' | 'session' | 'container' | 'general';
|
|
588
|
+
type ErrorSeverity = 'fatal' | 'error' | 'warning';
|
|
589
|
+
declare class WrongStackError extends Error {
|
|
590
|
+
readonly code: ErrorCode;
|
|
591
|
+
readonly subsystem: ErrorSubsystem;
|
|
592
|
+
readonly severity: ErrorSeverity;
|
|
593
|
+
readonly recoverable: boolean;
|
|
594
|
+
readonly context?: Record<string, unknown>;
|
|
595
|
+
constructor(opts: {
|
|
596
|
+
message: string;
|
|
597
|
+
code: ErrorCode;
|
|
598
|
+
subsystem: ErrorSubsystem;
|
|
599
|
+
severity?: ErrorSeverity;
|
|
600
|
+
recoverable?: boolean;
|
|
601
|
+
context?: Record<string, unknown>;
|
|
602
|
+
cause?: unknown;
|
|
603
|
+
});
|
|
604
|
+
/**
|
|
605
|
+
* Render a one-line user-facing description.
|
|
606
|
+
* Subclasses should override for domain-specific formatting.
|
|
607
|
+
*/
|
|
608
|
+
describe(): string;
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Tool execution errors — thrown by ToolExecutor and individual tools.
|
|
612
|
+
*/
|
|
613
|
+
declare class ToolError extends WrongStackError {
|
|
614
|
+
readonly toolName: string;
|
|
615
|
+
constructor(opts: {
|
|
616
|
+
message: string;
|
|
617
|
+
code: Extract<ErrorCode, 'TOOL_NOT_FOUND' | 'TOOL_PERMISSION_DENIED' | 'TOOL_EXECUTION_FAILED' | 'TOOL_TIMEOUT' | 'TOOL_INPUT_INVALID'>;
|
|
618
|
+
toolName: string;
|
|
619
|
+
recoverable?: boolean;
|
|
620
|
+
context?: Record<string, unknown>;
|
|
621
|
+
cause?: unknown;
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Config loading / validation errors.
|
|
626
|
+
*/
|
|
627
|
+
declare class ConfigError extends WrongStackError {
|
|
628
|
+
constructor(opts: {
|
|
629
|
+
message: string;
|
|
630
|
+
code: Extract<ErrorCode, 'CONFIG_INVALID' | 'CONFIG_NOT_FOUND' | 'CONFIG_PARSE_FAILED' | 'CONFIG_MIGRATION_NEEDED'>;
|
|
631
|
+
context?: Record<string, unknown>;
|
|
632
|
+
cause?: unknown;
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Plugin loading / lifecycle errors.
|
|
637
|
+
*/
|
|
638
|
+
declare class PluginError extends WrongStackError {
|
|
639
|
+
readonly pluginName: string;
|
|
640
|
+
constructor(opts: {
|
|
641
|
+
message: string;
|
|
642
|
+
code: Extract<ErrorCode, 'PLUGIN_LOAD_FAILED' | 'PLUGIN_API_MISMATCH' | 'PLUGIN_MISSING_DEPENDENCY'>;
|
|
643
|
+
pluginName: string;
|
|
644
|
+
context?: Record<string, unknown>;
|
|
645
|
+
cause?: unknown;
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Agent runtime errors — thrown by Agent.run when a non-WrongStackError
|
|
650
|
+
* escapes the inner loop, so callers always see a structured error.
|
|
651
|
+
*/
|
|
652
|
+
declare class AgentError extends WrongStackError {
|
|
653
|
+
constructor(opts: {
|
|
654
|
+
message: string;
|
|
655
|
+
code: Extract<ErrorCode, 'AGENT_ITERATION_LIMIT' | 'AGENT_CONTEXT_OVERFLOW' | 'AGENT_ABORTED' | 'AGENT_RUN_FAILED'>;
|
|
656
|
+
recoverable?: boolean;
|
|
657
|
+
context?: Record<string, unknown>;
|
|
658
|
+
cause?: unknown;
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Wrap an arbitrary thrown value into a `WrongStackError` so the caller
|
|
663
|
+
* always gets a structured error. Pass-throughs WrongStackError instances
|
|
664
|
+
* unchanged; raw `Error`s and primitives get an `AGENT_RUN_FAILED` wrapper
|
|
665
|
+
* with the original preserved as `cause`.
|
|
666
|
+
*/
|
|
667
|
+
declare function toWrongStackError(err: unknown, code?: Extract<ErrorCode, 'AGENT_RUN_FAILED' | 'AGENT_ABORTED' | 'UNKNOWN'>): WrongStackError;
|
|
668
|
+
/**
|
|
669
|
+
* Session storage errors.
|
|
670
|
+
*/
|
|
671
|
+
declare class SessionError extends WrongStackError {
|
|
672
|
+
readonly sessionId?: string;
|
|
673
|
+
constructor(opts: {
|
|
674
|
+
message: string;
|
|
675
|
+
code: Extract<ErrorCode, 'SESSION_NOT_FOUND' | 'SESSION_CORRUPTED' | 'SESSION_WRITE_FAILED'>;
|
|
676
|
+
sessionId?: string;
|
|
677
|
+
context?: Record<string, unknown>;
|
|
678
|
+
cause?: unknown;
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
declare function isWrongStackError(err: unknown): err is WrongStackError;
|
|
682
|
+
declare function isToolError(err: unknown): err is ToolError;
|
|
683
|
+
declare function isConfigError(err: unknown): err is ConfigError;
|
|
684
|
+
declare function isPluginError(err: unknown): err is PluginError;
|
|
685
|
+
declare function isSessionError(err: unknown): err is SessionError;
|
|
686
|
+
declare function isAgentError(err: unknown): err is AgentError;
|
|
687
|
+
|
|
688
|
+
interface Usage {
|
|
689
|
+
input: number;
|
|
690
|
+
output: number;
|
|
691
|
+
cacheRead?: number;
|
|
692
|
+
cacheWrite?: number;
|
|
693
|
+
}
|
|
694
|
+
interface Capabilities {
|
|
695
|
+
tools: boolean;
|
|
696
|
+
parallelTools: boolean;
|
|
697
|
+
vision: boolean;
|
|
698
|
+
streaming: boolean;
|
|
699
|
+
promptCache: boolean;
|
|
700
|
+
systemPrompt: boolean;
|
|
701
|
+
jsonMode: boolean;
|
|
702
|
+
maxContext: number;
|
|
703
|
+
cacheControl: 'native' | 'auto' | 'none';
|
|
704
|
+
}
|
|
705
|
+
interface Request {
|
|
706
|
+
model: string;
|
|
707
|
+
system?: TextBlock[];
|
|
708
|
+
messages: Message[];
|
|
709
|
+
tools?: Tool[];
|
|
710
|
+
maxTokens: number;
|
|
711
|
+
temperature?: number;
|
|
712
|
+
topP?: number;
|
|
713
|
+
stopSequences?: string[];
|
|
714
|
+
toolChoice?: 'auto' | 'required' | 'none' | {
|
|
715
|
+
type: 'tool';
|
|
716
|
+
name: string;
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
type StopReason = 'end_turn' | 'tool_use' | 'max_tokens' | 'stop_sequence' | 'refusal';
|
|
720
|
+
interface Response {
|
|
721
|
+
content: ContentBlock[];
|
|
722
|
+
stopReason: StopReason;
|
|
723
|
+
usage: Usage;
|
|
724
|
+
model: string;
|
|
725
|
+
}
|
|
726
|
+
type StreamEvent = {
|
|
727
|
+
type: 'message_start';
|
|
728
|
+
model: string;
|
|
729
|
+
} | {
|
|
730
|
+
type: 'content_block_start';
|
|
731
|
+
kind: 'text' | 'tool_use';
|
|
732
|
+
id?: string;
|
|
733
|
+
name?: string;
|
|
734
|
+
} | {
|
|
735
|
+
type: 'content_block_stop';
|
|
736
|
+
index: number;
|
|
737
|
+
} | {
|
|
738
|
+
type: 'text_delta';
|
|
739
|
+
text: string;
|
|
740
|
+
} | {
|
|
741
|
+
type: 'tool_use_start';
|
|
742
|
+
id: string;
|
|
743
|
+
name: string;
|
|
744
|
+
} | {
|
|
745
|
+
type: 'tool_use_input_delta';
|
|
746
|
+
id: string;
|
|
747
|
+
partial: string;
|
|
748
|
+
} | {
|
|
749
|
+
type: 'tool_use_stop';
|
|
750
|
+
id: string;
|
|
751
|
+
input: unknown;
|
|
752
|
+
providerMeta?: Record<string, unknown>;
|
|
753
|
+
} | {
|
|
754
|
+
type: 'message_stop';
|
|
755
|
+
stopReason: StopReason;
|
|
756
|
+
usage: Usage;
|
|
757
|
+
};
|
|
758
|
+
interface Provider {
|
|
759
|
+
readonly id: string;
|
|
760
|
+
readonly capabilities: Capabilities;
|
|
761
|
+
/** Canonical streaming entry point. `complete()` defaults to a wrapper that
|
|
762
|
+
* aggregates this stream — providers may override for non-streaming wires. */
|
|
763
|
+
stream(req: Request, opts: {
|
|
764
|
+
signal: AbortSignal;
|
|
765
|
+
}): AsyncIterable<StreamEvent>;
|
|
766
|
+
complete(req: Request, opts: {
|
|
767
|
+
signal: AbortSignal;
|
|
768
|
+
}): Promise<Response>;
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Structured body parsed from a provider's HTTP error response. Populated
|
|
772
|
+
* best-effort: providers return JSON shaped differently (Anthropic uses
|
|
773
|
+
* `{error: {type, message}}`, OpenAI uses `{error: {message, code}}`,
|
|
774
|
+
* Google uses `{error: {status, message}}`), so the fields here are the
|
|
775
|
+
* intersection that's usable for rendering and routing.
|
|
776
|
+
*/
|
|
777
|
+
interface ProviderErrorBody {
|
|
778
|
+
/** Provider-specific kind, e.g. "overloaded_error", "rate_limit_error", "invalid_request_error". */
|
|
779
|
+
type?: string;
|
|
780
|
+
/** Human-readable explanation from the provider. */
|
|
781
|
+
message?: string;
|
|
782
|
+
/** Provider request id, when present in the body or headers. */
|
|
783
|
+
requestId?: string;
|
|
784
|
+
/** Parsed Retry-After header (or equivalent body hint) in milliseconds. */
|
|
785
|
+
retryAfterMs?: number;
|
|
786
|
+
/** The raw response body (truncated), kept for debugging. */
|
|
787
|
+
raw?: string;
|
|
788
|
+
}
|
|
789
|
+
declare class ProviderError extends WrongStackError {
|
|
790
|
+
readonly status: number;
|
|
791
|
+
readonly retryable: boolean;
|
|
792
|
+
readonly providerId: string;
|
|
793
|
+
readonly body?: ProviderErrorBody;
|
|
794
|
+
constructor(message: string, status: number, retryable: boolean, providerId: string, opts?: {
|
|
795
|
+
body?: ProviderErrorBody;
|
|
796
|
+
cause?: unknown;
|
|
797
|
+
});
|
|
798
|
+
/**
|
|
799
|
+
* Render a one-line, user-facing description. Designed for the CLI/TUI
|
|
800
|
+
* status line and the agent's retry warning. Avoids dumping raw JSON
|
|
801
|
+
* (which is what users see today when a 529 lands and the log message
|
|
802
|
+
* includes the full `{"type":"error",...}` body).
|
|
803
|
+
*
|
|
804
|
+
* Examples:
|
|
805
|
+
* "minimax-coding-plan overloaded (529): High traffic detected. Upgrade for highspeed model. [req 06534785201de9c0…]"
|
|
806
|
+
* "openai rate limited (429): Retry after 12s"
|
|
807
|
+
* "anthropic invalid request (400): messages.0.role must be one of 'user'|'assistant'"
|
|
808
|
+
* "groq HTTP 500 (server error)"
|
|
809
|
+
*/
|
|
810
|
+
describe(): string;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
export { toWrongStackError as $, AgentError as A, type ToolResultBlock as B, type CacheStats as C, type ToolStreamEvent as D, type ErrorCode as E, type ToolUseBlock as F, asBlocks as G, asText as H, type ImageBlock as I, type JSONSchema as J, isAgentError as K, isConfigError as L, type Message as M, isImageBlock as N, isPluginError as O, type Permission as P, isSessionError as Q, type Request as R, type SessionData as S, type TextBlock as T, type Usage as U, isTextBlock as V, WrongStackError as W, isToolError as X, isToolResultBlock as Y, isToolUseBlock as Z, isWrongStackError as _, type Capabilities as a, Context as a0, type ContextInit as a1, ConversationState as a2, type ReadonlyConversationState as a3, type RunEnv as a4, type RunOptions as a5, type StateChange as a6, type StateChangeHandler as a7, type TodoItem as a8, extractRunEnv as a9, wrapAsState as aa, ConfigError as b, type ContentBlock as c, type ErrorSeverity as d, type ErrorSubsystem as e, type MessageRole as f, PluginError as g, type Provider as h, ProviderError as i, type ProviderErrorBody as j, type Response as k, type ResumedSession as l, SessionError as m, type SessionEvent as n, type SessionMetadata as o, type SessionStore as p, type SessionSummary as q, type SessionWriter as r, type StopReason as s, type StreamEvent as t, type TokenCounter as u, type Tool as v, type ToolCallContext as w, ToolError as x, type ToolFinalEvent as y, type ToolProgressEvent as z };
|