@vib-rato/agent-core 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +852 -0
  2. package/README.md +493 -0
  3. package/dist/types/agent-loop.d.ts +229 -0
  4. package/dist/types/agent.d.ts +533 -0
  5. package/dist/types/append-only-context.d.ts +141 -0
  6. package/dist/types/attempt-scope.d.ts +84 -0
  7. package/dist/types/compaction/adaptive.d.ts +31 -0
  8. package/dist/types/compaction/branch-summarization.d.ts +103 -0
  9. package/dist/types/compaction/compaction.d.ts +330 -0
  10. package/dist/types/compaction/entries.d.ts +124 -0
  11. package/dist/types/compaction/errors.d.ts +26 -0
  12. package/dist/types/compaction/index.d.ts +12 -0
  13. package/dist/types/compaction/messages.d.ts +61 -0
  14. package/dist/types/compaction/openai.d.ts +65 -0
  15. package/dist/types/compaction/pruning.d.ts +130 -0
  16. package/dist/types/compaction/utils.d.ts +32 -0
  17. package/dist/types/compaction.d.ts +1 -0
  18. package/dist/types/harmony-leak.d.ts +100 -0
  19. package/dist/types/heap-eviction-retainers.test.d.ts +1 -0
  20. package/dist/types/image-placeholder-guard.d.ts +4 -0
  21. package/dist/types/index.d.ts +13 -0
  22. package/dist/types/proxy.d.ts +95 -0
  23. package/dist/types/run-collector.d.ts +223 -0
  24. package/dist/types/run-resource-ledger.d.ts +2 -0
  25. package/dist/types/telemetry.d.ts +605 -0
  26. package/dist/types/thinking.d.ts +18 -0
  27. package/dist/types/tool-dispatch-identity.d.ts +27 -0
  28. package/dist/types/types.d.ts +790 -0
  29. package/package.json +72 -0
  30. package/src/agent-loop.ts +5632 -0
  31. package/src/agent.ts +2437 -0
  32. package/src/append-only-context.ts +496 -0
  33. package/src/attempt-scope.ts +195 -0
  34. package/src/compaction/adaptive.ts +92 -0
  35. package/src/compaction/branch-summarization.ts +358 -0
  36. package/src/compaction/compaction.ts +1569 -0
  37. package/src/compaction/entries.ts +158 -0
  38. package/src/compaction/errors.ts +31 -0
  39. package/src/compaction/index.ts +13 -0
  40. package/src/compaction/messages.ts +212 -0
  41. package/src/compaction/openai.ts +580 -0
  42. package/src/compaction/prompts/auto-handoff-threshold-focus.md +1 -0
  43. package/src/compaction/prompts/branch-summary-context.md +5 -0
  44. package/src/compaction/prompts/branch-summary-preamble.md +2 -0
  45. package/src/compaction/prompts/branch-summary.md +30 -0
  46. package/src/compaction/prompts/compaction-short-summary.md +9 -0
  47. package/src/compaction/prompts/compaction-summary-context.md +5 -0
  48. package/src/compaction/prompts/compaction-summary.md +38 -0
  49. package/src/compaction/prompts/compaction-turn-prefix.md +17 -0
  50. package/src/compaction/prompts/compaction-update-summary.md +45 -0
  51. package/src/compaction/prompts/file-operations.md +10 -0
  52. package/src/compaction/prompts/handoff-document.md +56 -0
  53. package/src/compaction/prompts/summarization-system.md +3 -0
  54. package/src/compaction/pruning.ts +1026 -0
  55. package/src/compaction/utils.ts +189 -0
  56. package/src/compaction.ts +1 -0
  57. package/src/harmony-leak.ts +457 -0
  58. package/src/heap-eviction-retainers.test.ts +293 -0
  59. package/src/image-placeholder-guard.ts +20 -0
  60. package/src/index.ts +23 -0
  61. package/src/prompts/escaped-nonascii-recovery.md +3 -0
  62. package/src/prompts/repeated-tool-failure-recovery.md +1 -0
  63. package/src/proxy.ts +408 -0
  64. package/src/run-collector.ts +728 -0
  65. package/src/run-resource-ledger.ts +345 -0
  66. package/src/telemetry.ts +2161 -0
  67. package/src/thinking.ts +20 -0
  68. package/src/tool-dispatch-identity.ts +87 -0
  69. package/src/types.ts +882 -0
@@ -0,0 +1,790 @@
1
+ import type { AssistantMessage, AssistantMessageEvent, AssistantMessageEventStream, Effort, ImageContent, Message, Model, SimpleStreamOptions, Static, streamSimple, TextContent, Tool, ToolChoice, ToolResultMessage, TransportFailureFacts, TSchema, UserMessage } from "@vib-rato/ai";
2
+ import type { AppendOnlyContextManager } from "./append-only-context";
3
+ import type { AttemptMinter, AttemptRunHandle, AttemptScope } from "./attempt-scope";
4
+ import type { HarmonyAuditEvent } from "./harmony-leak";
5
+ import type { AgentRunCoverage, AgentRunSummary } from "./run-collector";
6
+ import type { AgentTelemetryConfig } from "./telemetry";
7
+ /** Stream function - can return sync or Promise for async config lookup */
8
+ export type StreamFn = (...args: Parameters<typeof streamSimple>) => AssistantMessageEventStream | Promise<AssistantMessageEventStream>;
9
+ /**
10
+ * Request context supplied to provider-aware metadata resolvers.
11
+ *
12
+ * The model is the exact model selected for the concrete request (including
13
+ * fallback and ephemeral requests), while `transport` distinguishes the
14
+ * built-in stream path from a caller-supplied stream function. Metadata that
15
+ * carries provider identity must use both values to fail closed when routing
16
+ * is not the canonical provider path.
17
+ */
18
+ export interface AgentMetadataResolverContext {
19
+ provider: string;
20
+ model?: Model;
21
+ transport?: "default" | "custom";
22
+ }
23
+ /** Stable identifier for a managed logical run, shared by all of its retry attempts. */
24
+ export type ManagedLogicalRunId = number;
25
+ /** A resource owned by a prompt run until its promise settles. */
26
+ export type RunResourceKind = "provider_factory" | "provider_iterator" | "tool" | "post_prompt";
27
+ export interface RunResourceEntry {
28
+ id: string;
29
+ kind: RunResourceKind;
30
+ label: string;
31
+ registeredAt: number;
32
+ }
33
+ export type RunSettlementReason = "unknown_run" | "run_not_sealed" | "resources_pending" | "quarantined";
34
+ export type RunSettlementProof = {
35
+ status: "settled";
36
+ } | {
37
+ status: "unfenced";
38
+ reason: RunSettlementReason;
39
+ pending: RunResourceEntry[];
40
+ };
41
+ export interface RunCancellationDomain {
42
+ readonly resourceRunId: string;
43
+ readonly signal: AbortSignal;
44
+ }
45
+ export interface RunCancellationDomainBridge {
46
+ open(resourceRunId: string): {
47
+ ok: true;
48
+ domain: RunCancellationDomain;
49
+ created: boolean;
50
+ } | {
51
+ ok: false;
52
+ reason: "duplicate_identity" | "quarantined";
53
+ };
54
+ lookup(resourceRunId: string): RunCancellationDomain | undefined;
55
+ abort(resourceRunId: string, reason?: unknown): {
56
+ ok: true;
57
+ newlyAborted: boolean;
58
+ } | {
59
+ ok: false;
60
+ reason: "unknown_run" | "quarantined";
61
+ };
62
+ release(resourceRunId: string, disposition: "settled" | "quarantined"): void;
63
+ }
64
+ export type ReserveProducerResult = {
65
+ ok: true;
66
+ lease: RunResourceProducerLease;
67
+ } | {
68
+ ok: false;
69
+ reason: "unknown_run" | "sealed" | "quarantined" | "domain_mismatch";
70
+ };
71
+ export type ClaimProducerResult = {
72
+ ok: true;
73
+ lease: RunResourceProducerLease;
74
+ } | {
75
+ ok: false;
76
+ reason: "already_claimed" | "handle_mismatch" | "domain_mismatch" | "closed" | "quarantined";
77
+ };
78
+ export type ForkProducerResult = {
79
+ ok: true;
80
+ lease: RunResourceProducerLease;
81
+ } | {
82
+ ok: false;
83
+ reason: "parent_closed" | "quarantined" | "domain_mismatch";
84
+ };
85
+ export interface RunResourceProducerLease {
86
+ readonly resourceRunId: string;
87
+ readonly domain: RunCancellationDomain;
88
+ readonly signal: AbortSignal;
89
+ track(kind: RunResourceKind, label: string, settled: PromiseLike<unknown>): boolean;
90
+ fork(expectedDomain: RunCancellationDomain, kind: RunResourceKind, label: string): ForkProducerResult;
91
+ closeDiscovery(): void;
92
+ }
93
+ export interface AgentTerminalOwnerContext {
94
+ readonly resourceRunId: string;
95
+ readonly domain: RunCancellationDomain;
96
+ }
97
+ export declare function setAgentTerminalOwnerContext(event: object, context: AgentTerminalOwnerContext): void;
98
+ export declare function getAgentTerminalOwnerContext(event: object): AgentTerminalOwnerContext | undefined;
99
+ export interface StandaloneRunOwnership {
100
+ readonly resourceRunId: string;
101
+ readonly domain: RunCancellationDomain;
102
+ claimContinuation(): {
103
+ ok: true;
104
+ ownership: StandaloneRunOwnership;
105
+ } | {
106
+ ok: false;
107
+ reason: "already_claimed" | "terminal" | "quarantined";
108
+ };
109
+ abandon(reason: "cancelled" | "error"): void;
110
+ }
111
+ export interface RunResourceLedger {
112
+ /** Bind the bridge once, before any logical run may be opened. */
113
+ bindCancellationDomainBridge(bridge: RunCancellationDomainBridge): void;
114
+ /** Bind the unforgeable AgentSession claim key once, before terminal publication. */
115
+ bindAgentSessionClaimKey(key: object): void;
116
+ /** Reserve a run handle before publishing its `agent_start` event. */
117
+ open(resourceRunId: string): RunCancellationDomain | undefined;
118
+ lookupDomain(resourceRunId: string): RunCancellationDomain | undefined;
119
+ reserveProducer(resourceRunId: string, expectedDomain: RunCancellationDomain | undefined, kind: RunResourceKind, label: string): ReserveProducerResult;
120
+ claimProducer(resourceRunId: string, expectedDomain: RunCancellationDomain | undefined, ownerKey: object): ClaimProducerResult;
121
+ track(resourceRunId: string, kind: RunResourceKind, label: string, settled: PromiseLike<unknown>): void;
122
+ pending(resourceRunId: string): RunResourceEntry[];
123
+ /** Seal a run after terminal event publication; only sealed empty runs settle. */
124
+ seal(resourceRunId: string): void;
125
+ waitForSettlement(resourceRunId: string, options: {
126
+ graceMs: number;
127
+ }): Promise<RunSettlementProof>;
128
+ /** Terminally detach a run; its bounded tombstone remains unfenced forever. */
129
+ quarantine(resourceRunId: string): RunResourceEntry[];
130
+ }
131
+ /** Terminal completion requested for a logical run. */
132
+ export interface RunTerminalRequest {
133
+ stopReason: "cancelled" | "error" | "exhausted";
134
+ messages?: AgentMessage[];
135
+ }
136
+ /**
137
+ * Ownership token supplied when Agent invokes a retry continuation.
138
+ *
139
+ * A continuation MUST verify `isCurrent()` immediately before starting a
140
+ * follow-up invocation and abandon the retry when it returns false. The token
141
+ * becomes invalid when its originating run is force-aborted or superseded.
142
+ * Coding-agent retry continuations must accept this argument and must not call
143
+ * `agent.continue()` after ownership has been lost.
144
+ */
145
+ export interface ManagedAttemptContinuationOwnership {
146
+ /** Per-attempt run-loop id; use only for attempt-local ownership checks. */
147
+ readonly runId: number;
148
+ /** Stable managed logical-run id; use for all terminal completion requests. */
149
+ readonly logicalRunId: ManagedLogicalRunId;
150
+ readonly generation: number;
151
+ readonly domain: RunCancellationDomain;
152
+ readonly lease: RunResourceProducerLease;
153
+ /** Immutable per-attempt handle used by terminalizers and continuations. */
154
+ readonly handle: AttemptRunHandle;
155
+ isCurrent(): boolean;
156
+ }
157
+ /** Runs after a discarded attempt is idle, only while its ownership token remains current. */
158
+ export type ManagedAttemptContinuation = (ownership: ManagedAttemptContinuationOwnership) => void | Promise<void>;
159
+ /** Decision returned by managed fallback policy for one provisional attempt. */
160
+ export type ManagedAttemptDecision = {
161
+ type: "retry";
162
+ continuation: ManagedAttemptContinuation;
163
+ } | {
164
+ type: "maintenance";
165
+ continuation: ManagedAttemptContinuation;
166
+ } | {
167
+ type: "terminal";
168
+ terminal: RunTerminalRequest;
169
+ };
170
+ /** Structured result for one managed upstream invocation. */
171
+ export type ManagedAttemptOutcome = {
172
+ type: "retryable_discarded";
173
+ failure: {
174
+ message: AssistantMessage;
175
+ /** Exact provider transport facts, including retry headers, for fallback policy. */
176
+ transportFailure?: TransportFailureFacts;
177
+ };
178
+ scope?: AttemptScope;
179
+ } | {
180
+ type: "escaped_arguments_discarded";
181
+ /** The defective assistant turn; already removed from usable history by the loop. */
182
+ message: AssistantMessage;
183
+ scope?: AttemptScope;
184
+ } | {
185
+ type: "context_overflow_discarded";
186
+ message: AssistantMessage;
187
+ scope?: AttemptScope;
188
+ } | {
189
+ type: "run_terminal";
190
+ reason: "cancelled" | "error" | "exhausted";
191
+ scope?: AttemptScope;
192
+ };
193
+ export type ManagedAttemptOutcomeHandler = (outcome: ManagedAttemptOutcome) => ManagedAttemptDecision | Promise<ManagedAttemptDecision>;
194
+ /**
195
+ * Outcome of a cooperative mid-run context-maintenance checkpoint (see
196
+ * {@link AgentLoopConfig.maintainContext}). Any value other than "not-needed"
197
+ * means the checkpoint mutated (or attempted to mutate) durable context, so the
198
+ * loop ends the current run without the lossy `agent_end` finalization and the
199
+ * maintenance owner resumes the run on the rewritten context.
200
+ */
201
+ export type MidRunMaintenanceOutcome = "not-needed" | "pruned" | "compacted" | "promoted" | "failed" | "aborted";
202
+ export interface ContextMaintenanceResult {
203
+ outcome: MidRunMaintenanceOutcome;
204
+ releaseCurrentContext?: boolean;
205
+ }
206
+ /**
207
+ * Configuration for the agent loop.
208
+ */
209
+ export interface AgentLoopConfig extends SimpleStreamOptions {
210
+ model: Model;
211
+ /**
212
+ * Supplies a fresh opaque token at each concrete managed transport invocation.
213
+ * The callback runs at the stream boundary so controller accounting matches
214
+ * upstream request count, including multi-step tool turns.
215
+ */
216
+ nextFallbackAttempt?: (model: Model) => SimpleStreamOptions["fallbackAttempt"];
217
+ /** Called after a managed upstream request is accepted and committed. */
218
+ onManagedAttemptAccepted?: () => void | Promise<void>;
219
+ /** Receives a managed invocation outcome without publishing provisional lifecycle events. */
220
+ onManagedAttemptOutcome?: ManagedAttemptOutcomeHandler;
221
+ /** Per-attempt scope allocator for direct loop callers. */
222
+ attemptMinter?: AttemptMinter;
223
+ /** Scope allocated by the owning Agent for the first attempt in this loop. */
224
+ initialScope?: AttemptScope;
225
+ /**
226
+ * When to interrupt tool execution for steering messages.
227
+ * - "immediate" = check after each tool call (default)
228
+ * - "wait" = defer steering until the current turn completes
229
+ */
230
+ interruptMode?: "immediate" | "wait";
231
+ /**
232
+ * Optional session identifier forwarded to LLM providers.
233
+ * Used by providers that support session-based caching (e.g., OpenAI code provider).
234
+ */
235
+ sessionId?: string;
236
+ /**
237
+ * Optional provider-facing cache/session affinity identifier. When set, this
238
+ * is forwarded to providers as StreamOptions.sessionId while `sessionId`
239
+ * remains the logical agent conversation id for telemetry/metadata.
240
+ */
241
+ providerSessionId?: string;
242
+ /**
243
+ * Optional resolver called per LLM request to produce request metadata.
244
+ * When set, the agent loop evaluates it **after** `getApiKey` resolves the
245
+ * session-sticky credential, ensuring the metadata's `account_uuid` reflects
246
+ * the credential actually used for the request (not the credential that was
247
+ * current when `AgentLoopConfig` was first constructed). Overrides the static
248
+ * `metadata` field when present.
249
+ */
250
+ metadataResolver?: (context: AgentMetadataResolverContext) => Record<string, unknown> | undefined;
251
+ /**
252
+ * Converts AgentMessage[] to LLM-compatible Message[] before each LLM call.
253
+ *
254
+ * Each AgentMessage must be converted to a UserMessage, AssistantMessage, or ToolResultMessage
255
+ * that the LLM can understand. AgentMessages that cannot be converted (e.g., UI-only notifications,
256
+ * status messages) should be filtered out.
257
+ *
258
+ * @example
259
+ * ```typescript
260
+ * convertToLlm: (messages) => messages.flatMap(m => {
261
+ * if (m.role === "custom") {
262
+ * // Convert custom message to user message
263
+ * return [{ role: "user", content: m.content, timestamp: m.timestamp }];
264
+ * }
265
+ * if (m.role === "notification") {
266
+ * // Filter out UI-only messages
267
+ * return [];
268
+ * }
269
+ * // Pass through standard LLM messages
270
+ * return [m];
271
+ * })
272
+ * ```
273
+ */
274
+ convertToLlm: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;
275
+ /**
276
+ * Optional transform applied to the context before `convertToLlm`.
277
+ *
278
+ * Use this for operations that work at the AgentMessage level:
279
+ * - Context window management (pruning old messages)
280
+ * - Injecting context from external sources
281
+ *
282
+ * @example
283
+ * ```typescript
284
+ * transformContext: async (messages) => {
285
+ * if (estimateTokens(messages) > MAX_TOKENS) {
286
+ * return pruneOldMessages(messages);
287
+ * }
288
+ * return messages;
289
+ * }
290
+ * ```
291
+ */
292
+ transformContext?: (messages: AgentMessage[], signal?: AbortSignal, scope?: AttemptScope) => Promise<AgentMessage[]>;
293
+ /**
294
+ * Resolves an API key dynamically for each LLM call.
295
+ *
296
+ * Useful for short-lived OAuth tokens (e.g., GitHub Copilot) that may expire
297
+ * during long-running tool execution phases.
298
+ */
299
+ getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
300
+ /** Returns the credential type selected by the most recent getApiKey call for this session/provider. */
301
+ getAuthCredentialType?: (provider: string) => "api_key" | "oauth" | undefined;
302
+ /**
303
+ * Returns steering messages to inject into the conversation mid-run.
304
+ *
305
+ * Called after each tool execution to check for user interruptions unless interruptMode is "wait".
306
+ * If messages are returned, remaining tool calls are skipped and
307
+ * these messages are added to the context before the next LLM call.
308
+ */
309
+ getSteeringMessages?: () => Promise<AgentMessage[]>;
310
+ /**
311
+ * Returns steering messages that were dequeued for this run but cannot be
312
+ * delivered by it, so the next run can pick them up.
313
+ *
314
+ * A user interrupt that lands while a tool is executing aborts the run's
315
+ * signal without ending the loop: the loop still unwinds the tool and reaches
316
+ * its steering drain. Continuing there would start a turn on an already
317
+ * aborted signal, which the provider call rejects before the first token, so
318
+ * the loop hands the messages back instead and ends the run.
319
+ */
320
+ requeueSteeringMessages?: (messages: AgentMessage[]) => void;
321
+ /**
322
+ * Returns follow-up messages to process after the agent would otherwise stop.
323
+ *
324
+ * Called when the agent has no more tool calls and no steering messages.
325
+ * If messages are returned, they're added to the context and the agent
326
+ * continues with another turn.
327
+ */
328
+ getFollowUpMessages?: () => Promise<AgentMessage[]>;
329
+ /**
330
+ * Invoked with the follow-up messages the loop dequeues for the next turn
331
+ * (right after {@link getFollowUpMessages}). The consumer may use this to
332
+ * attach per-turn state (e.g., a fresh owned-completion lineage) at actual
333
+ * resume admission rather than when the message was merely queued.
334
+ */
335
+ /**
336
+ * Invoked when follow-up messages are consumed. `startsOwnRun` is true only
337
+ * when the batch owns a new agent run; maintenance and in-run consumption
338
+ * report false so callers attach to the existing lifecycle.
339
+ */
340
+ onFollowUpConsumed?: (messages: AgentMessage[], promotion?: {
341
+ startsOwnRun: boolean;
342
+ }) => void;
343
+ /**
344
+ * Invoked with the steering messages the loop dequeues mid-run for the
345
+ * CURRENT turn (right after getSteeringMessages). `promotion.startsOwnRun`
346
+ * is false for in-run consumption and true when the batch starts a new run.
347
+ */
348
+ /** Invoked when steering is consumed; see `startsOwnRun` on the promotion disposition. */
349
+ onSteeringConsumed?: (messages: AgentMessage[], promotion?: {
350
+ startsOwnRun: boolean;
351
+ }) => void;
352
+ /**
353
+ * One-shot transient recovery instruction attached to the first assistant
354
+ * request of this loop invocation. Sent only to the provider (never committed
355
+ * to durable agent message history) so a caller-owned retry of a discarded
356
+ * attempt can name the defect it is retrying around.
357
+ */
358
+ transientRecoveryMessage?: UserMessage;
359
+ /**
360
+ * Supplies one bounded synthetic recovery instruction before the loop would
361
+ * otherwise yield. Unlike a follow-up, it is sent only to the provider and
362
+ * is not committed to durable agent message history.
363
+ */
364
+ getSyntheticRecoveryMessage?: () => Promise<UserMessage | undefined>;
365
+ /**
366
+ * Cooperative pause checkpoint evaluated at safe loop boundaries.
367
+ *
368
+ * Called after completed tool execution has been emitted and before the loop
369
+ * polls steering/follow-up queues or schedules another assistant response.
370
+ * Returning true ends the current loop with `agent_end.stopReason === "paused"`
371
+ * without aborting any in-flight model or tool work.
372
+ */
373
+ shouldPause?: () => boolean;
374
+ /**
375
+ * Hook fired right before the loop would exit.
376
+ *
377
+ * Called when the agent has no more tool calls and no steering messages,
378
+ * immediately before polling follow-up messages.
379
+ */
380
+ onBeforeYield?: () => Promise<void> | void;
381
+ /**
382
+ * Provides tool execution context, resolved per tool call.
383
+ * Use for late-bound UI or session state access.
384
+ */
385
+ getToolContext?: (toolCall?: ToolCallContext) => AgentToolContext | undefined;
386
+ /**
387
+ * Refreshes prompt/tool context from live session state before each model call.
388
+ * Use this when tool availability or the system prompt can change mid-turn.
389
+ */
390
+ syncContextBeforeModelCall?: (context: AgentContext) => void | Promise<void>;
391
+ /**
392
+ * Cooperative mid-run context-maintenance checkpoint.
393
+ *
394
+ * Invoked at the top of every loop iteration AFTER pending tool-result /
395
+ * steering messages have been materialized into durable context and BEFORE
396
+ * {@link syncContextBeforeModelCall} and the model call. This is the only
397
+ * boundary where the full unsent context (tool results + dequeued steering)
398
+ * is already durable, so a long uninterrupted tool loop can be bounded here
399
+ * before it grows past the provider window.
400
+ *
401
+ * The callback owns the maintenance decision (prune / compact / promote) and
402
+ * receives the minimal cancellation-aware lifecycle: `signal` is the
403
+ * non-optional loop signal, and `awaitEventDrain(invocationSignal)` waits for
404
+ * prior event consumer bodies with loop and invocation cancellation composed.
405
+ * Any outcome other than "not-needed" ends the current run with
406
+ * `agent_end.stopReason === "maintenance"` (NOT the lossy pause / completed
407
+ * finalization); the callback's continuation owner resumes the run on the
408
+ * rewritten context.
409
+ */
410
+ maintainContext?: (context: AgentContext, lifecycle: {
411
+ signal: AbortSignal;
412
+ awaitEventDrain: (invocationSignal: AbortSignal) => Promise<void>;
413
+ }) => Promise<ContextMaintenanceResult | MidRunMaintenanceOutcome> | ContextMaintenanceResult | MidRunMaintenanceOutcome;
414
+ /**
415
+ * Optional transform applied to tool call arguments before execution.
416
+ * Use for deobfuscating secrets or rewriting arguments.
417
+ */
418
+ transformToolCallArguments?: (args: Record<string, unknown>, toolName: string) => Record<string, unknown>;
419
+ /**
420
+ * Enable intent tracing for tool calls.
421
+ * When enabled, the harness injects a `string` field into tool schemas sent to the model,
422
+ * then strips from arguments before executing tools.
423
+ */
424
+ intentTracing?: boolean;
425
+ /**
426
+ * Append-only context mode — stabilizes system prompt + tool spec bytes
427
+ * across turns so provider prefix caches hit at maximum rate.
428
+ *
429
+ * When set, the loop reads messages from the append-only log (stable
430
+ * byte prefix) and caches system prompt + tools. Tools exclude per-turn
431
+ * `_i` intent fields.
432
+ */
433
+ appendOnlyContext?: AppendOnlyContextManager;
434
+ /**
435
+ * Inspect assistant streaming events before they are published to the outer agent event stream.
436
+ * Callers may abort synchronously to stop consuming buffered provider events.
437
+ */
438
+ onAssistantMessageEvent?: (message: AssistantMessage, event: AssistantMessageEvent) => void;
439
+ /** Observe unmanaged provisional assistant deltas before public publication. */
440
+ onProvisionalAssistantMessageEvent?: (message: AssistantMessage, event: AssistantMessageEvent) => void;
441
+ /** True when the host consumes provisional assistant events for live safety checks. */
442
+ hasProvisionalAssistantMessageEventConsumer?: boolean;
443
+ /** Called for non-content tool-choice incapability stream events. */
444
+ onToolChoiceIncapability?: (event: Extract<AssistantMessageEvent, {
445
+ type: "toolChoiceIncapability";
446
+ }>) => void;
447
+ /**
448
+ * Called when GPT-5 Harmony protocol leakage is detected and mitigated.
449
+ */
450
+ onHarmonyLeak?: (event: HarmonyAuditEvent) => void | Promise<void>;
451
+ /**
452
+ * Dynamic tool choice override, resolved per LLM call.
453
+ * When set and returns a value, overrides the static `toolChoice`.
454
+ */
455
+ getToolChoice?: () => ToolChoice | undefined;
456
+ /**
457
+ * Dynamic reasoning effort override, resolved per LLM call.
458
+ * When set and returns a value, overrides the static `reasoning` captured
459
+ * at run-loop start. Use this so mid-run thinking-level changes apply on
460
+ * the next model call instead of waiting for the next prompt.
461
+ */
462
+ getReasoning?: () => Effort | undefined;
463
+ /**
464
+ * Called after a tool call has been validated and is about to execute.
465
+ *
466
+ * Return `{ block: true }` to prevent execution. The loop emits an error tool
467
+ * result instead (using `reason` as the error text, or a default if omitted).
468
+ *
469
+ * Mutating `context.args` in place changes the arguments passed to `tool.execute`
470
+ * — the loop does **not** re-validate after this hook runs.
471
+ *
472
+ * The hook receives the tool abort signal (`signal`) and is responsible for
473
+ * honoring it. Throwing surfaces as a tool-error result and does not abort the
474
+ * rest of the batch.
475
+ */
476
+ beforeToolCall?: (context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined> | BeforeToolCallResult | undefined;
477
+ /**
478
+ * Called after a tool finishes executing, before `tool_execution_end` and the
479
+ * tool-result message are emitted.
480
+ *
481
+ * Return an `AfterToolCallResult` to override individual fields of the executed
482
+ * tool result. Omitted fields keep their original values; there is no deep merge.
483
+ *
484
+ * Throwing surfaces as a tool-error result and does not abort the rest of the batch.
485
+ */
486
+ afterToolCall?: (context: AfterToolCallContext, signal?: AbortSignal) => Promise<AfterToolCallResult | undefined> | AfterToolCallResult | undefined;
487
+ /**
488
+ * Opt-in OpenTelemetry instrumentation. Passing `{}` enables the loop's
489
+ * GenAI-semantic-convention spans (`invoke_agent`, `chat`, `execute_tool`)
490
+ * using the global tracer provider. Leaving this field undefined disables
491
+ * the instrumentation entirely — the loop performs zero tracer lookups.
492
+ *
493
+ * See {@link AgentTelemetryConfig} for the full surface (hooks, content
494
+ * capture, cost estimator, agent identity).
495
+ */
496
+ telemetry?: AgentTelemetryConfig;
497
+ /**
498
+ * Optional prompt-run resource ownership ledger. Provider and scheduler-level tool
499
+ * work is tracked until its owned lifecycle promise settles.
500
+ */
501
+ resourceLedger?: RunResourceLedger;
502
+ /** Stable resource ownership identifier for this prompt run. */
503
+ resourceRunId?: string;
504
+ /** Immutable logical cancellation domain bound by the resource ledger. */
505
+ resourceCancellationDomain?: RunCancellationDomain;
506
+ /** Agent passes caller ownership; direct loop callers retain loop-owned sealing. */
507
+ resourceSealOwner?: "caller" | "loop";
508
+ /** Opaque ownership required to resume a standalone maintenance lifecycle. */
509
+ standaloneRunOwnership?: StandaloneRunOwnership;
510
+ }
511
+ /**
512
+ * Batch/sequencing metadata for the tool call currently being processed.
513
+ */
514
+ export interface ToolCallContext {
515
+ batchId: string;
516
+ index: number;
517
+ total: number;
518
+ toolCalls: Array<{
519
+ id: string;
520
+ name: string;
521
+ }>;
522
+ }
523
+ /** A single tool-call content block emitted by an assistant message. */
524
+ export type AgentToolCall = Extract<AssistantMessage["content"][number], {
525
+ type: "toolCall";
526
+ }>;
527
+ /**
528
+ * Result returned from `beforeToolCall`.
529
+ *
530
+ * Set `block: true` to prevent the tool from executing. The loop emits an error tool
531
+ * result instead, using `reason` as the error text (or a default if omitted).
532
+ *
533
+ * Mutating the `args` reference passed in `BeforeToolCallContext` is supported and
534
+ * survives into execution — the loop does **not** re-validate after this hook runs.
535
+ */
536
+ export interface BeforeToolCallResult {
537
+ block?: boolean;
538
+ reason?: string;
539
+ }
540
+ /**
541
+ * Partial override returned from `afterToolCall`.
542
+ *
543
+ * Merge semantics are field-by-field; omitted fields keep the executed values.
544
+ * No deep merge is performed.
545
+ */
546
+ export interface AfterToolCallResult {
547
+ /** If provided, replaces the tool result content array in full. */
548
+ content?: (TextContent | ImageContent)[];
549
+ /** If provided, replaces the tool result details payload in full. */
550
+ details?: unknown;
551
+ /** If provided, replaces the error flag carried with the tool result. */
552
+ isError?: boolean;
553
+ }
554
+ /** Context passed to `beforeToolCall`. */
555
+ export interface BeforeToolCallContext {
556
+ /** The assistant message that requested the tool call. */
557
+ assistantMessage: AssistantMessage;
558
+ /** The raw tool call block from `assistantMessage.content`. */
559
+ toolCall: AgentToolCall;
560
+ /**
561
+ * Validated tool arguments. The same reference is forwarded to `tool.execute`
562
+ * (after any `transformToolCallArguments` pass), so in-place mutations stick.
563
+ */
564
+ args: Record<string, unknown>;
565
+ /** Current agent context at the time the tool call is prepared. */
566
+ context: AgentContext;
567
+ }
568
+ /** Context passed to `afterToolCall`. */
569
+ export interface AfterToolCallContext {
570
+ /** The assistant message that requested the tool call. */
571
+ assistantMessage: AssistantMessage;
572
+ /** The raw tool call block from `assistantMessage.content`. */
573
+ toolCall: AgentToolCall;
574
+ /** Validated tool arguments used for execution (post `beforeToolCall` mutations). */
575
+ args: Record<string, unknown>;
576
+ /** The executed tool result before any `afterToolCall` overrides are applied. */
577
+ result: AgentToolResult<any>;
578
+ /** Whether the executed tool result is currently treated as an error. */
579
+ isError: boolean;
580
+ /** Current agent context at the time the tool call is finalized. */
581
+ context: AgentContext;
582
+ }
583
+ /**
584
+ * Extensible interface for custom app messages.
585
+ * Apps can extend via declaration merging:
586
+ *
587
+ * @example
588
+ * ```typescript
589
+ * declare module "@vib-rato/agent" {
590
+ * interface CustomAgentMessages {
591
+ * artifact: ArtifactMessage;
592
+ * notification: NotificationMessage;
593
+ * }
594
+ * }
595
+ * ```
596
+ */
597
+ export interface CustomAgentMessages {
598
+ }
599
+ /**
600
+ * AgentMessage: Union of LLM messages + custom messages.
601
+ * This abstraction allows apps to add custom message types while maintaining
602
+ * type safety and compatibility with the base LLM messages.
603
+ */
604
+ export type AgentMessage = Message | CustomAgentMessages[keyof CustomAgentMessages];
605
+ /**
606
+ * Agent state containing all configuration and conversation data.
607
+ */
608
+ export interface AgentState {
609
+ systemPrompt: string[];
610
+ model: Model | undefined;
611
+ thinkingLevel?: Effort;
612
+ tools: AgentTool<any>[];
613
+ messages: AgentMessage[];
614
+ isStreaming: boolean;
615
+ streamMessage: AgentMessage | null;
616
+ pendingToolCalls: Set<string>;
617
+ error?: string;
618
+ }
619
+ export interface AgentToolResult<T = any, _TInput = unknown> {
620
+ content: (TextContent | ImageContent)[];
621
+ details?: T;
622
+ isError?: boolean;
623
+ }
624
+ export type AgentToolUpdateCallback<T = any, TInput = unknown> = (partialResult: AgentToolResult<T, TInput>) => void;
625
+ /** Options passed to renderResult */
626
+ export interface RenderResultOptions {
627
+ /** Whether the result view is expanded */
628
+ expanded: boolean;
629
+ /** Whether this is a partial/streaming result */
630
+ isPartial: boolean;
631
+ /** Current spinner frame index for animated elements (optional) */
632
+ spinnerFrame?: number;
633
+ }
634
+ /**
635
+ * Context passed to tool execution.
636
+ * Apps can extend via declaration merging.
637
+ */
638
+ export interface AgentToolContext {
639
+ /** Per-attempt scope used to attribute tool lifecycle and extension delivery. */
640
+ attemptScope?: AttemptScope;
641
+ }
642
+ export type AgentToolExecFn<TParameters extends TSchema = TSchema, TDetails = any, TTheme = unknown> = (this: AgentTool<TParameters, TDetails, TTheme>, toolCallId: string, params: Static<TParameters>, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback<TDetails, TParameters>, context?: AgentToolContext) => Promise<AgentToolResult<TDetails, TParameters>>;
643
+ export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any, TTheme = unknown> extends Tool<TParameters> {
644
+ label: string;
645
+ /** If true, tool is excluded unless explicitly listed in --tools or agent's tools field */
646
+ hidden?: boolean;
647
+ /** If true, tool can stage a pending action that requires explicit resolution via the resolve tool. */
648
+ deferrable?: boolean;
649
+ /** Built-in tool loading behavior. "essential" loads initially; "discoverable" can be activated by tool search. */
650
+ loadMode?: "essential" | "discoverable";
651
+ /** Short one-line summary used for tool discovery indexes. */
652
+ summary?: string;
653
+ /** If true, tool execution ignores abort signals (runs to completion) */
654
+ nonAbortable?: boolean;
655
+ /**
656
+ * Concurrency mode for tool scheduling when multiple calls are in one turn.
657
+ * - "shared": can run alongside other shared tools (default)
658
+ * - "exclusive": runs alone; other tools wait until it finishes
659
+ */
660
+ concurrency?: "shared" | "exclusive";
661
+ /** If true, argument validation errors are non-fatal: raw args are passed to execute() instead of returning an error to the LLM. */
662
+ lenientArgValidation?: boolean;
663
+ /**
664
+ * Controls how the INTENT_FIELD (`_i`) is handled for this tool.
665
+ * - `"require"` (default): `_i` is injected and required in the parameter schema.
666
+ * - `"optional"`: `_i` is injected as an optional/nullable field.
667
+ * - `"omit"`: `_i` is NOT injected. Use for tools where intent is obvious (yield, resolve, todo_write, …).
668
+ * - function: `_i` is NOT injected; intent is derived dynamically from (potentially partial / streaming) args.
669
+ */
670
+ intent?: "omit" | "optional" | "require" | ((args: Partial<Static<TParameters>>) => string | undefined);
671
+ /**
672
+ * Argument fields (dotted paths into the arguments object) that render to
673
+ * the user as pure display text — question wording and option labels, never
674
+ * ids, metadata, or persisted records. When every `\uXXXX`-escaped scalar in
675
+ * a tool call corroborates a decoded non-ASCII character inside these
676
+ * fields, the agent loop degrades to a single warning and executes the
677
+ * decoded call instead of discarding the turn: a mistyped hex digit there
678
+ * can only change what the user reads, never what executes or persists.
679
+ * Every other field of these arguments — and every field of every other
680
+ * tool — keeps the fail-closed rejection for unverified `\uXXXX` payloads.
681
+ */
682
+ displaySafeEscapedArgFields?: readonly string[];
683
+ /** The main execution callback for this tool. */
684
+ execute: AgentToolExecFn<TParameters, TDetails, TTheme>;
685
+ /** Optional custom rendering for tool call display (returns UI component) */
686
+ renderCall?: (args: Static<TParameters>, options: RenderResultOptions, theme: TTheme) => unknown;
687
+ /** Optional custom rendering for tool result display (returns UI component) */
688
+ renderResult?: (result: AgentToolResult<TDetails, TParameters>, options: RenderResultOptions, theme: TTheme) => unknown;
689
+ }
690
+ export interface AgentContext {
691
+ systemPrompt: string[];
692
+ messages: AgentMessage[];
693
+ tools?: AgentTool<any>[];
694
+ }
695
+ /**
696
+ * Sanitized failure diagnostic carried by `agent_failed`: a stable classifier code
697
+ * plus a fixed human-readable message. Never the raw provider error — consumers may
698
+ * not depend on provider-specific detail, request bodies, or raw error objects.
699
+ */
700
+ export interface AgentFailureDiagnostic {
701
+ code: string;
702
+ message: string;
703
+ }
704
+ /**
705
+ * Events emitted by the Agent for UI updates.
706
+ * These events provide fine-grained lifecycle information for messages, turns, and tool executions.
707
+ */
708
+ export type AgentEvent = {
709
+ type: "agent_start";
710
+ scope?: AttemptScope;
711
+ } | {
712
+ type: "agent_failed";
713
+ error: AgentFailureDiagnostic;
714
+ scope?: AttemptScope;
715
+ } | {
716
+ type: "agent_end";
717
+ messages: AgentMessage[];
718
+ /** Indicates whether the loop ended normally, suspended, cancelled, or entered maintenance. */
719
+ stopReason?: "completed" | "paused" | "cancelled" | "maintenance";
720
+ /** Present iff `stopReason === "maintenance"`; the maintenance outcome. */
721
+ maintenanceOutcome?: MidRunMaintenanceOutcome;
722
+ /** Present iff `AgentTelemetryConfig` was supplied on this run. */
723
+ telemetry?: AgentRunSummary;
724
+ coverage?: AgentRunCoverage;
725
+ scope?: AttemptScope;
726
+ } | {
727
+ type: "turn_start";
728
+ scope?: AttemptScope;
729
+ } | {
730
+ type: "turn_end";
731
+ message: AgentMessage;
732
+ toolResults: ToolResultMessage[];
733
+ scope?: AttemptScope;
734
+ } | {
735
+ type: "message_start";
736
+ message: AgentMessage;
737
+ scope?: AttemptScope;
738
+ } | {
739
+ type: "message_update";
740
+ message: AgentMessage;
741
+ assistantMessageEvent: AssistantMessageEvent;
742
+ scope?: AttemptScope;
743
+ } | {
744
+ type: "message_end";
745
+ message: AgentMessage;
746
+ scope?: AttemptScope;
747
+ } | {
748
+ type: "tool_execution_start";
749
+ toolCallId: string;
750
+ toolName: string;
751
+ args: any;
752
+ intent?: string;
753
+ scope?: AttemptScope;
754
+ } | {
755
+ type: "tool_execution_update";
756
+ toolCallId: string;
757
+ toolName: string;
758
+ args: any;
759
+ partialResult: any;
760
+ scope?: AttemptScope;
761
+ } | {
762
+ type: "tool_execution_end";
763
+ toolCallId: string;
764
+ toolName: string;
765
+ result: any;
766
+ isError?: boolean;
767
+ scope?: AttemptScope;
768
+ };
769
+ /**
770
+ * Why a tool call failed when the loop — not the tool — produced the result.
771
+ *
772
+ * `argument_validation` means the call never dispatched: the arguments were
773
+ * rejected before `execute` ran. `execution` means `execute` threw.
774
+ */
775
+ export type ToolFailureKind = "argument_validation" | "execution";
776
+ /**
777
+ * The result details the loop attaches when a tool call fails without the tool
778
+ * returning its own details. It carries no tool-owned field, so a consumer that
779
+ * dereferences a tool's own detail shape must recognise it first.
780
+ */
781
+ export interface ToolFailureEnvelope {
782
+ failureKind: ToolFailureKind;
783
+ }
784
+ export declare function toolFailureEnvelope(kind: ToolFailureKind): ToolFailureEnvelope;
785
+ /**
786
+ * True only for the loop's own envelope. Tools that report a `failureKind`
787
+ * alongside their own details (`todo_write`, todo persistence) keep those fields,
788
+ * so their renderers still own the result and are left alone.
789
+ */
790
+ export declare function isToolFailureEnvelope(value: unknown): value is ToolFailureEnvelope;