@axlsdk/axl 0.23.2 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -2,6 +2,58 @@ import { z, ZodError } from 'zod';
2
2
  import { EventEmitter } from 'node:events';
3
3
  import { Readable } from 'node:stream';
4
4
 
5
+ /** Audio supplied to a transcription provider. Network and file-system sources
6
+ * are deliberately excluded: callers retain control of all I/O. */
7
+ type RecordedAudioSource = {
8
+ readonly type: 'bytes';
9
+ readonly data: Uint8Array;
10
+ readonly mediaType: string;
11
+ } | {
12
+ readonly type: 'base64';
13
+ readonly data: string;
14
+ readonly mediaType: string;
15
+ } | {
16
+ readonly type: 'provider-file';
17
+ readonly provider: string;
18
+ readonly reference: string;
19
+ readonly mediaType?: string;
20
+ };
21
+ type TranscriptionRequest = {
22
+ readonly model: string;
23
+ readonly audio: RecordedAudioSource;
24
+ readonly language?: string;
25
+ readonly timestamps?: 'segment' | 'word';
26
+ readonly diarization?: boolean;
27
+ readonly providerOptions?: Record<string, unknown>;
28
+ };
29
+ type Transcript = {
30
+ readonly text: string;
31
+ readonly detectedLanguages?: readonly string[];
32
+ readonly segments?: readonly {
33
+ readonly text: string;
34
+ readonly start: number;
35
+ readonly end: number;
36
+ readonly speaker?: string;
37
+ }[];
38
+ readonly words?: readonly {
39
+ readonly text: string;
40
+ readonly start: number;
41
+ readonly end: number;
42
+ readonly speaker?: string;
43
+ }[];
44
+ readonly usage?: {
45
+ readonly audioSeconds?: number;
46
+ readonly inputTokens?: number;
47
+ readonly outputTokens?: number;
48
+ readonly totalTokens?: number;
49
+ readonly cost?: number;
50
+ };
51
+ readonly pricingStatus?: 'priced' | 'unpriced' | 'zero';
52
+ readonly providerMetadata?: Readonly<Record<string, unknown>>;
53
+ };
54
+ /** Maximum decoded size of caller-supplied inline audio. */
55
+ declare const MAX_INLINE_TRANSCRIPTION_BYTES: number;
56
+
5
57
  /** Ordered, model-facing input. Strings retain the legacy shorthand. */
6
58
  type ModelInput = string | readonly InputContentPart[];
7
59
  type InputTextPart = {
@@ -31,8 +83,15 @@ type InputImagePart = {
31
83
  readonly source: InputMediaSource;
32
84
  readonly label?: string;
33
85
  };
34
- type InputContentPart = InputTextPart | InputImagePart;
35
- /** Maximum decoded bytes retained across inline images in one logical input. */
86
+ /** A finite recording supplied as ordered model input. Sources are shared with
87
+ * `ctx.transcribe()`, which makes audio URLs unrepresentable by construction. */
88
+ type InputAudioPart = {
89
+ readonly type: 'audio';
90
+ readonly source: RecordedAudioSource;
91
+ readonly label?: string;
92
+ };
93
+ type InputContentPart = InputTextPart | InputImagePart | InputAudioPart;
94
+ /** Maximum decoded bytes retained across all inline media in one logical input. */
36
95
  declare const MAX_INLINE_MODEL_INPUT_BYTES: number;
37
96
  /** Bounded, observation-safe representation of a rich input. */
38
97
  type ModelInputDescriptor = {
@@ -46,15 +105,261 @@ type ModelInputDescriptor = {
46
105
  readonly bytes?: number;
47
106
  readonly locator?: string;
48
107
  readonly label?: string;
108
+ } | {
109
+ readonly type: 'audio';
110
+ readonly source: RecordedAudioSource['type'];
111
+ readonly mediaType?: string;
112
+ readonly bytes?: number;
113
+ /** Provider-file reference only; audio has no URL source. */
114
+ readonly locator?: string;
115
+ readonly label?: string;
49
116
  })[];
50
117
  };
51
118
  /** Return legacy text unchanged, or the deterministic ordered text projection. */
52
119
  declare function inputText(input: ModelInput): string;
120
+ /** Safe representation for context summarizers; never includes locators or data. */
121
+ declare function summarizeModelInput(input: ModelInput): string;
122
+
123
+ /**
124
+ * Core accounting — one authoritative record of what a workload spent.
125
+ *
126
+ * This module owns the *measurement* rail. It is deliberately independent of
127
+ * the trace/event rail (`event-utils.ts`): trace retention, trace level,
128
+ * redaction and capture change diagnostics, never the numbers here. Both rails
129
+ * are fed by the same settlement producer (the scoped provider facade and the
130
+ * instrumented tool / memory / transcription boundaries), so turning tracing
131
+ * off cannot change `Accounting`.
132
+ *
133
+ * The three concepts:
134
+ *
135
+ * - **Operation** — one logical paid unit of work (a chat call, a stream, an
136
+ * embedding, a transcription, a tool invocation attempt, a declared external
137
+ * call). It has a stable id and a lifecycle: `admitted` → `dispatched` →
138
+ * `settled` | `denied` | `abandoned`. Transport retries are subordinate to
139
+ * the operation; they never mint a second one.
140
+ * - **Scope** — an `AsyncLocalStorage` frame opened by
141
+ * `AxlRuntime.trackOutcome()`. Scopes nest; an operation settled in a child
142
+ * is recorded exactly once in every open ancestor (deduped by operation id),
143
+ * so a parent total is the sum of disjoint operations and never a double
144
+ * charge.
145
+ * - **Admission** — a synchronous known-spend threshold ({@link
146
+ * AdmissionController}) attached to a scope. It is a threshold, not a
147
+ * reservation system: work already dispatched settles and is counted.
148
+ *
149
+ * Zero is never confused with unknown. A known $0 settles `complete`; anything
150
+ * dispatched whose charge we could not establish settles `unknown` with a
151
+ * {@link AccountingReason}, which makes the scope `incomplete` and makes
152
+ * `knownCost` an explicit lower bound.
153
+ */
154
+ /**
155
+ * How much of the declared/instrumented scope the numbers actually cover.
156
+ *
157
+ * This is coverage of Axl-observable operations, NOT invoice reconciliation.
158
+ *
159
+ * - `'complete'` — every operation in the scope reached a terminal state with a
160
+ * usable charge (including known $0).
161
+ * - `'incomplete'` — at least one operation settled without a usable charge;
162
+ * `knownCost` is a lower bound and `reasons` says why.
163
+ * - `'unverified'` — reserved for READERS of artifacts that carry no
164
+ * accounting at all (legacy eval results). A live scope never produces it.
165
+ */
166
+ type AccountingCompleteness = 'complete' | 'incomplete' | 'unverified';
167
+ /** Where a settled USD figure came from. Keys of `Accounting.provenance`. */
168
+ type CostProvenance =
169
+ /** The vendor supplied the USD figure itself (e.g. OpenRouter `usage.cost`). */
170
+ 'provider_reported'
171
+ /** An Axl/adapter price table was applied to reported usage. */
172
+ | 'price_table_estimate'
173
+ /** An adapter supplied a cost without declaring its basis. */
174
+ | 'adapter_reported'
175
+ /** `withExternalOperation` / tool / legacy caller value. */
176
+ | 'caller_reported';
177
+ /** Why an operation could not contribute a usable charge. */
178
+ type AccountingReason =
179
+ /** Usage was reported but no usable cost was (undefined / NaN / negative / ∞). */
180
+ 'unpriced_model'
181
+ /** Dispatched, but the terminal outcome carried no usage (failure/abort/stall). */
182
+ | 'usage_missing'
183
+ /** Dispatched and never settled before its scope finalized. */
184
+ | 'abandoned'
185
+ /** An external operation finished without calling `report.setCost()`. */
186
+ | 'external_unreported'
187
+ /**
188
+ * A consumer ran work with no accounting scope available (e.g. `@axlsdk/eval`
189
+ * driving a runtime that predates `trackOutcome`). Core defines the name only
190
+ * — no core producer emits it.
191
+ */
192
+ | 'uninstrumented';
193
+ /** The instrumented boundaries that can open an operation. */
194
+ type OperationKind = 'chat' | 'stream' | 'embedding' | 'transcription' | 'tool' | 'external';
195
+ /** Whether a scope's operations are generating an answer or judging one. */
196
+ type OperationPurpose = 'generation' | 'judging';
197
+ /**
198
+ * Usage folded across a scope's settled operations.
199
+ *
200
+ * `reasoningTokens` / `cachedTokens` / `cacheWriteTokens` are counted ONCE in
201
+ * their own bucket. `inputTokens` is the provider's already-folded
202
+ * `prompt_tokens` — cached tokens are not re-added to it.
203
+ */
204
+ type AccountingUsage = {
205
+ inputTokens: number;
206
+ outputTokens: number;
207
+ reasoningTokens: number;
208
+ cachedTokens: number;
209
+ cacheWriteTokens: number;
210
+ audioSeconds: number;
211
+ };
212
+ /** The authoritative accounting record for one tracked scope. */
213
+ type Accounting = {
214
+ version: 1;
215
+ currency: 'USD';
216
+ /** Sum of settled, usable, disjoint charges. Never includes caller aggregates. */
217
+ knownCost: number;
218
+ completeness: AccountingCompleteness;
219
+ /** Reason → count of operations carrying it. Empty object when complete. */
220
+ reasons: Partial<Record<AccountingReason, number>>;
221
+ usage: AccountingUsage;
222
+ operations: {
223
+ /**
224
+ * Operations admitted and opened in this scope (or a descendant). An
225
+ * operation later refused at the dispatch check is retracted from this
226
+ * count and appears under `denied` instead, so
227
+ * `total === settled + unknown` always holds.
228
+ */
229
+ total: number;
230
+ /** Terminal with a usable cost, including a known $0. */
231
+ settled: number;
232
+ /** Terminal or abandoned without a usable cost. Drives `completeness`. */
233
+ unknown: number;
234
+ /** Refused admission — never dispatched, contributes nothing anywhere. */
235
+ denied: number;
236
+ /** `total` split by kind. Denied operations are not included. */
237
+ byKind: Partial<Record<OperationKind, number>>;
238
+ };
239
+ /** `knownCost` split. `external` is kind `'external'`; the rest by scope purpose. */
240
+ breakdown: {
241
+ generation: number;
242
+ judging: number;
243
+ external: number;
244
+ };
245
+ /** `knownCost` split by provenance. Keys sum to `knownCost`. */
246
+ provenance: Partial<Record<CostProvenance, number>>;
247
+ };
248
+ /**
249
+ * The dispatch-level admission hook.
250
+ *
251
+ * `fetchWithRetry` calls `beforeDispatch` after acquiring a rate-governor
252
+ * permit and immediately before EVERY `fetch` attempt, outside the
253
+ * network-error catch. Throwing an {@link AdmissionDeniedError} from it stops
254
+ * the request before it leaves the process; the permit is still released.
255
+ *
256
+ * A third-party adapter that ignores this hook still gets the
257
+ * operation-open check, but a request already queued behind a governor or
258
+ * sleeping in its own backoff cannot be stopped. See `docs/providers.md`.
259
+ */
260
+ type DispatchAdmission = {
261
+ /** @param attempt 1-indexed transport attempt. Throws to refuse the dispatch. */
262
+ beforeDispatch(attempt: number): void;
263
+ };
264
+ /** Reports a declared external charge for one `withExternalOperation` call. */
265
+ type ExternalOperationReport = {
266
+ /**
267
+ * Record a finite, non-negative, DISJOINT USD charge for this operation.
268
+ * Explicit `0` means known-free. Nested Axl calls account for themselves and
269
+ * must be excluded.
270
+ *
271
+ * @throws AxlError `INVALID_COST_REPORT` on a non-finite/negative amount or a
272
+ * second call.
273
+ */
274
+ setCost(amountUsd: number, usage?: Partial<AccountingUsage>): void;
275
+ };
276
+ /** Descriptor for `withExternalOperation` / {@link externalOperation}. */
277
+ type ExternalOperationDescriptor = {
278
+ name: string;
279
+ };
280
+ /**
281
+ * @internal The settlement channel into a controller.
282
+ *
283
+ * `Symbol.for`, NOT `Symbol()`: this package legitimately loads twice in one
284
+ * process — an ESM consumer whose `runtime.eval()` dynamically imports
285
+ * `@axlsdk/eval`, which resolves its own copy of `@axlsdk/axl`, is the ordinary
286
+ * case, not a pathology. A module-private symbol would make the controller
287
+ * built by copy A invisible to the settlement walk in copy B, and spend would
288
+ * silently stop reaching the budget. The registry symbol is shared across
289
+ * realms, so both copies agree.
290
+ */
291
+ declare const RECORD_SPEND: unique symbol;
292
+ /**
293
+ * A synchronous known-spend threshold for one invocation.
294
+ *
295
+ * Attach it to a scope via `runtime.trackOutcome(fn, { admission })`. Every
296
+ * instrumented operation checks it when it opens, and every built-in transport
297
+ * checks it again immediately before each dispatch.
298
+ *
299
+ * It is a **threshold, not a reservation**: a call admitted before a sibling
300
+ * settles may push known spend past `limit`. `knownOvershoot` reports how far.
301
+ * Never share one controller across unrelated invocations.
302
+ */
303
+ declare class AdmissionController {
304
+ /** The configured USD threshold. Admission closes at `knownSpend >= limit`. */
305
+ readonly limit: number;
306
+ private spend;
307
+ constructor(options: {
308
+ limit: number;
309
+ });
310
+ /** USD settled so far under this controller. Unknown spend is NOT included. */
311
+ get knownSpend(): number;
312
+ /** `true` once `knownSpend >= limit`. A `limit` of 0 is closed from the start. */
313
+ get closed(): boolean;
314
+ get status(): 'open' | 'closed';
315
+ /** `max(0, knownSpend - limit)`, unconditionally — necessarily 0 while open. */
316
+ get knownOvershoot(): number;
317
+ /** Synchronous admission decision. No await between the check and the answer. */
318
+ admit(): {
319
+ admitted: true;
320
+ } | {
321
+ admitted: false;
322
+ limit: number;
323
+ knownSpend: number;
324
+ };
325
+ snapshot(): {
326
+ limit: number;
327
+ status: 'open' | 'closed';
328
+ knownSpend: number;
329
+ knownOvershoot: number;
330
+ };
331
+ /** @internal Applied synchronously at settlement, in the tick it is observed. */
332
+ [RECORD_SPEND](amountUsd: number): void;
333
+ }
334
+ /**
335
+ * Declare a paid unit of work Axl cannot observe — a third-party API call
336
+ * inside a tool, a custom scorer's own model call — so it joins the scope's
337
+ * accounting as an `'external'` operation.
338
+ *
339
+ * Admission is checked BEFORE `fn` runs. The operation finalizes on return,
340
+ * throw or abort; a cost reported before a later throw is kept. Not calling
341
+ * `report.setCost()` makes the scope `incomplete` with reason
342
+ * `'external_unreported'` — never free.
343
+ *
344
+ * The reported amount must be DISJOINT: nested `ctx.ask` / provider calls
345
+ * account for themselves and must be excluded, or the scope double-charges.
346
+ *
347
+ * Outside any accounting scope `fn` still runs, with a report that validates
348
+ * its argument but records nothing.
349
+ *
350
+ * @throws AdmissionDeniedError when the enclosing budget has closed.
351
+ */
352
+ declare function externalOperation<T>(descriptor: ExternalOperationDescriptor, fn: (report: ExternalOperationReport) => Promise<T>): Promise<T>;
53
353
 
54
354
  type InputModalitySupport = {
55
355
  image?: {
56
356
  sources: readonly InputMediaSource['type'][];
57
357
  };
358
+ /** Declaring `audio` is the sole way a provider opts in to general recorded
359
+ * audio input; the runtime fails closed before validation without it. */
360
+ audio?: {
361
+ sources: readonly RecordedAudioSource['type'][];
362
+ };
58
363
  };
59
364
  type ProviderInputValidationRequest = {
60
365
  model: string;
@@ -124,6 +429,14 @@ type ChatOptions = {
124
429
  responseFormat?: ResponseFormat;
125
430
  stop?: string[];
126
431
  signal?: AbortSignal;
432
+ /**
433
+ * @internal Runtime admission hook. Set by the scoped provider facade when an
434
+ * accounting scope with a budget is active. Built-in adapters forward it to
435
+ * `fetchWithRetry`, which calls it immediately before EVERY fetch attempt so a
436
+ * request queued behind the rate governor or sleeping in retry backoff is not
437
+ * dispatched after the budget closes. It is never sent to the vendor.
438
+ */
439
+ dispatchAdmission?: DispatchAdmission;
127
440
  /** @internal Runtime observer for the actual adapter transport lifecycle. */
128
441
  requestLifecycle?: {
129
442
  /** The request was actually dispatched, after limiter queue and retry backoff. */
@@ -195,9 +508,20 @@ type StreamChunk = {
195
508
  cached_tokens?: number;
196
509
  /** Tokens written to a provider prompt cache during this call, when reported. */
197
510
  cache_write_tokens?: number;
511
+ /**
512
+ * Audio tokens counted inside `prompt_tokens`, when the provider reports
513
+ * the per-modality split. Absent — never `0` — when unreported. Kept in
514
+ * field parity with `ProviderResponse.usage` so a streaming consumer
515
+ * never loses the split.
516
+ */
517
+ audio_input_tokens?: number;
518
+ /** Audio tokens counted inside `completion_tokens`, when reported. */
519
+ audio_output_tokens?: number;
198
520
  };
199
521
  /** Estimated cost in USD for this call, computed the same way as ProviderResponse.cost. */
200
522
  cost?: number;
523
+ /** How `cost` was derived. Field parity with `ProviderResponse.costProvenance`. */
524
+ costProvenance?: 'provider_reported' | 'price_table_estimate';
201
525
  /** Provider-specific opaque metadata (e.g. raw Gemini parts with thought signatures). */
202
526
  providerMetadata?: Record<string, unknown>;
203
527
  /**
@@ -1625,17 +1949,31 @@ type ToolCallMessage = {
1625
1949
  */
1626
1950
  type CallTiming = {
1627
1951
  /**
1628
- * Time parked in Axl's own opt-in `RateLimiter` (concurrency cap plus
1629
- * `minIntervalMs` spacing) before the request was allowed out. `0` when the
1630
- * provider has no `rateLimit` configured. This is self-imposed wait, not
1631
- * provider latency.
1952
+ * Every wait Axl imposed on itself: the first permit (concurrency cap),
1953
+ * `minIntervalMs` spacing, adaptive pacing after a rate-limit 429, a
1954
+ * rate-limit pause on the scope, and the re-acquire after a rate-limit 429.
1955
+ * `0` when nothing waited. This is
1956
+ * self-imposed wait, not provider latency.
1632
1957
  */
1633
1958
  queuedMs: number;
1634
- /** `fetch` attempts made for this call, including the final one (≥ 1). */
1959
+ /**
1960
+ * Requests actually sent for this call, including the final one (≥ 1). A call
1961
+ * held back by a rate-limit pause before sending is not an attempt.
1962
+ */
1635
1963
  attempts: number;
1636
1964
  /**
1637
- * First attempt's dispatch → final attempt's dispatch, i.e. everything spent
1638
- * on failed attempts and their backoff sleeps. `0` for a single attempt.
1965
+ * Rate-limit 429s this call received and retried: how often the provider
1966
+ * throttled it. A 429 that is returned instead (a spend cap, or the last one
1967
+ * once the retry budget is spent) is not counted, nor are 503/529/network
1968
+ * retries. Built-in adapters always set it (`0` when none); optional so a
1969
+ * custom `Provider` may omit it.
1970
+ */
1971
+ rateLimitRetries?: number;
1972
+ /**
1973
+ * First attempt's dispatch → final attempt's dispatch, minus the
1974
+ * self-imposed waits inside that span (which are in `queuedMs`): failed
1975
+ * attempts and their 503/529/network backoff sleeps. Disjoint from
1976
+ * `queuedMs`. `0` for a single attempt.
1639
1977
  */
1640
1978
  retryMs: number;
1641
1979
  /** Final dispatch → response headers. Time to first byte. */
@@ -1670,8 +2008,24 @@ type ProviderResponse = {
1670
2008
  cached_tokens?: number;
1671
2009
  /** Tokens written to a provider prompt cache during this call, when reported. */
1672
2010
  cache_write_tokens?: number;
2011
+ /**
2012
+ * Audio tokens counted inside `prompt_tokens`, when the provider reports the
2013
+ * per-modality split. Absent — never `0` — when the provider reports no
2014
+ * split, so a consumer can tell "no audio" from "not reported".
2015
+ */
2016
+ audio_input_tokens?: number;
2017
+ /** Audio tokens counted inside `completion_tokens`, when reported. */
2018
+ audio_output_tokens?: number;
1673
2019
  };
1674
2020
  cost?: number;
2021
+ /**
2022
+ * How `cost` was derived, when the adapter can say. `'provider_reported'`
2023
+ * means the vendor supplied the USD figure itself; `'price_table_estimate'`
2024
+ * means the adapter priced reported usage from a table. Built-in adapters set
2025
+ * it whenever they return a `cost`; a cost with no provenance is recorded as
2026
+ * `'adapter_reported'`.
2027
+ */
2028
+ costProvenance?: 'provider_reported' | 'price_table_estimate';
1675
2029
  /** Provider-specific opaque metadata that needs to round-trip through conversation history. */
1676
2030
  providerMetadata?: Record<string, unknown>;
1677
2031
  /** Per-call latency breakdown. Absent on providers that don't instrument it. */
@@ -1831,6 +2185,54 @@ declare class BudgetExceededError extends AxlError {
1831
2185
  readonly policy: string;
1832
2186
  constructor(limit: number, spent: number, policy: string);
1833
2187
  }
2188
+ /**
2189
+ * Thrown when an `AdmissionController` refuses to admit a paid operation
2190
+ * because known spend has reached its limit.
2191
+ *
2192
+ * This is a *stop*, not a failure of the provider: it is never wrapped in a
2193
+ * `ProviderError`, never auto-retried by the transport, and never treated as
2194
+ * an abort. It is raised either when the operation opens or at the transport's
2195
+ * pre-dispatch check — in both cases before the request leaves the process, so
2196
+ * it never accompanies a charge.
2197
+ *
2198
+ * Distinct from `BudgetExceededError`, which is `ctx.budget()`'s own
2199
+ * workflow-scoped policy and keeps its existing semantics.
2200
+ */
2201
+ declare class AdmissionDeniedError extends AxlError {
2202
+ /** The controller's configured USD threshold. */
2203
+ readonly limit: number;
2204
+ /** Known settled spend at the moment of refusal. Unknown spend is excluded. */
2205
+ readonly knownSpend: number;
2206
+ /** What was refused. */
2207
+ readonly operation: {
2208
+ kind: string;
2209
+ model?: string;
2210
+ };
2211
+ constructor(options: {
2212
+ limit: number;
2213
+ knownSpend: number;
2214
+ operation: {
2215
+ kind: string;
2216
+ model?: string;
2217
+ };
2218
+ });
2219
+ }
2220
+ /**
2221
+ * Is this an admission denial, regardless of which copy of `@axlsdk/axl` threw it?
2222
+ *
2223
+ * `instanceof AdmissionDeniedError` is the natural check and it is the WRONG one
2224
+ * across a module boundary: `runtime.eval()` dynamically imports `@axlsdk/eval`,
2225
+ * which resolves its own `@axlsdk/axl`, and a dual ESM/CJS consumer can hold two
2226
+ * copies at once. The classes are then structurally identical and referentially
2227
+ * distinct, so `instanceof` returns false and a budget stop gets misreported as
2228
+ * an ordinary workflow failure — the item reads `failed` instead of
2229
+ * `budget_interrupted`, which is a lie about why the run stopped.
2230
+ *
2231
+ * Duck-typing on the stable public identity (`code` + `name`) is realm-proof.
2232
+ * Prefer this over `instanceof` in any consumer that can be loaded separately
2233
+ * from the runtime that threw.
2234
+ */
2235
+ declare function isAdmissionDeniedError(error: unknown): error is AdmissionDeniedError;
1834
2236
  /** Thrown when an agent exceeds its maximum number of tool-calling turns */
1835
2237
  declare class MaxTurnsError extends AxlError {
1836
2238
  readonly maxTurns: number;
@@ -2302,7 +2704,253 @@ declare class AxlEventBus implements AsyncIterable<AxlEvent> {
2302
2704
  }
2303
2705
 
2304
2706
  /**
2305
- * Opt-in client-side rate governor for provider HTTP calls.
2707
+ * Diagnostic artifact storage — the durable side of opt-in request capture.
2708
+ *
2709
+ * Captured request records are far too large to live inside an eval result, so
2710
+ * they live beside it as an **artifact**: an opaque id owned by an eval history
2711
+ * entry, holding a JSONL stream of records plus a small manifest describing
2712
+ * what the capture actually managed to record.
2713
+ *
2714
+ * The lifecycle is deliberately two-phase, because the artifact and the history
2715
+ * row are written to different stores and either write can fail:
2716
+ *
2717
+ * ```
2718
+ * stage(owner) → append(...)* → finalize(status) → [save history] → commit(expiresAt)
2719
+ * ↘ (save threw) → rollback()
2720
+ * ```
2721
+ *
2722
+ * A `staged` artifact is never served and is reclaimed once its lease expires,
2723
+ * so a process that dies mid-run leaves recoverable bytes rather than a
2724
+ * permanent orphan. Only a `committed` artifact whose owner still exists and
2725
+ * whose logical expiry has not passed is readable.
2726
+ *
2727
+ * Deletion is also two-phase: `markDeletePending` records the intent, then
2728
+ * `delete` removes the bytes. A failure between the two leaves the intent
2729
+ * behind, so a later `reconcile` finishes the job without the caller having to
2730
+ * re-supply anything.
2731
+ *
2732
+ * The interface is the extension point: a host running Redis history (whose
2733
+ * server-side TTL cannot notify a local filesystem) supplies its own
2734
+ * implementation. {@link FileDiagnosticArtifactStore} is the only built-in
2735
+ * backend, and Axl never silently falls back to a process temp folder.
2736
+ */
2737
+ /** Who an artifact belongs to. Today only an eval history entry can own one. */
2738
+ type ArtifactOwner = {
2739
+ kind: 'eval';
2740
+ /** The eval history entry id (`EvalHistoryEntry.id`). */
2741
+ id: string;
2742
+ };
2743
+ /** How completely a capture managed to record what it was asked to record. */
2744
+ type ArtifactStatus =
2745
+ /** Every record the run produced was written. */
2746
+ 'complete'
2747
+ /** A byte/queue bound stopped capture partway; `reason` says which. */
2748
+ | 'truncated'
2749
+ /** The writer never finalized (process death). Bytes are readable but partial. */
2750
+ | 'interrupted'
2751
+ /** The sink failed; there may be no usable bytes at all. `reason` says why. */
2752
+ | 'unavailable';
2753
+ /** The versioned descriptor written beside every artifact's records. */
2754
+ type ArtifactManifest = {
2755
+ version: 1;
2756
+ artifactId: string;
2757
+ owner: ArtifactOwner;
2758
+ state: 'staged' | 'committed' | 'delete_pending';
2759
+ /** Records actually written (including truncation stubs). */
2760
+ records: number;
2761
+ /** UTF-8 bytes of the written record lines. */
2762
+ bytes: number;
2763
+ status: ArtifactStatus;
2764
+ reason?: string;
2765
+ /** What the captured requests represent. Never provider wire bytes. */
2766
+ fidelity: 'runtime_request';
2767
+ redaction: 'applied' | 'none';
2768
+ createdAt: number;
2769
+ committedAt?: number;
2770
+ /** Absolute logical expiry, mirrored from the owning history row's retention. */
2771
+ expiresAt?: number;
2772
+ /** While `staged`: the deadline after which an abandoned writer is reclaimed. */
2773
+ leaseUntil?: number;
2774
+ /** Provenance when a rescore copied this artifact from another run's. */
2775
+ copiedFrom?: {
2776
+ artifactId: string;
2777
+ ownerId: string;
2778
+ };
2779
+ };
2780
+ /** A staged artifact, held open by a renewable lease while its writer runs. */
2781
+ type StagedArtifact = {
2782
+ artifactId: string;
2783
+ /** Push the lease deadline out. Call periodically from a long-running writer. */
2784
+ renewLease(): Promise<void>;
2785
+ };
2786
+ /** An opened artifact: its manifest plus a lazily-read stream of JSONL lines. */
2787
+ type OpenedArtifact = {
2788
+ manifest: ArtifactManifest;
2789
+ lines: AsyncIterable<string>;
2790
+ };
2791
+ /**
2792
+ * The outcome of a manifest write that can legitimately find nothing to write.
2793
+ *
2794
+ * `commit`, `markDeletePending` and `refreshExpiry` all name an artifact that a
2795
+ * sweep, an operator, or a crashed sibling process may have already removed.
2796
+ * Returning success in that case is how a history row gets published pointing
2797
+ * at bytes that are not there, still claiming `status: 'complete'` — so the
2798
+ * absence is reported instead of swallowed.
2799
+ */
2800
+ type ArtifactWriteResult = {
2801
+ ok: true;
2802
+ manifest: ArtifactManifest;
2803
+ } | {
2804
+ ok: false;
2805
+ reason: 'missing';
2806
+ };
2807
+ /**
2808
+ * The storage contract for diagnostic artifacts.
2809
+ *
2810
+ * Every method is idempotent where that is meaningful: deleting an unknown id
2811
+ * succeeds, marking delete-pending twice succeeds, and rolling back an
2812
+ * already-gone artifact succeeds. Keys are opaque ids the store mints — a
2813
+ * caller never supplies a path or a URL, which is what keeps an imported
2814
+ * artifact reference from naming an arbitrary file.
2815
+ */
2816
+ interface DiagnosticArtifactStore {
2817
+ /** Reserve a new artifact id for `owner`, held by a lease of `leaseMs`. */
2818
+ stage(owner: ArtifactOwner, opts: {
2819
+ leaseMs: number;
2820
+ }): Promise<StagedArtifact>;
2821
+ /** Append one already-encoded JSONL record. The caller pre-bounds its size. */
2822
+ append(artifactId: string, line: string): Promise<void>;
2823
+ /**
2824
+ * Seal the record stream and record how complete it is.
2825
+ *
2826
+ * `redaction` is what the WRITER applied. The store cannot infer it — the
2827
+ * records arrive already scrubbed — and a manifest that says `'none'` over
2828
+ * redacted bytes is the kind of quiet dishonesty a compliance reader acts on.
2829
+ */
2830
+ finalize(artifactId: string, status: ArtifactStatus, reason?: string, redaction?: 'applied' | 'none'): Promise<ArtifactManifest>;
2831
+ /** Promote a finalized artifact to `committed` once its owner row exists. */
2832
+ commit(artifactId: string, opts: {
2833
+ expiresAt?: number;
2834
+ }): Promise<ArtifactWriteResult>;
2835
+ /** Discard a staged artifact entirely (its owner row was never written). */
2836
+ rollback(artifactId: string): Promise<void>;
2837
+ /** Read a committed (or interrupted) artifact, or `undefined` if absent. */
2838
+ open(artifactId: string): Promise<OpenedArtifact | undefined>;
2839
+ /**
2840
+ * Copy `sourceId`'s records under a new id owned by `owner`, bounded by bytes.
2841
+ *
2842
+ * The result is a STAGED artifact, exactly as `stage` returns one: a copy is
2843
+ * the beginning of the new owner's capture, not the end of it — a rescore
2844
+ * goes on writing its own judge calls into the same artifact. It therefore
2845
+ * carries `renewLease` too, since a copy whose lease nobody holds is swept
2846
+ * out from under the run still filling it.
2847
+ */
2848
+ copy(sourceId: string, owner: ArtifactOwner, opts: {
2849
+ maxBytes: number;
2850
+ leaseMs: number;
2851
+ }): Promise<(StagedArtifact & {
2852
+ truncated: boolean;
2853
+ bytes: number;
2854
+ /** What the SOURCE said about the bytes just copied. Never inferred. */
2855
+ redaction: 'applied' | 'none';
2856
+ }) | undefined>;
2857
+ /** Record an idempotent deletion intent. Reports an artifact already gone. */
2858
+ markDeletePending(artifactId: string): Promise<ArtifactWriteResult>;
2859
+ /** Physically remove an artifact. Idempotent. */
2860
+ delete(artifactId: string): Promise<void>;
2861
+ /** Every manifest the store holds. */
2862
+ list(): Promise<ArtifactManifest[]>;
2863
+ /** Rewrite an artifact's logical expiry (its owner row was re-saved). */
2864
+ refreshExpiry(artifactId: string, expiresAt: number | undefined): Promise<ArtifactWriteResult>;
2865
+ }
2866
+ /**
2867
+ * The built-in filesystem backend: one directory per artifact under `root`,
2868
+ * holding `manifest.json` and `records.jsonl`.
2869
+ *
2870
+ * Manifest writes go through a temp file and a rename, so a crash mid-write
2871
+ * leaves either the previous manifest or the new one — never a half-parsed
2872
+ * JSON document that would make the artifact unreadable and unreclaimable.
2873
+ * Record appends are serialized per artifact through a promise chain so
2874
+ * concurrent writers cannot interleave partial lines.
2875
+ */
2876
+ declare class FileDiagnosticArtifactStore implements DiagnosticArtifactStore {
2877
+ private readonly root;
2878
+ /** Per-artifact serialization chain for appends. */
2879
+ private readonly writeChains;
2880
+ /** Live byte/record counters, folded into the manifest at finalize. */
2881
+ private readonly counters;
2882
+ /**
2883
+ * The most recently deleted ids, newest last.
2884
+ *
2885
+ * Ids are minted, never reused, so a write for one of these is always a
2886
+ * caller bug rather than a legitimate resurrection. Remembering them is what
2887
+ * stops such a write from recreating a directory holding records with no
2888
+ * manifest — a shape `list()` skips and reconciliation therefore never
2889
+ * reclaims.
2890
+ *
2891
+ * Bounded, because a long-lived Studio server deletes an artifact for every
2892
+ * eval it ever expires and an unbounded set would hold every id for the life
2893
+ * of the process. The window that matters is short: a dropped write is one
2894
+ * already in flight when the delete landed, i.e. queued behind it on the same
2895
+ * serializer. Keeping the last {@link DELETED_MEMORY} ids covers that window
2896
+ * by orders of magnitude. Past it the append does not recreate anything —
2897
+ * `appendFile` does not create parent directories, so it fails `ENOENT` — it
2898
+ * degrades that run's capture, which the guard exists to avoid: the channel
2899
+ * reads a rejected write as a dead sink and stops capturing the rest of the
2900
+ * run.
2901
+ */
2902
+ private readonly deleted;
2903
+ constructor(options: {
2904
+ root: string;
2905
+ });
2906
+ private dir;
2907
+ private readManifest;
2908
+ /** Atomic manifest replacement: write a sibling temp file, then rename over. */
2909
+ private writeManifest;
2910
+ /**
2911
+ * Run `work` after everything already queued for this artifact.
2912
+ *
2913
+ * EVERY write to an artifact goes through here — record appends and manifest
2914
+ * mutations alike — because a manifest mutation is a read-modify-write and
2915
+ * two of them interleave badly. The concrete race: the lease renewal reads a
2916
+ * pre-finalize manifest, `finalize` writes `complete/137 records`, and the
2917
+ * renewal's rename lands last and restores `interrupted/0`. `finalize`
2918
+ * returned its in-memory object, so the result would claim 137 records while
2919
+ * the artifact on disk claimed none.
2920
+ */
2921
+ private serialize;
2922
+ private mutate;
2923
+ stage(owner: ArtifactOwner, opts: {
2924
+ leaseMs: number;
2925
+ }): Promise<StagedArtifact>;
2926
+ append(artifactId: string, line: string): Promise<void>;
2927
+ finalize(artifactId: string, status: ArtifactStatus, reason?: string, redaction?: 'applied' | 'none'): Promise<ArtifactManifest>;
2928
+ commit(artifactId: string, opts: {
2929
+ expiresAt?: number;
2930
+ }): Promise<ArtifactWriteResult>;
2931
+ rollback(artifactId: string): Promise<void>;
2932
+ open(artifactId: string): Promise<OpenedArtifact | undefined>;
2933
+ copy(sourceId: string, owner: ArtifactOwner, opts: {
2934
+ maxBytes: number;
2935
+ leaseMs: number;
2936
+ }): Promise<(StagedArtifact & {
2937
+ truncated: boolean;
2938
+ bytes: number;
2939
+ redaction: 'applied' | 'none';
2940
+ }) | undefined>;
2941
+ markDeletePending(artifactId: string): Promise<ArtifactWriteResult>;
2942
+ delete(artifactId: string): Promise<void>;
2943
+ /** Record a deletion, evicting the oldest id once the window is full. */
2944
+ private rememberDeleted;
2945
+ list(): Promise<ArtifactManifest[]>;
2946
+ refreshExpiry(artifactId: string, expiresAt: number | undefined): Promise<ArtifactWriteResult>;
2947
+ }
2948
+
2949
+ /**
2950
+ * Client-side rate governor for provider HTTP calls: the plain, non-adaptive
2951
+ * base. Constructed directly (custom adapters, transcription) it applies only
2952
+ * the configured static caps. The built-in chat adapters use its pooled,
2953
+ * default-on adaptive subclass, `ScopeGovernor` (`governor-pool.ts`).
2306
2954
  *
2307
2955
  * Wraps the single `fetchWithRetry` chokepoint (see `retry.ts`) so a configured
2308
2956
  * provider can bound how many requests it has *in flight* at once and, optionally,
@@ -2321,10 +2969,13 @@ declare class AxlEventBus implements AsyncIterable<AxlEvent> {
2321
2969
  * its whole lifetime. Non-streaming calls still hold it across generation in
2322
2970
  * practice, because a provider sends no headers until the completion is
2323
2971
  * finished. Neither bounds tokens/min.
2324
- * - **Per provider instance.** Providers are singletons per (runtime, provider
2325
- * type), so one governor governs all calls through that adapter — but NOT
2326
- * embedder calls (the embedder is constructed outside the registry) and NOT
2327
- * other processes/runtimes sharing the same API key.
2972
+ * - **One governor per scope, per runtime.** The built-in chat adapters don't
2973
+ * construct this class directly: they resolve a governor per call from the
2974
+ * runtime's `GovernorPool` (`governor-pool.ts`), one per provider family +
2975
+ * base-URL origin + credential source + model. A directly constructed
2976
+ * `RateLimiter` (custom adapters) governs exactly the calls it is passed to.
2977
+ * Neither covers embedder calls (constructed outside the registry) nor other
2978
+ * processes.
2328
2979
  * - **`minIntervalMs` is global spacing, not a burst bucket.** A single
2329
2980
  * last-grant timestamp gates every grant: a permit may be free yet a grant
2330
2981
  * still waits out the interval. There is no accumulated burst allowance.
@@ -2332,8 +2983,9 @@ declare class AxlEventBus implements AsyncIterable<AxlEvent> {
2332
2983
  * never across a nested `ctx.ask`, so an agent-as-tool chain on the same
2333
2984
  * provider under `maxConcurrent: 1` still completes — permits don't stack.
2334
2985
  *
2335
- * `observe(res)` is a no-op seam in v1 for a future adaptive (header-driven)
2336
- * pacing follow-up.
2986
+ * `observe(res)` is a no-op here. The per-scope governor in `governor-pool.ts`
2987
+ * overrides it (quota-header hint) and adds the rate-limit brake; a directly
2988
+ * constructed `RateLimiter` is never adaptive.
2337
2989
  */
2338
2990
  /** Configuration for a provider's {@link RateLimiter}. All fields optional. */
2339
2991
  type RateLimitConfig = {
@@ -2355,23 +3007,67 @@ type RateLimitConfig = {
2355
3007
  * If set, `acquire()` rejects (fail loud) when a caller has queued longer than
2356
3008
  * this many ms, instead of waiting indefinitely — surfaces a misconfigured
2357
3009
  * cap rather than silently hanging the run.
3010
+ *
3011
+ * Bounds only a call's FIRST wait for a permit. On a scope that adapts (see
3012
+ * `adaptive`), a call first waits out any active rate-limit brake and only
3013
+ * then starts this clock; a call already queued when a brake begins keeps
3014
+ * its clock running through it. A retry's re-acquire after a rate-limit 429
3015
+ * is exempt: it is bounded by `maxRateLimitRetries` and the ask's `timeout`.
2358
3016
  */
2359
3017
  acquireTimeoutMs?: number;
3018
+ /**
3019
+ * Feedback-driven pacing on every built-in chat provider. Default `true`.
3020
+ * When on, a rate-limit 429 brakes every call on the scope for the
3021
+ * exponential backoff, lengthened (never shortened) by a longer
3022
+ * `Retry-After` / `retry-after-ms` and clamped at 60 s, and
3023
+ * the call retries on its own budget (`maxRateLimitRetries`), and the scope
3024
+ * then paces its grants adaptively (a rate seeded from recent demand,
3025
+ * recovered linearly on success, dropped once traffic stays well below it;
3026
+ * tuning is internal) until it reopens. Nothing waits before the first 429.
3027
+ *
3028
+ * Only first-party OpenAI (`openai:`, `openai-responses:`) and Anthropic at
3029
+ * the vendor's default endpoint origin read the 429 body: there a spend-cap
3030
+ * 429 fails fast, and a low quota header holds recovery. Everywhere else
3031
+ * (Gemini, OpenAI-compatible presets, a proxy or gateway `baseUrl`) every
3032
+ * 429 is a rate limit, so a spend-cap or daily-quota 429 fails only once
3033
+ * `maxRateLimitRetries` is spent.
3034
+ *
3035
+ * `false` restores the plain behavior: a 429 shares the transient retry
3036
+ * budget, holds up no other call, and nothing is paced. Ignored by a
3037
+ * directly constructed `RateLimiter`, which is never adaptive.
3038
+ */
3039
+ adaptive?: boolean;
3040
+ /**
3041
+ * Retries after a rate-limit 429 on an adaptive scope, separate from the
3042
+ * transient (503/529/network) budget. Default `8`. A non-negative integer;
3043
+ * `0` still brakes the scope but returns the 429. Ignored where
3044
+ * `adaptive` has no effect.
3045
+ */
3046
+ maxRateLimitRetries?: number;
2360
3047
  };
2361
3048
  /**
2362
3049
  * Counting semaphore + FIFO waiter queue. Dependency-free. Not shared across
2363
3050
  * processes — purely an in-process pacing aid.
2364
3051
  */
2365
3052
  declare class RateLimiter {
2366
- private readonly maxConcurrent;
2367
- private readonly minIntervalMs;
2368
- private readonly acquireTimeoutMs?;
3053
+ protected maxConcurrent: number;
3054
+ protected minIntervalMs: number;
3055
+ protected acquireTimeoutMs?: number;
2369
3056
  private active;
2370
3057
  private lastGrantAt;
2371
3058
  private readonly queue;
2372
3059
  private spacingTimer?;
2373
3060
  private warnedQueued;
2374
3061
  constructor(config?: RateLimitConfig);
3062
+ /**
3063
+ * Apply already-sanitized limits (see {@link sanitizeRateLimitConfig}). An
3064
+ * absent field means "no limit". Takes effect for the next grant; a waiter
3065
+ * already queued keeps the `acquireTimeoutMs` timer it was armed with.
3066
+ *
3067
+ * @internal Extension point for Axl's own per-scope governor; not a supported
3068
+ * public contract and may change without notice.
3069
+ */
3070
+ protected applyLimits(limits: RateLimitConfig): void;
2375
3071
  /**
2376
3072
  * Acquire a permit. Resolves when one is granted (respecting `maxConcurrent`
2377
3073
  * and `minIntervalMs`). The caller MUST pair every resolved `acquire()` with
@@ -2386,18 +3082,298 @@ declare class RateLimiter {
2386
3082
  * With `acquireTimeoutMs`, a waiter that sits in the queue too long rejects.
2387
3083
  */
2388
3084
  acquire(signal?: AbortSignal): Promise<void>;
3085
+ /**
3086
+ * Queue for a permit. A `priority` waiter goes ahead of every non-priority
3087
+ * waiter (FIFO among priority waiters) and never warns about queueing;
3088
+ * `timeoutMs` arms the fail-loud queue timeout (`undefined`: none).
3089
+ *
3090
+ * @internal Extension point for Axl's own per-scope governor (a retry's
3091
+ * re-acquire); not a supported public contract and may change without notice.
3092
+ */
3093
+ protected enqueue(signal: AbortSignal | undefined, options: {
3094
+ priority: boolean;
3095
+ timeoutMs: number | undefined;
3096
+ }): Promise<void>;
3097
+ /**
3098
+ * Take a permit synchronously if one is free right now — nobody queued,
3099
+ * capacity available and spacing satisfied — exactly when `acquire()` would
3100
+ * grant without waiting. Returns whether it took one; the caller pairs a
3101
+ * `true` with one {@link release}.
3102
+ *
3103
+ * @internal Extension point for Axl's own per-scope governor.
3104
+ */
3105
+ protected tryGrant(): boolean;
3106
+ /**
3107
+ * The minimum gap between two grants, in ms. Every grant path (the queue
3108
+ * pump, priority waiters and {@link tryGrant}) spaces by it.
3109
+ *
3110
+ * @internal Extension point for Axl's own per-scope governor, which widens it
3111
+ * while adaptively paced; not a supported public contract.
3112
+ */
3113
+ protected grantIntervalMs(): number;
3114
+ /**
3115
+ * Called on every grant, with its timestamp.
3116
+ *
3117
+ * @internal Extension point for Axl's own per-scope governor.
3118
+ */
3119
+ protected onGrant(_at: number): void;
3120
+ /** How long the next grant must wait for spacing; `<= 0` means it may go now. */
3121
+ private spacingWaitMs;
3122
+ /**
3123
+ * Whether a CONFIGURED cap is what stops a grant right now: the concurrency
3124
+ * cap is full, or the configured spacing has not elapsed. Queueing for any
3125
+ * other reason (a brake, adaptive spacing) is not a cap the user can raise,
3126
+ * so it never triggers the queued warning.
3127
+ */
3128
+ private staticCapBinding;
3129
+ /**
3130
+ * Whether any caller is queued for a permit.
3131
+ *
3132
+ * @internal Extension point for Axl's own per-scope governor.
3133
+ */
3134
+ protected hasWaiters(): boolean;
2389
3135
  /** Release a previously acquired permit and wake the next waiter. */
2390
3136
  release(): void;
2391
- /** v1 no-op seam for a future adaptive (rate-limit-header-driven) pacing follow-up. */
3137
+ /** No-op; the per-scope governor overrides it to read quota headers. */
2392
3138
  observe(_res: Response): void;
2393
- /** Drain the queue: grant head waiters while a permit is free and spacing allows. */
2394
- private pump;
3139
+ /**
3140
+ * Drain the queue: grant head waiters while a permit is free and spacing allows.
3141
+ * Protected so the internal per-scope governor can gate grants on its own
3142
+ * state (for example a brake) by overriding it.
3143
+ *
3144
+ * @internal Extension point for Axl's own per-scope governor; not a supported
3145
+ * public contract and may change without notice.
3146
+ */
3147
+ protected pump(): void;
2395
3148
  private grant;
2396
3149
  /** Reject a still-queued waiter (abort or timeout) and remove it cleanly. */
2397
3150
  private settleWaiter;
2398
3151
  private clearWaiterTimers;
2399
3152
  }
2400
3153
 
3154
+ /**
3155
+ * Why a 429 was returned. `'unknown'` means the body did not identify either
3156
+ * cause; the transport treats it as a rate limit, as it does every 429 on a
3157
+ * scope with no dialect.
3158
+ */
3159
+ type QuotaClass = 'rate_limit' | 'spend_cap' | 'unknown';
3160
+ type QuotaDialect = {
3161
+ /**
3162
+ * Minimum `remaining / limit` over the dialect's lanes, in `[0, 1]`, or
3163
+ * `undefined` when no lane is usable. Meant for 2xx responses.
3164
+ *
3165
+ * Total over header *values*. A `Headers` object whose `get` itself throws
3166
+ * is a broken input rather than provider data, so that throw propagates for
3167
+ * the governor's warn-once containment instead of being hidden here.
3168
+ */
3169
+ hint(headers: Headers): number | undefined;
3170
+ /**
3171
+ * Classify a 429 without consuming the original body. Never rejects:
3172
+ * a non-429, a response without `clone()` or a body, a failing read, or a
3173
+ * non-JSON body all yield `'unknown'`.
3174
+ */
3175
+ classify429(res: Response): Promise<QuotaClass>;
3176
+ };
3177
+
3178
+ /**
3179
+ * Per-runtime pool of rate governors, one per **scope**.
3180
+ *
3181
+ * A provider's rate limits belong to the account and model, not to an adapter
3182
+ * instance. Before this pool, every built-in adapter built its own
3183
+ * `RateLimiter`, so `openai:` and `openai-responses:` on one key got two
3184
+ * independent caps (effective concurrency = the sum), and one cap covered every
3185
+ * model the adapter served. Here a scope is
3186
+ *
3187
+ * provider family + base-URL origin + credential source + model
3188
+ *
3189
+ * - **Family**: `openai` for both OpenAI adapters (Chat Completions and
3190
+ * Responses share an account's limits); otherwise the adapter or preset name.
3191
+ * - **Origin**: `new URL(baseUrl).origin`, so a proxy or a self-hosted endpoint
3192
+ * is a different account. The origin carries no userinfo, path or query.
3193
+ * - **Credential source**: a callback `apiKey` by *identity* (a rotating token
3194
+ * callback is one scope), a string by *value* (two tenants' keys never
3195
+ * share). Each source maps to an opaque in-process number; the key itself is
3196
+ * never part of a map key that is logged, never in a warning, never in an
3197
+ * error.
3198
+ * - **Model**: the effective wire model of the call. There is no model-family
3199
+ * table; models that share a vendor bucket are separate scopes.
3200
+ *
3201
+ * Ownership: the runtime's `ProviderRegistry` holds one pool and hands it to the
3202
+ * built-in factories, which join the adapter they construct to it at
3203
+ * construction ({@link bindGovernorPool}). An adapter constructed directly
3204
+ * joins a private pool on its first call, so one instance registered in two runtimes shares its
3205
+ * governors (the documented explicit cross-runtime sharing path). Sharing is
3206
+ * never inferred from equal keys across runtimes.
3207
+ *
3208
+ * Config merge: only when two provider blocks reach one account (for example
3209
+ * `providers.openai` and `providers['openai-responses']` with the same key and
3210
+ * origin). Each explicit field takes the strictest value and the conflict is
3211
+ * warned about once per account. Merging only ever tightens, so a live
3212
+ * governor can adopt the merged limits in place.
3213
+ *
3214
+ * Internal: nothing here is barrel-exported.
3215
+ */
3216
+
3217
+ /**
3218
+ * The governor for one scope. A {@link RateLimiter} whose limits can be
3219
+ * tightened in place when a second provider block reaches the same account.
3220
+ *
3221
+ * Unless `adaptive` is `false` ({@link ScopeGovernor.adapts}), it is also the
3222
+ * scope's **fleet brake**: `fetchWithRetry` calls {@link brake} after a
3223
+ * rate-limit 429, and from then until `brakeUntil` no call on the scope is
3224
+ * granted a permit or dispatches. `braked()`, `awaitClear()` and `pump()`
3225
+ * share one predicate, `Date.now() < brakeUntil`. A scope that does not adapt
3226
+ * never brakes, so it behaves exactly as a plain `RateLimiter`.
3227
+ *
3228
+ * Adapting needs no quota dialect. A dialect (first-party OpenAI and Anthropic
3229
+ * at the vendor origin, see `quota.ts`) adds only two vendor-specific extras:
3230
+ * spend-cap classification of a 429 body, and the 2xx quota hint that holds
3231
+ * recovery. Without one, every 429 is treated as a rate limit and the hint is
3232
+ * never read, so recovery and reopening proceed as if the account were healthy.
3233
+ */
3234
+ declare class ScopeGovernor extends RateLimiter {
3235
+ readonly dialect?: QuotaDialect | undefined;
3236
+ private readonly family;
3237
+ private brakeUntil?;
3238
+ private brakeEndTimer?;
3239
+ private brakeEndAt?;
3240
+ private adaptive;
3241
+ private rateLimitRetries;
3242
+ private warnedHint;
3243
+ private warnedEngaged;
3244
+ /** The last 2xx quota hint (`remaining / limit`, see `quota.ts`); a low one holds growth. */
3245
+ lastHint: number | undefined;
3246
+ /**
3247
+ * Grants per second, or `undefined` while the scope is fully open (its state
3248
+ * until the first rate-limit 429, and again after it reopens). Enforced as a
3249
+ * minimum gap of `1000 / rate` ms between grants, never below `minIntervalMs`.
3250
+ */
3251
+ private rate;
3252
+ /** The rate the last cut set; recovery adds `max(rateAtLastCut, ALPHA_FLOOR_RATE) / R` per second. */
3253
+ private rateAtLastCut;
3254
+ /** When the last cut happened. A 429 cuts only if its request left after this. */
3255
+ private lastCutAt;
3256
+ /**
3257
+ * Recovery accrues from here. Advanced by each 2xx and pushed to the end of
3258
+ * any brake, so braked time never accrues.
3259
+ */
3260
+ private lastProgressAt;
3261
+ /** Since when the reopen condition has held on every 2xx; `undefined` while it doesn't. */
3262
+ private reopenSince;
3263
+ /** Grant timestamps within `WINDOW_MS`, oldest first, from `grantsHead` on. */
3264
+ private grants;
3265
+ private grantsHead;
3266
+ /** Braked spans overlapping the window, oldest first, non-overlapping. */
3267
+ private brakeSpans;
3268
+ /**
3269
+ * @param limits already sanitized (see `sanitizeRateLimitConfig`).
3270
+ * @param dialect the scope's quota dialect, or `undefined` when the vendor
3271
+ * has none (no spend-cap classification, no quota hint).
3272
+ * @param family the provider family, only to name the scope in a warning.
3273
+ */
3274
+ constructor(limits: RateLimitConfig, dialect?: QuotaDialect | undefined, family?: string);
3275
+ /** Adopt the account's merged limits. Merges only tighten (see {@link STRICTEST}). */
3276
+ reconfigure(limits: RateLimitConfig): void;
3277
+ /** Whether this scope brakes and retries rate-limit 429s on their own budget. */
3278
+ get adapts(): boolean;
3279
+ /** The rate-limit retry budget (`maxRateLimitRetries`). */
3280
+ get maxRateLimitRetries(): number;
3281
+ /** The brake predicate shared by `awaitClear`, `pump` and `fetchWithRetry`. */
3282
+ braked(): boolean;
3283
+ /**
3284
+ * On a rate-limit 429: brake every call on the scope for `ms` (already
3285
+ * clamped by the caller to the transport's backoff ceiling), and cut the
3286
+ * adaptive rate. Extends, never shortens, an active brake. `dispatchedAt` is
3287
+ * when the 429'd request left.
3288
+ *
3289
+ * The cut happens at most once per congestion epoch: only when the 429'd
3290
+ * request left after the last cut. The rest of a wave that was already in
3291
+ * flight extends the brake without cutting again.
3292
+ */
3293
+ brake(ms: number, dispatchedAt: number): void;
3294
+ /** The adaptive rate in grants per second; `undefined` while fully open. @internal */
3295
+ get currentRate(): number | undefined;
3296
+ /**
3297
+ * `rate = BETA × min(rate ?? ∞, demand)`, floored at `MIN_RATE`, where demand
3298
+ * is grants in the window over its unbraked elapsed time (floored at 1 s).
3299
+ * Demand is used only when it can be trusted: when no brake overlaps the
3300
+ * window (a burst from a cold scope reads its true count), or once the window
3301
+ * holds `MIN_DEMAND_SPAN_MS` of unbraked time. Right after a long brake the
3302
+ * window holds only a few spaced grants, so the cut uses the current rate.
3303
+ */
3304
+ private cut;
3305
+ /** Advance recovery on a 2xx, then check whether the scope can reopen (plan §4.4, Q11). */
3306
+ private recover;
3307
+ /**
3308
+ * Whether `rate` exceeds `REOPEN_FACTOR ×` the window's peak one-second grant
3309
+ * count. The peak is at least the window's per-second average, so a scope
3310
+ * paced near its demand (the common case, with the largest window) is
3311
+ * answered without the O(window) scan.
3312
+ */
3313
+ private farAbovePeakDemand;
3314
+ /** The most grants in any one-second span of the window (two pointers, O(window)). */
3315
+ private peakOneSecondGrants;
3316
+ /** Total braked time inside `[from, to)`. */
3317
+ private brakedOverlap;
3318
+ private recordBrakeSpan;
3319
+ /** Drop grants and braked spans older than the window; memory stays bounded by it. */
3320
+ private pruneWindow;
3321
+ protected onGrant(at: number): void;
3322
+ /** Adaptive spacing on top of the configured `minIntervalMs`, never below it (RQ5). */
3323
+ protected grantIntervalMs(): number;
3324
+ /**
3325
+ * Resolve once the scope is not braked. Rejects with `signal.reason` if the
3326
+ * signal is already aborted (even when not braked) or aborts while waiting.
3327
+ */
3328
+ awaitClear(signal?: AbortSignal): Promise<void>;
3329
+ /**
3330
+ * Re-acquire a permit for a call that already dispatched (after a
3331
+ * rate-limit 429, or a transient backoff that woke into a brake): queued
3332
+ * ahead of first-time callers and exempt from `acquireTimeoutMs`.
3333
+ */
3334
+ reacquire(signal?: AbortSignal): Promise<void>;
3335
+ /**
3336
+ * Take a permit without waiting, if `acquire()` / `reacquire()` would grant
3337
+ * one at once: not braked and a permit free. Lets the transport skip timing a
3338
+ * wait that never happened, so an unqueued call reports `queuedMs` exactly 0.
3339
+ */
3340
+ tryAcquire(): boolean;
3341
+ /**
3342
+ * On a 2xx: read the quota hint (if the scope has a dialect), then advance
3343
+ * recovery. Total: a throwing dialect warns once and is ignored (the hint is
3344
+ * then treated as healthy). Without a dialect `lastHint` stays `undefined`.
3345
+ */
3346
+ observe(res: Response): void;
3347
+ /**
3348
+ * Grants nothing while braked; re-pumps at the brake's end if anyone is
3349
+ * queued. The brake-end timer lives only as long as its waiters: when the
3350
+ * last one leaves (abort or timeout, both of which re-pump) it is cleared, so
3351
+ * a long brake never holds the event loop open with nothing to wake. It is
3352
+ * deliberately not `unref`'d — while a waiter exists, that timer is the only
3353
+ * thing that will resume its call.
3354
+ */
3355
+ protected pump(): void;
3356
+ private clearBrakeEnd;
3357
+ private armBrakeEnd;
3358
+ private sleepUntilBrakeEnd;
3359
+ }
3360
+
3361
+ /**
3362
+ * Input modalities a profile declares for the OpenAI-compatible wire format.
3363
+ * Declaring a modality is the ONLY way a profile opts in: the engine derives
3364
+ * `inputCapabilities`, `validateInput`, and the rich-part builder from this,
3365
+ * never from the provider's name.
3366
+ */
3367
+ type ProfileInputModalities = {
3368
+ image?: {
3369
+ sources: readonly InputMediaSource['type'][];
3370
+ };
3371
+ audio?: {
3372
+ sources: readonly RecordedAudioSource['type'][];
3373
+ /** Closed media type → `input_audio.format` table (see `audio-format.ts`). */
3374
+ formats: Readonly<Record<string, string>>;
3375
+ };
3376
+ };
2401
3377
  /**
2402
3378
  * A profile value that may legitimately differ per model within one provider.
2403
3379
  * Mirrors the model-aware shape of OpenAI's `clampReasoningEffort(model, …)`.
@@ -2505,6 +3481,14 @@ type CapabilityFlags = {
2505
3481
  * `stream_options.include_usage`. When false, don't request it.
2506
3482
  */
2507
3483
  supportsStreamUsage?: boolean;
3484
+ /**
3485
+ * Rich input modalities this provider carries on the Chat Completions wire.
3486
+ * Absent (the default) means text only: the engine rejects every rich part,
3487
+ * declares no capability to the runtime, and passes array content through
3488
+ * untouched — byte-identical to the pre-audio engine. Adding a modality here
3489
+ * is the single opt-in switch; the engine never infers one from the name.
3490
+ */
3491
+ inputModalities?: ProfileInputModalities;
2508
3492
  };
2509
3493
  /**
2510
3494
  * A provider definition. The engine reads ONLY this — adding a provider is
@@ -2611,11 +3595,13 @@ type OpenAICompatibleOptions = {
2611
3595
  declare class OpenAICompatibleProvider implements Provider {
2612
3596
  readonly name: string;
2613
3597
  readonly reportsRequestLifecycle: true;
2614
- inputCapabilities(model: string): {
2615
- image?: {
2616
- sources: readonly InputMediaSource['type'][];
2617
- };
2618
- };
3598
+ /**
3599
+ * Modalities the effective profile declares for this model. A blank model
3600
+ * declares nothing, so a rich request against one fails validation exactly
3601
+ * as it did before any modality existed.
3602
+ */
3603
+ protected inputModalities(model: string): ProfileInputModalities;
3604
+ inputCapabilities(model: string): InputModalitySupport;
2619
3605
  validateInput(request: ProviderInputValidationRequest): ProviderInputValidationResult;
2620
3606
  /** `json_schema` is honored natively when the profile's `supportsJsonSchema`
2621
3607
  * capability is true for this model (the default); otherwise the engine
@@ -2627,8 +3613,20 @@ declare class OpenAICompatibleProvider implements Provider {
2627
3613
  /** A key string, or a resolver invoked per request (expiring tokens). */
2628
3614
  protected readonly apiKeySource: ApiKeySource;
2629
3615
  protected readonly authHeader?: AuthHeader;
2630
- protected readonly governor?: RateLimiter;
3616
+ /**
3617
+ * Namespaced so it cannot collide with a member a downstream subclass
3618
+ * declares. A plain property (not `#private` or a WeakMap keyed by `this`)
3619
+ * so a caller's own Proxy around the adapter still reaches it.
3620
+ */
3621
+ private readonly axlRateGovernors;
2631
3622
  constructor(options: OpenAICompatibleOptions);
3623
+ /**
3624
+ * The rate governor for one call to `model` (the effective wire model), from
3625
+ * the runtime's per-scope pool. Every scope has one (with no `rateLimit` it
3626
+ * caps nothing until a rate-limit 429). Subclasses that issue their own
3627
+ * `fetchWithRetry` pass this as `governor`.
3628
+ */
3629
+ protected governorFor(model: string): ScopeGovernor;
2632
3630
  /** Resolve the API key for one request, validating against allowMissingApiKey. */
2633
3631
  protected resolveKey(): Promise<string>;
2634
3632
  chat(messages: ChatMessage[], options: ChatOptions): Promise<ProviderResponse>;
@@ -2650,6 +3648,13 @@ declare class OpenAICompatibleProvider implements Provider {
2650
3648
  response?: OpenAIModelResponse;
2651
3649
  }): number | undefined;
2652
3650
  protected parseResponse(json: OpenAIChatResponse, model: string, request?: Record<string, unknown>): ProviderResponse;
3651
+ /**
3652
+ * How this profile's `computeCost` derived its figure. `'from-response'`
3653
+ * profiles (OpenRouter, xAI) echo a vendor-supplied USD amount and are
3654
+ * authoritative; table and zero pricing are Axl estimates. Native subclasses
3655
+ * that override `computeCost` override this alongside it.
3656
+ */
3657
+ protected costProvenance(cost: number | undefined): ProviderResponse['costProvenance'];
2653
3658
  private roundTripMetadata;
2654
3659
  /**
2655
3660
  * PRIVATE, and deliberately not an extension point. It was `protected`, but
@@ -2673,10 +3678,12 @@ type OpenAIUsage = {
2673
3678
  total_tokens: number;
2674
3679
  completion_tokens_details?: {
2675
3680
  reasoning_tokens?: number;
3681
+ audio_tokens?: unknown;
2676
3682
  };
2677
3683
  prompt_tokens_details?: {
2678
3684
  cached_tokens?: number;
2679
3685
  cache_write_tokens?: number;
3686
+ audio_tokens?: unknown;
2680
3687
  };
2681
3688
  /** OpenRouter / Vercel Gateway: per-call cost in USD. */
2682
3689
  cost?: number;
@@ -2838,12 +3845,53 @@ interface StateStore {
2838
3845
  * deleting an unknown id returns `false` without throwing.
2839
3846
  */
2840
3847
  deleteExecution?(executionId: string): Promise<boolean>;
2841
- /** Save an eval result to history. */
3848
+ /** Save an eval result to history. Creates the row, or replaces it. */
2842
3849
  saveEvalResult?(entry: EvalHistoryEntry): Promise<void>;
3850
+ /**
3851
+ * Replace an eval history row that ALREADY EXISTS, leaving its retention
3852
+ * exactly as it was. Returns `true` iff a row was there and was updated;
3853
+ * **never creates one**.
3854
+ *
3855
+ * This is how the runtime writes a CORRECTION — a diagnostics sweep rewriting
3856
+ * a row whose artifact it just reclaimed, a commit failure downgrading one.
3857
+ * Such a write has no business creating anything: the row may have been
3858
+ * deleted (a right-to-be-forgotten request) or expired between the moment the
3859
+ * correction was computed and the moment it lands, and `saveEvalResult` would
3860
+ * bring it back — permanently on a store with no expiry. Checking first and
3861
+ * then saving does not close that window; only the store can, so the
3862
+ * condition lives here.
3863
+ *
3864
+ * Implement it conditionally and atomically: `SET ... XX KEEPTTL` on Redis,
3865
+ * `UPDATE ... WHERE id = ?` on SQL, a presence check on an in-process map.
3866
+ * A store that does not implement it gets no correction written at all.
3867
+ */
3868
+ updateEvalResult?(entry: EvalHistoryEntry): Promise<boolean>;
2843
3869
  /** List eval history entries (most recent first). */
2844
3870
  listEvalResults?(limit?: number): Promise<EvalHistoryEntry[]>;
2845
3871
  /** Delete an eval history entry by id. Returns true if an entry was deleted. */
2846
3872
  deleteEvalResult?(id: string): Promise<boolean>;
3873
+ /**
3874
+ * Retention capability: does this eval history row still exist, and when does
3875
+ * the store itself expire it?
3876
+ *
3877
+ * Diagnostic artifacts (opt-in captured requests) live OUTSIDE the state store
3878
+ * — typically on a filesystem — and their lifetime must follow the history row
3879
+ * that owns them. A store with server-side expiry (Redis `PTTL`) cannot notify
3880
+ * that filesystem when a key ages out, so the runtime mirrors the absolute
3881
+ * `expiresAt` onto the artifact manifest at commit time and sweeps expired
3882
+ * bytes on a timer and at startup. Physical deletion is therefore EVENTUAL,
3883
+ * not synchronous with the store's expiry.
3884
+ *
3885
+ * Built-ins implement it: `MemoryStore` and `SqliteStore` never expire, so
3886
+ * `expiresAt` is `undefined`; `RedisStore` reports existence and any live TTL.
3887
+ * A custom store WITHOUT this method cannot host managed artifact capture —
3888
+ * `AxlRuntime` rejects that configuration when it is constructed rather than
3889
+ * after a run has spent money.
3890
+ */
3891
+ getEvalRetention?(id: string): Promise<{
3892
+ exists: boolean;
3893
+ expiresAt?: number;
3894
+ }>;
2847
3895
  /** List all session IDs (used by Studio session browser). */
2848
3896
  listSessions?(): Promise<string[]>;
2849
3897
  /**
@@ -3015,12 +4063,18 @@ type ProviderConfig = {
3015
4063
  /** OpenAI-compatible presets only: override the profile auth header shape. */
3016
4064
  authHeader?: AuthHeader;
3017
4065
  /**
3018
- * Opt-in client-side rate governor for this provider's HTTP calls (see
3019
- * {@link RateLimitConfig}). Bounds in-flight request concurrency (and,
3020
- * optionally, request spacing) through the shared `fetchWithRetry` chokepoint.
3021
- * Omitted ⇒ no governor (behavior unchanged). Caveat: governs **chat** calls
3022
- * through this provider instance only — NOT memory-embedder calls (constructed
3023
- * outside the registry) and NOT other processes sharing the same API key.
4066
+ * Client-side rate governor for this provider's chat calls (see
4067
+ * {@link RateLimitConfig}), through the shared `fetchWithRetry` chokepoint.
4068
+ * Omitted ⇒ no static caps (no concurrency bound, no spacing), but the
4069
+ * adaptive brake and pacing still apply after a rate-limit 429; turn them
4070
+ * off with `{ adaptive: false }`. Governors are pooled per runtime, one per
4071
+ * scope: provider family (`openai` covers `openai-responses`) + base-URL
4072
+ * origin + credential source + model. Two blocks reaching one scope use the
4073
+ * strictest value per field. Caveats: the pooled, adaptive governor covers
4074
+ * **chat** calls only. Transcription providers apply the same static caps
4075
+ * through their own per-instance, non-adaptive limiter; memory-embedder
4076
+ * calls, other runtimes (unless they share a provider instance) and other
4077
+ * processes are not governed.
3024
4078
  */
3025
4079
  rateLimit?: RateLimitConfig;
3026
4080
  };
@@ -3128,6 +4182,49 @@ type DiagnosticsConfig = {
3128
4182
  * `AXL_DIAGNOSTICS_SILENT=true`.
3129
4183
  */
3130
4184
  silent?: boolean;
4185
+ /**
4186
+ * Where opt-in captured-request artifacts are stored, and how their lifecycle
4187
+ * is reclaimed. Managed capture (`runEval({ captureRequests: true })`) is
4188
+ * UNAVAILABLE until this is configured — Axl never silently writes captured
4189
+ * prompts to a process temp folder.
4190
+ *
4191
+ * Supply exactly one of `root` (use the built-in
4192
+ * {@link DiagnosticArtifactStore} filesystem backend) or `store` (your own
4193
+ * backend — required when eval history lives in Redis, whose server-side TTL
4194
+ * cannot notify a local filesystem).
4195
+ *
4196
+ * The configured `StateStore` must implement `getEvalRetention` so an
4197
+ * artifact's logical expiry can follow its owning history row; a custom store
4198
+ * without it is rejected when the runtime is constructed, not after a run has
4199
+ * already spent money.
4200
+ */
4201
+ artifacts?: DiagnosticArtifactsConfig;
4202
+ };
4203
+ /** Artifact storage + reclamation settings. See {@link DiagnosticsConfig.artifacts}. */
4204
+ type DiagnosticArtifactsConfig = {
4205
+ /** A host-supplied backend. Takes precedence over `root`. */
4206
+ store?: DiagnosticArtifactStore;
4207
+ /** Directory for the built-in filesystem backend. Ignored when `store` is set. */
4208
+ root?: string;
4209
+ /** Reconciliation sweep period in ms. Default `60_000`. The timer is unref'd
4210
+ * and cleared by `runtime.shutdown()`. */
4211
+ sweepIntervalMs?: number;
4212
+ /** How long a staged (actively written) artifact is protected from the
4213
+ * sweeper before it counts as abandoned. Default `300_000`. */
4214
+ leaseMs?: number;
4215
+ /**
4216
+ * Longest a single artifact's lease is renewed before the runtime lets go.
4217
+ * Default `86_400_000` (24 h).
4218
+ *
4219
+ * The lease renewal is a timer, and a caller that never finalizes or rolls
4220
+ * back its artifact would otherwise pin it forever — a leak with no
4221
+ * self-healing path, because the sweeper only reclaims a lease that expired.
4222
+ * Past this bound the runtime stops renewing, the lease runs out, and the
4223
+ * artifact is reclaimed on the next sweep like any abandoned writer's. Set it
4224
+ * above the longest run you expect; a run that outlives it keeps working and
4225
+ * only loses its captured requests.
4226
+ */
4227
+ maxHoldMs?: number;
3131
4228
  };
3132
4229
 
3133
4230
  /** Full Axl configuration */
@@ -3160,6 +4257,7 @@ type ResolvedProvider = {
3160
4257
  provider: Provider;
3161
4258
  model: string;
3162
4259
  };
4260
+ /** A user factory registered with {@link ProviderRegistry.register}. */
3163
4261
  type ProviderFactory = (config: AxlConfig) => Provider;
3164
4262
  /**
3165
4263
  * Registry for LLM providers. Holds cached provider instances and supports
@@ -3175,6 +4273,11 @@ declare class ProviderRegistry {
3175
4273
  private instances;
3176
4274
  /** Factory functions, keyed by provider name */
3177
4275
  private factories;
4276
+ /**
4277
+ * Rate governors for the adapters built-in factories construct, one per
4278
+ * scope. Per registry, so per runtime: the state dies with the runtime.
4279
+ */
4280
+ private governors;
3178
4281
  /** Fallback provider returned when no factory or instance matches */
3179
4282
  private fallbackInstance?;
3180
4283
  constructor();
@@ -3220,6 +4323,9 @@ declare class ProviderRegistry {
3220
4323
  resolve(uri: string | undefined, config?: AxlConfig): ResolvedProvider;
3221
4324
  /**
3222
4325
  * Clear all cached provider instances. Useful for testing or reconfiguration.
4326
+ * Also starts a fresh governor pool, so adapters rebuilt from a changed
4327
+ * `rateLimit` are not merged with the discarded ones. A call already in
4328
+ * flight finishes on the governor it holds.
3223
4329
  */
3224
4330
  clearCache(): void;
3225
4331
  /**
@@ -3229,58 +4335,6 @@ declare class ProviderRegistry {
3229
4335
  clearFactories(): void;
3230
4336
  }
3231
4337
 
3232
- /** Audio supplied to a transcription provider. Network and file-system sources
3233
- * are deliberately excluded: callers retain control of all I/O. */
3234
- type RecordedAudioSource = {
3235
- readonly type: 'bytes';
3236
- readonly data: Uint8Array;
3237
- readonly mediaType: string;
3238
- } | {
3239
- readonly type: 'base64';
3240
- readonly data: string;
3241
- readonly mediaType: string;
3242
- } | {
3243
- readonly type: 'provider-file';
3244
- readonly provider: string;
3245
- readonly reference: string;
3246
- readonly mediaType?: string;
3247
- };
3248
- type TranscriptionRequest = {
3249
- readonly model: string;
3250
- readonly audio: RecordedAudioSource;
3251
- readonly language?: string;
3252
- readonly timestamps?: 'segment' | 'word';
3253
- readonly diarization?: boolean;
3254
- readonly providerOptions?: Record<string, unknown>;
3255
- };
3256
- type Transcript = {
3257
- readonly text: string;
3258
- readonly detectedLanguages?: readonly string[];
3259
- readonly segments?: readonly {
3260
- readonly text: string;
3261
- readonly start: number;
3262
- readonly end: number;
3263
- readonly speaker?: string;
3264
- }[];
3265
- readonly words?: readonly {
3266
- readonly text: string;
3267
- readonly start: number;
3268
- readonly end: number;
3269
- readonly speaker?: string;
3270
- }[];
3271
- readonly usage?: {
3272
- readonly audioSeconds?: number;
3273
- readonly inputTokens?: number;
3274
- readonly outputTokens?: number;
3275
- readonly totalTokens?: number;
3276
- readonly cost?: number;
3277
- };
3278
- readonly pricingStatus?: 'priced' | 'unpriced' | 'zero';
3279
- readonly providerMetadata?: Readonly<Record<string, unknown>>;
3280
- };
3281
- /** Maximum decoded size of caller-supplied inline audio. */
3282
- declare const MAX_INLINE_TRANSCRIPTION_BYTES: number;
3283
-
3284
4338
  type TranscriptionCapabilities = {
3285
4339
  readonly sources: readonly RecordedAudioSource['type'][];
3286
4340
  readonly timestamps?: readonly ('segment' | 'word')[];
@@ -3409,11 +4463,6 @@ type RecallResult = {
3409
4463
  data: unknown | VectorResult[] | null;
3410
4464
  usage?: EmbedUsage;
3411
4465
  };
3412
- /**
3413
- * Coordinates key-value memory and vector store for semantic search.
3414
- * All key-value operations delegate to the StateStore.
3415
- * Vector operations use the configured VectorStore + Embedder.
3416
- */
3417
4466
  declare class MemoryManager {
3418
4467
  private vectorStore?;
3419
4468
  private embedder?;
@@ -3524,6 +4573,18 @@ type WorkflowContextInit = {
3524
4573
  metadata?: Record<string, unknown>;
3525
4574
  config: AxlConfig;
3526
4575
  providerRegistry: ProviderRegistry;
4576
+ /**
4577
+ * Internal: resolve a `provider:model` URI to the runtime's SCOPED provider
4578
+ * facade, so every model call the context makes joins the active accounting
4579
+ * scope and honors its budget. Supplied by `AxlRuntime`; when omitted the
4580
+ * context wraps the registry's raw adapter itself, so a directly constructed
4581
+ * context is instrumented too. The registry stays available for everything
4582
+ * else that needs it.
4583
+ */
4584
+ resolveProvider?: (uri: string) => {
4585
+ provider: Provider;
4586
+ model: string;
4587
+ };
3527
4588
  transcriptionProviderRegistry?: TranscriptionProviderRegistry;
3528
4589
  sessionHistory?: ChatMessage[];
3529
4590
  onTrace?: (event: AxlEvent) => void;
@@ -3629,6 +4690,9 @@ declare class WorkflowContext<TInput = unknown> {
3629
4690
  get totalCost(): number;
3630
4691
  private config;
3631
4692
  private providerRegistry;
4693
+ private readonly resolveProviderUri;
4694
+ /** Fallback facade cache, used only when no runtime resolver was supplied. */
4695
+ private readonly ownScopedProviders;
3632
4696
  private transcriptionProviderRegistry?;
3633
4697
  private sessionHistory;
3634
4698
  private onTrace?;
@@ -4014,6 +5078,29 @@ declare class WorkflowContext<TInput = unknown> {
4014
5078
  * @param options - Optional: schema, routerModel, metadata, retries
4015
5079
  */
4016
5080
  delegate<T = string>(agents: Agent[], prompt: ModelInput, options?: DelegateOptions<T>): Promise<T>;
5081
+ /**
5082
+ * Declare a paid unit of work Axl cannot observe — a third-party API called
5083
+ * from a tool, a vendor SDK, a scraper with per-request billing — so it joins
5084
+ * the run's accounting and its budget.
5085
+ *
5086
+ * Admission is checked BEFORE `fn` runs, so a closed budget refuses the call
5087
+ * with an `AdmissionDeniedError` instead of spending. The operation finalizes
5088
+ * on return, throw or abort; a cost reported before a later throw is kept.
5089
+ * Not reporting a cost marks the run's accounting `incomplete` with reason
5090
+ * `'external_unreported'` — silence is never read as free.
5091
+ *
5092
+ * The amount must be DISJOINT: nested `ctx.ask` and other Axl operations
5093
+ * account for themselves, so including them double-charges.
5094
+ *
5095
+ * ```ts
5096
+ * const rows = await ctx.withExternalOperation({ name: 'vendor-search' }, async (report) => {
5097
+ * const res = await vendor.search(query);
5098
+ * report.setCost(res.billedUsd);
5099
+ * return res.rows;
5100
+ * });
5101
+ * ```
5102
+ */
5103
+ withExternalOperation<T>(descriptor: ExternalOperationDescriptor, fn: (report: ExternalOperationReport) => Promise<T>): Promise<T>;
4017
5104
  /** Transcribe recorded audio through the dedicated transcription registry.
4018
5105
  * This operation never enters the chat history or chat-provider path. */
4019
5106
  transcribe(request: TranscriptionRequest): Promise<Transcript>;
@@ -4267,6 +5354,8 @@ type SessionOptions = {
4267
5354
  /** Model URI to use for summarization (e.g., 'openai:gpt-4o-mini'). Required when summarize is true. */
4268
5355
  summaryModel?: string;
4269
5356
  };
5357
+ /** Whether an identical current session input is sent to the model once. Default: true. */
5358
+ deduplicateInput?: boolean;
4270
5359
  /** Whether to persist session history to the state store. Default: true. */
4271
5360
  persist?: boolean;
4272
5361
  };
@@ -4326,13 +5415,282 @@ declare class Session {
4326
5415
  }): Promise<Session>;
4327
5416
  }
4328
5417
 
5418
+ /**
5419
+ * Opt-in request capture — "what did we actually submit on the turn after the
5420
+ * model's output was rejected?"
5421
+ *
5422
+ * Capture is a **diagnostics** rail, strictly subordinate to the accounting
5423
+ * rail in `accounting.ts`. Nothing here may change what a run costs, whether it
5424
+ * succeeds, or how long a provider call takes:
5425
+ *
5426
+ * - Records are built at the scoped provider facade, by value, BEFORE the
5427
+ * adapter can mutate the request. A caller mutating its own `messages` array
5428
+ * afterwards cannot change what was recorded.
5429
+ * - Every sink write is fire-and-forget into a byte-bounded queue. A sink that
5430
+ * blocks forever exhausts the queue and capture stops; the provider call is
5431
+ * never awaited on it.
5432
+ * - Any failure — a throwing sink, an exhausted bound, an unserializable
5433
+ * payload — degrades the manifest's `status`/`reason` and nothing else.
5434
+ *
5435
+ * Privacy is enforced by projection, not by filtering: only an allowlist of
5436
+ * request options is serialized, `providerOptions` contributes its KEYS only,
5437
+ * and rich media parts become bounded descriptors. Signals, functions and
5438
+ * credentials never enter a record in the first place. When redaction is
5439
+ * configured the projected record additionally passes through
5440
+ * `redactCapturedRequest()` before it reaches the sink.
5441
+ */
5442
+
5443
+ /** One conversation message as captured: text kept, rich media described. */
5444
+ type CapturedMessage = {
5445
+ role: string;
5446
+ /** Text content verbatim, or `null` when the message carried only rich parts. */
5447
+ content: string | null;
5448
+ /** Bounded descriptor for the non-text parts, when there were any. */
5449
+ input?: ModelInputDescriptor;
5450
+ name?: string;
5451
+ tool_calls?: ToolCallMessage[];
5452
+ tool_call_id?: string;
5453
+ };
5454
+ /** The provider-neutral request, snapshotted by value at invocation. */
5455
+ type CapturedRequest = {
5456
+ messages: CapturedMessage[];
5457
+ /** Tool definitions as the model sees them: names + JSON-Schema parameters. */
5458
+ tools?: Array<{
5459
+ name: string;
5460
+ description?: string;
5461
+ parameters: unknown;
5462
+ }>;
5463
+ responseFormat?: ResponseFormat;
5464
+ /** Allowlisted call options only. Never signals, functions or credentials. */
5465
+ options: {
5466
+ model: string;
5467
+ temperature?: number;
5468
+ maxTokens?: number;
5469
+ effort?: string;
5470
+ thinkingBudget?: number;
5471
+ includeThoughts?: boolean;
5472
+ toolChoice?: unknown;
5473
+ stop?: string[];
5474
+ promptCache?: boolean;
5475
+ };
5476
+ /** `providerOptions` KEYS only — the values can carry credentials. */
5477
+ providerOptionKeys?: string[];
5478
+ };
5479
+ /** What came back, projected to the fields that explain a turn. */
5480
+ type CapturedResponse = {
5481
+ content: string;
5482
+ tool_calls?: ToolCallMessage[];
5483
+ usage?: ProviderResponse['usage'];
5484
+ cost?: number;
5485
+ costProvenance?: string;
5486
+ timing?: ProviderResponse['timing'];
5487
+ };
5488
+ /** What went wrong, projected to identity rather than a serialized error. */
5489
+ type CapturedError = {
5490
+ name?: string;
5491
+ message: string;
5492
+ code?: string;
5493
+ status?: number;
5494
+ };
5495
+ /** The gate that rejected the previous turn and the correction fed back in. */
5496
+ type CapturedCorrection = {
5497
+ stage: 'schema' | 'validate' | 'guardrail';
5498
+ reason?: string;
5499
+ /** The exact text appended to the conversation for the next turn. */
5500
+ feedbackMessage: string;
5501
+ };
5502
+ /** Which part of an operation's life a record describes. */
5503
+ type CapturedPhase = 'start' | 'attempt' | 'end';
5504
+ /**
5505
+ * One JSONL line. `v: 1` is the codec version; `@axlsdk/eval` re-exports this
5506
+ * as `RequestRecord` and owns the bundle/import validation around it.
5507
+ *
5508
+ * A `start` record carries the request; `attempt` records mark additional
5509
+ * transport attempts for the SAME logical operation (a 429 retry is not an
5510
+ * output repair); an `end` record carries the response or the error. An
5511
+ * operation that never returns therefore leaves a start record with no end —
5512
+ * which is exactly the call you most want to inspect.
5513
+ */
5514
+ type CapturedRequestRecord = {
5515
+ v: 1;
5516
+ phase: CapturedPhase;
5517
+ operationId: string;
5518
+ kind: 'chat' | 'stream';
5519
+ caseIndex?: number;
5520
+ scorer?: string;
5521
+ executionId?: string;
5522
+ askId?: string;
5523
+ parentAskId?: string;
5524
+ turn?: number;
5525
+ retryReason?: 'schema' | 'validate' | 'guardrail';
5526
+ /** 1-indexed transport attempt this record describes. */
5527
+ transportAttempts: number;
5528
+ provider: string;
5529
+ model: string;
5530
+ request?: CapturedRequest;
5531
+ response?: CapturedResponse;
5532
+ error?: CapturedError;
5533
+ /**
5534
+ * Why an operation ended without a response, when it ended deliberately.
5535
+ *
5536
+ * A stream closed by a stall timeout, a consumer `break`, or an adapter that
5537
+ * simply stopped yielding is NOT a call that never came back. Without this
5538
+ * the two are indistinguishable, and `start_only` — the status reserved for a
5539
+ * genuinely hung call — stops discriminating.
5540
+ */
5541
+ termination?: string;
5542
+ correction?: CapturedCorrection;
5543
+ captured: {
5544
+ fidelity: 'runtime_request';
5545
+ redacted: boolean;
5546
+ /** `true` when the record was replaced by a stub over `maxRecordBytes`. */
5547
+ truncated: boolean;
5548
+ /** What could not be represented, e.g. `['media']`. */
5549
+ omitted: string[];
5550
+ /**
5551
+ * Why this record is a stub, when it is one.
5552
+ *
5553
+ * A stub has two causes and a reader has to be able to tell them apart:
5554
+ * the record exceeded `maxRecordBytes` (its size is on `bytes`), or the
5555
+ * request/response could not be projected at all — a `json_schema.schema`
5556
+ * holding a function, a content part no adapter names. Both keep the
5557
+ * operation's identity; neither is a call that failed.
5558
+ */
5559
+ reason?: string;
5560
+ };
5561
+ /** Encoded size of the record this stub replaced. Present only on stubs. */
5562
+ bytes?: number;
5563
+ };
5564
+ /** A pointer from an eval item / scorer to one captured operation. */
5565
+ type CapturedOperationRef = {
5566
+ operationId: string;
5567
+ kind: 'chat' | 'stream';
5568
+ turn?: number;
5569
+ attempt?: number;
5570
+ status: 'recorded' | 'start_only' | 'truncated' | 'omitted';
5571
+ };
5572
+ /** Where encoded record lines go. One call per JSONL line, already bounded. */
5573
+ type RequestCaptureSink = {
5574
+ append(line: string): Promise<void>;
5575
+ };
5576
+ /** Per-run capture bounds. All finite positive byte counts. */
5577
+ type RequestCaptureOptions = {
5578
+ sink: RequestCaptureSink;
5579
+ /** Encoded bytes above which a record is replaced by a stub. Default 256 KiB. */
5580
+ maxRecordBytes?: number;
5581
+ /** Encoded bytes above which capture stops for the run. Default 16 MiB. */
5582
+ maxRunBytes?: number;
5583
+ /** Pending unflushed bytes above which capture stops. Default 1 MiB. */
5584
+ maxQueueBytes?: number;
5585
+ /** Apply `redactCapturedRequest` before every write. */
5586
+ redact?: boolean;
5587
+ /** Bounded wait for the queue to drain at close. Default 5s. */
5588
+ flushTimeoutMs?: number;
5589
+ /**
5590
+ * Bytes the artifact this channel writes into ALREADY holds.
5591
+ *
5592
+ * A rescore opens its channel over an artifact seeded with its source run's
5593
+ * copied records. `maxRunBytes` is a promise about how large the artifact
5594
+ * gets, not about this channel's share of it, so the carried bytes count
5595
+ * against the same bound.
5596
+ */
5597
+ carriedBytes?: number;
5598
+ };
5599
+ /** Eval-side correlation stamped onto every record produced inside a scope. */
5600
+ type CaptureCorrelation = {
5601
+ caseIndex?: number;
5602
+ scorer?: string;
5603
+ };
5604
+ /** What a finished capture channel has to say about itself. */
5605
+ type RequestCaptureStatus = {
5606
+ status: 'complete' | 'truncated' | 'unavailable';
5607
+ reason?: string;
5608
+ records: number;
5609
+ bytes: number;
5610
+ redaction: 'applied' | 'none';
5611
+ };
5612
+ declare const DEFAULT_MAX_RECORD_BYTES: number;
5613
+ declare const DEFAULT_MAX_RUN_BYTES: number;
5614
+ declare const DEFAULT_MAX_QUEUE_BYTES: number;
5615
+ /**
5616
+ * The bounded, non-blocking pipeline between the facade and a sink.
5617
+ *
5618
+ * One channel per capturing run. It owns the run's byte budget, the pending
5619
+ * queue, and the ledger of which operations produced which records — the
5620
+ * ledger is what lets an eval item point at its own operations without the
5621
+ * runner having to parse the JSONL back.
5622
+ */
5623
+ declare class RequestCaptureChannel {
5624
+ private readonly sink;
5625
+ private readonly maxRecordBytes;
5626
+ private readonly maxRunBytes;
5627
+ private readonly maxQueueBytes;
5628
+ private readonly flushTimeoutMs;
5629
+ readonly redact: boolean;
5630
+ private runBytes;
5631
+ private queueBytes;
5632
+ private records;
5633
+ private stopped;
5634
+ private status;
5635
+ private reason;
5636
+ private drain;
5637
+ private stoppedSignal;
5638
+ private readonly stoppedPromise;
5639
+ /** operationId → the ref an eval item/scorer will point at. */
5640
+ private readonly refs;
5641
+ constructor(options: RequestCaptureOptions);
5642
+ /**
5643
+ * Report a CHANNEL-WIDE capture failure from outside the channel.
5644
+ *
5645
+ * Terminal for the run, so it is reserved for a loss that really is
5646
+ * run-wide: a sink that cannot be written to, a host producer that has lost
5647
+ * its ability to record anything further. A single record that could not be
5648
+ * projected — a `json_schema.schema` holding a function, a content part no
5649
+ * adapter names — is NOT this: it is the truncation case, and the provider
5650
+ * path stubs that one record and keeps capturing. Calling `fail()` for it
5651
+ * would throw away every later record in the run.
5652
+ */
5653
+ fail(reason: string): void;
5654
+ /** Stop capturing for the rest of the run and remember why. */
5655
+ private stop;
5656
+ /**
5657
+ * Enqueue one record. Synchronous and total: it returns immediately whatever
5658
+ * the sink is doing, and it never throws into the provider call path.
5659
+ */
5660
+ write(record: CapturedRequestRecord): void;
5661
+ /** Register (or upgrade) the ref an eval item will point at. */
5662
+ noteOperation(ref: CapturedOperationRef & {
5663
+ caseIndex?: number;
5664
+ scorer?: string;
5665
+ }): void;
5666
+ /** Every operation this run captured, with the correlation it was seen under. */
5667
+ operations(): Array<CapturedOperationRef & {
5668
+ caseIndex?: number;
5669
+ scorer?: string;
5670
+ }>;
5671
+ /**
5672
+ * Wait for the queue to drain (bounded) and report the run's capture status.
5673
+ *
5674
+ * Called after the tracked function has already returned, so it can never
5675
+ * delay a provider call. It is additionally bounded three ways: a stopped
5676
+ * channel abandons its queue immediately, the queue itself is byte-bounded,
5677
+ * and a flush timeout is the final backstop against a sink that never
5678
+ * settles.
5679
+ */
5680
+ close(): Promise<RequestCaptureStatus>;
5681
+ }
5682
+
4329
5683
  /**
4330
5684
  * Function exported from an eval module that overrides the default
4331
5685
  * `runtime.execute(workflow, input)` behavior. The runtime injects itself as
4332
5686
  * the second argument so user code can call `runtime.createContext()` etc.
4333
5687
  *
4334
- * Returns `{ output, cost?, metadata? }` — cost and metadata are optional and
4335
- * fall back to values derived from `runtime.trackExecution()` when omitted.
5688
+ * Returns `{ output, cost?, metadata? }`. Both extras are optional and purely
5689
+ * informational: since 0.24 the eval runner MEASURES spend on the accounting
5690
+ * rail, so a `cost` reported here is recorded as a caller claim
5691
+ * (`EvalItem.callerReport`) and never becomes a run total. Reserved diagnostic
5692
+ * metadata keys (`models`, `tokens`, …) are likewise kept apart from the
5693
+ * runtime's own.
4336
5694
  *
4337
5695
  * Single source of truth for the eval-execution contract; consumed by
4338
5696
  * `registerEval`, `getRegisteredEval`, the `axl-eval` CLI, and
@@ -4397,6 +5755,8 @@ type RuntimeEvalConfigShape = {
4397
5755
  scorerConcurrency?: number;
4398
5756
  budget?: string;
4399
5757
  failOnScorerErrorRate?: number;
5758
+ /** Mirrors `EvalConfig.failOnItemErrorRate` (default `0.05`; `1` disables the gate). */
5759
+ failOnItemErrorRate?: number;
4400
5760
  metadata?: Record<string, unknown>;
4401
5761
  };
4402
5762
  type CreateContextOptions = {
@@ -4414,6 +5774,143 @@ type CreateContextOptions = {
4414
5774
  * 'drop-oldest-non-terminal'`. */
4415
5775
  events?: EventStreamOptions;
4416
5776
  };
5777
+ /**
5778
+ * Event-derived execution metadata returned by {@link AxlRuntime.trackExecution}
5779
+ * and {@link AxlRuntime.trackOutcome}.
5780
+ *
5781
+ * Derived from trace events, so it follows trace configuration — unlike
5782
+ * `Accounting`, which is independent of it.
5783
+ */
5784
+ type TrackExecutionMetadata = {
5785
+ /** Unique effective model URIs observed, in first-seen order. */
5786
+ models: string[];
5787
+ modelCallCounts?: Record<string, number>;
5788
+ /**
5789
+ * Agent token totals only — embedder tokens from semantic memory are a
5790
+ * different category and are excluded. Read `Accounting.usage` for the
5791
+ * folded, category-separated totals.
5792
+ */
5793
+ tokens: {
5794
+ input: number;
5795
+ output: number;
5796
+ reasoning: number;
5797
+ };
5798
+ agentCalls: number;
5799
+ /**
5800
+ * Unique workflow names observed during execution, ordered by first
5801
+ * appearance (outermost first for nested calls). Captured automatically
5802
+ * from `workflow_start` trace events — callers don't need to declare
5803
+ * anything. Parallel mechanism to `models`.
5804
+ */
5805
+ workflows: string[];
5806
+ /** Call counts per workflow, if workflows.length > 0. */
5807
+ workflowCallCounts?: Record<string, number>;
5808
+ };
5809
+ /**
5810
+ * Per-model sums of `agent_call_end.timing`, keyed by the same effective
5811
+ * model URI as `TrackExecutionMetadata.modelCallCounts`. Present only when at
5812
+ * least one tracked call reported timing, so absence means "nothing was
5813
+ * instrumented" rather than "everything took zero ms".
5814
+ *
5815
+ * `calls` counts the SUCCESSFUL timed calls only — it can be lower than the
5816
+ * same model's `modelCallCounts` entry when a provider omits `timing` or a
5817
+ * call failed. Divide a sum by `calls`, never by `modelCallCounts`.
5818
+ *
5819
+ * Failed calls are excluded even though they now report timing: a non-2xx
5820
+ * response is a measured round trip, but a rollup blending answers with
5821
+ * failures describes neither, and a fast 429 would improve a model's
5822
+ * apparent latency. The failures stay on the events themselves.
5823
+ *
5824
+ * `firstTokenMs` is streaming-only and is summed across the calls that
5825
+ * reported it; it is omitted entirely when no call did, so a non-streaming
5826
+ * model never reports a misleading `0`. Its denominator is `firstTokenCalls`,
5827
+ * NOT `calls` — the two travel together and are present or absent together,
5828
+ * so a mixed streaming/non-streaming model still yields an exact mean.
5829
+ *
5830
+ * Sums are per-call totals, so `wireMs` across concurrent calls can exceed
5831
+ * the wall clock of the tracked function — that is expected under fan-out and
5832
+ * is why nothing in the core sums timing at the ask level.
5833
+ */
5834
+ type ModelTimingRollup = Record<string, {
5835
+ calls: number;
5836
+ queuedMs: number;
5837
+ retryMs: number;
5838
+ wireMs: number;
5839
+ firstTokenMs?: number;
5840
+ /** Timed calls that reported a `firstTokenMs` — the denominator for it. */
5841
+ firstTokenCalls?: number;
5842
+ /**
5843
+ * The per-call blocks the sums were built from, in event order.
5844
+ * `length === calls`. Present only under `captureTimingSamples`, because
5845
+ * retaining it is O(calls) in memory and the sums alone are O(models) — a
5846
+ * caller that needs a real DISTRIBUTION (percentiles, min/max) cannot
5847
+ * recover one from sums, and everyone else should not pay for it.
5848
+ */
5849
+ samples?: CallTiming[];
5850
+ }>;
5851
+ /** Options for {@link AxlRuntime.trackOutcome}. */
5852
+ type TrackOutcomeOptions = {
5853
+ /**
5854
+ * How this scope's operations are classified in `Accounting.breakdown`.
5855
+ * Defaults to the enclosing scope's purpose, else `'generation'`.
5856
+ */
5857
+ purpose?: OperationPurpose;
5858
+ /** Stop admitting new paid operations once known spend reaches its limit. */
5859
+ admission?: AdmissionController;
5860
+ /** Collect the raw `AxlEvent[]` observed during the call. */
5861
+ captureTraces?: boolean;
5862
+ /** Retain the per-call `CallTiming` blocks behind the `modelTiming` sums. */
5863
+ captureTimingSamples?: boolean;
5864
+ /**
5865
+ * Opt in to bounded, redaction-aware capture of the provider-neutral REQUESTS
5866
+ * this scope submits, written into `sink` as JSONL.
5867
+ *
5868
+ * Diagnostics only: capture can be truncated, fail outright, or be turned off
5869
+ * entirely and `accounting` is byte-identical either way. The sink is never
5870
+ * awaited by a provider call. Nested scopes inherit the enclosing channel, so
5871
+ * declare it once at the outermost scope of a run.
5872
+ *
5873
+ * The CALLER owns the channel because the caller is the one that has to
5874
+ * `close()` it and read back its operation ledger after the run.
5875
+ */
5876
+ capture?: RequestCaptureChannel;
5877
+ /**
5878
+ * Stamp consumer-side identity onto every record captured inside this scope
5879
+ * (an eval's `caseIndex`, a judge's `scorer` name). Merged over the enclosing
5880
+ * scope's correlation, so a scorer scope keeps its item's case index.
5881
+ */
5882
+ captureCorrelation?: CaptureCorrelation;
5883
+ };
5884
+ /** The result of {@link AxlRuntime.trackOutcome} — never a rejection. */
5885
+ type TrackedOutcome<T> = ({
5886
+ status: 'fulfilled';
5887
+ value: T;
5888
+ }
5889
+ /** The ORIGINAL thrown value: primitives and frozen objects are untouched. */
5890
+ | {
5891
+ status: 'rejected';
5892
+ error: unknown;
5893
+ }) & {
5894
+ /** Authoritative spend. Independent of trace level, capture and redaction. */
5895
+ accounting: Accounting;
5896
+ metadata: TrackExecutionMetadata;
5897
+ modelTiming?: ModelTimingRollup;
5898
+ traces?: AxlEvent[];
5899
+ };
5900
+ /** The result of {@link AxlRuntime.trackExecution}. */
5901
+ type TrackExecutionResult<T> = {
5902
+ result: T;
5903
+ /** Compatibility view of `accounting.knownCost`. */
5904
+ cost: number;
5905
+ /** Compatibility view of `accounting.completeness !== 'complete'`. When true,
5906
+ * `cost` is a LOWER BOUND. */
5907
+ unpriced: boolean;
5908
+ /** The authoritative record `cost` and `unpriced` are derived from. */
5909
+ accounting: Accounting;
5910
+ modelTiming?: ModelTimingRollup;
5911
+ traces?: AxlEvent[];
5912
+ metadata: TrackExecutionMetadata;
5913
+ };
4417
5914
  /**
4418
5915
  * The main entry point for executing Axl workflows.
4419
5916
  * Manages workflow registration, provider resolution, state storage, tracing, MCP servers,
@@ -4426,6 +5923,9 @@ declare class AxlRuntime extends EventEmitter {
4426
5923
  private tools;
4427
5924
  private agents;
4428
5925
  private providerRegistry;
5926
+ /** Raw adapter → its scoped facade. Keyed weakly so a registry that drops an
5927
+ * adapter does not pin it here. */
5928
+ private scopedProviders;
4429
5929
  private transcriptionProviderRegistry;
4430
5930
  private stateStore;
4431
5931
  private executions;
@@ -4478,7 +5978,181 @@ declare class AxlRuntime extends EventEmitter {
4478
5978
  * The Set is mutated by the chain's own `.finally` so entries clear as
4479
5979
  * soon as the work settles. */
4480
5980
  private persistInflight;
5981
+ /** Resolved diagnostic artifact backend, when `diagnostics.artifacts` is set. */
5982
+ private artifactStore?;
5983
+ private artifactLeaseMs;
5984
+ private artifactMaxHoldMs;
5985
+ private artifactSweepTimer?;
5986
+ /**
5987
+ * Eval history ids this process has established are gone.
5988
+ *
5989
+ * Two jobs. It makes a delete started here beat a correction already in
5990
+ * flight, which no store-side condition can do while the delete has not
5991
+ * landed yet. And it keeps the lazy first load from merging a row back in
5992
+ * from a snapshot taken before the eviction. Ids are minted per result and
5993
+ * never reused, so an entry can only ever be right.
5994
+ *
5995
+ * Bounded like the artifact store's deleted-id window, and for the same
5996
+ * reason: a long-lived server with TTL churn would otherwise grow it for the
5997
+ * life of the process. Both jobs are windows measured in the length of one
5998
+ * in-flight write or one lazy first load, so the oldest ids are the ones with
5999
+ * nothing left to protect. Past the window the row is simply re-read from the
6000
+ * store, which by then is the authority anyway.
6001
+ */
6002
+ private readonly deletedEvalIds;
6003
+ /** Lease-renewal timers for artifacts currently being written, by id. */
6004
+ private readonly artifactRenewals;
4481
6005
  constructor(config?: AxlConfig);
6006
+ /**
6007
+ * Resolve the artifact backend and arm reclamation.
6008
+ *
6009
+ * Two configuration errors are raised HERE rather than at capture time,
6010
+ * because both mean "this deployment can never host managed capture" and the
6011
+ * alternative is discovering it after an eval has already spent money:
6012
+ * a `diagnostics.artifacts` block with neither `store` nor `root`, and a
6013
+ * custom `StateStore` that cannot report eval retention.
6014
+ */
6015
+ private setUpDiagnosticArtifacts;
6016
+ /** The configured artifact backend, or `undefined` when capture is off. */
6017
+ getDiagnosticArtifactStore(): DiagnosticArtifactStore | undefined;
6018
+ private requireArtifactStore;
6019
+ /**
6020
+ * Reserve an artifact for `owner` and hand back a sink to write records into.
6021
+ *
6022
+ * Throws `AxlError('DIAGNOSTICS_UNAVAILABLE')` BEFORE any work when capture is
6023
+ * not configured, which is why `runEval` calls it ahead of loading the
6024
+ * dataset. The returned sink renews the staging lease as it writes, so a long
6025
+ * run is never swept out from under itself, while a run that dies leaves a
6026
+ * lease that expires and is reclaimed.
6027
+ */
6028
+ stageDiagnosticArtifact(owner: ArtifactOwner): Promise<{
6029
+ artifactId: string;
6030
+ sink: RequestCaptureSink;
6031
+ }>;
6032
+ /**
6033
+ * Hold a staged artifact's lease for as long as its writer lives, and hand
6034
+ * back the sink that writer appends through.
6035
+ *
6036
+ * The lease belongs to the WRITER's lifetime, not to its write rate. Renewing
6037
+ * from inside `append` looks equivalent and is not: a run that exhausts
6038
+ * `maxRunBytes` at minute two stops appending forever, and a run waiting on a
6039
+ * tool or a human approval may not call a provider for longer than a lease.
6040
+ * Either way the sweeper would delete a live run's evidence, and the run
6041
+ * would then fail to finalize the artifact it had been filling.
6042
+ */
6043
+ private holdStagedArtifact;
6044
+ /** Stop renewing a lease once the writer is done with the artifact. */
6045
+ private stopArtifactRenewal;
6046
+ /** Seal a staged artifact's manifest. Called before the history row is saved. */
6047
+ finalizeDiagnosticArtifact(artifactId: string, status: ArtifactStatus, reason?: string, redaction?: 'applied' | 'none'): Promise<ArtifactManifest>;
6048
+ /** Discard a staged artifact whose owner will never be written. */
6049
+ rollbackDiagnosticArtifact(artifactId: string): Promise<void>;
6050
+ /**
6051
+ * Copy a source run's artifact into a new owner (rescore provenance).
6052
+ *
6053
+ * Bounded by `maxBytes`; a partial copy reports `truncated` so the caller can
6054
+ * finalize the new manifest honestly. Returns `undefined` when the source is
6055
+ * gone — a rescore of a run whose artifact was deleted still produces numbers,
6056
+ * it just reports its diagnostics as `unavailable`.
6057
+ *
6058
+ * The copy comes back STAGED and lease-held, with a sink: the new owner
6059
+ * carries the source records forward AND keeps writing its own calls into the
6060
+ * same artifact, which is what makes one rescore result hold both halves of
6061
+ * its evidence.
6062
+ */
6063
+ copyDiagnosticArtifact(sourceId: string, owner: ArtifactOwner, opts: {
6064
+ maxBytes: number;
6065
+ }): Promise<{
6066
+ artifactId: string;
6067
+ truncated: boolean;
6068
+ bytes: number;
6069
+ /** What the SOURCE manifest said about the copied bytes. Never inferred. */
6070
+ redaction: 'applied' | 'none';
6071
+ sink: RequestCaptureSink;
6072
+ } | undefined>;
6073
+ /**
6074
+ * Read a committed artifact's records, or `undefined`.
6075
+ *
6076
+ * Two logical checks stand between a caller and the bytes, because the bytes
6077
+ * outlive the store that governs them: the owning history row must still
6078
+ * exist, and the mirrored `expiresAt` must not have passed. A Redis row that
6079
+ * aged out while this process was offline therefore reads as unavailable the
6080
+ * moment it is asked for, not whenever the sweeper next runs.
6081
+ */
6082
+ openDiagnosticArtifact(artifactId: string): Promise<OpenedArtifact | undefined>;
6083
+ /**
6084
+ * Reclaim artifacts nothing can legitimately read any more.
6085
+ *
6086
+ * Removed: everything marked `delete_pending` (a delete that did not finish),
6087
+ * every `committed` artifact whose owner row is gone or whose expiry has
6088
+ * passed, and every `staged` artifact whose lease ran out. A staged artifact
6089
+ * with a LIVE lease is never touched — that is an active writer, and sweeping
6090
+ * it would race a run that is still producing evidence.
6091
+ */
6092
+ reconcileDiagnosticArtifacts(): Promise<{
6093
+ removed: string[];
6094
+ }>;
6095
+ /**
6096
+ * Rewrite the history row that owned an artifact the sweep has just removed.
6097
+ *
6098
+ * Best effort by design: reclaiming the bytes is the operation that had to
6099
+ * succeed, and a state store that cannot take the correction must not turn a
6100
+ * routine sweep into a throwing one. The next `saveEvalResult` or artifact
6101
+ * read corrects the row anyway, because both already downgrade a dangling id.
6102
+ */
6103
+ private downgradeReclaimedOwner;
6104
+ /**
6105
+ * Write back a history row this process corrected in memory — but ONLY while
6106
+ * the store still holds it.
6107
+ *
6108
+ * The history cache is process-global, loaded once and evicted only by
6109
+ * `deleteEvalResult`. It therefore outlives whatever the STORE decided: a
6110
+ * Redis TTL that elapsed, a delete by another process, a
6111
+ * right-to-be-forgotten request. A blind `saveEvalResult` of a cached row
6112
+ * resurrects all of it — item inputs, outputs and scores — and on Redis
6113
+ * re-SETs it with a fresh full TTL window, silently extending the retention
6114
+ * the operator configured. Correcting a row must never be able to un-delete
6115
+ * one, so the store is asked first and the stale cache entry is dropped when
6116
+ * the answer is no.
6117
+ *
6118
+ * A store with no retention view cannot answer, and a blind write is exactly
6119
+ * what this exists to prevent, so nothing is written: the cache is corrected
6120
+ * and the store keeps what it decided. In practice the question does not
6121
+ * arise, because configuring artifacts at all requires `getEvalRetention`.
6122
+ */
6123
+ private persistCorrectedRow;
6124
+ /**
6125
+ * Drop a row from the history cache and refuse to re-add it.
6126
+ *
6127
+ * The cache is process-global and loaded once, so a row the STORE has
6128
+ * dropped — expired, deleted here or elsewhere — would otherwise be served
6129
+ * for the life of the process, and an in-flight first load whose snapshot
6130
+ * predates the eviction would merge it straight back in.
6131
+ */
6132
+ private forgetEvalRow;
6133
+ /** The artifact id an eval result carries, when it carries one. */
6134
+ private artifactIdOf;
6135
+ /**
6136
+ * Rewrite an entry's diagnostics to say plainly that its evidence is gone.
6137
+ *
6138
+ * Mutates the entry in place on purpose: the caller usually holds the same
6139
+ * `EvalResult` object it is about to return to a user, and a result that
6140
+ * claims `complete` while storage has nothing is the failure being fixed.
6141
+ * The dangling id is cleared too — `''` is the sentinel every lifecycle path
6142
+ * already skips.
6143
+ */
6144
+ private downgradeDiagnostics;
6145
+ /**
6146
+ * The artifact id `ownerId` actually owns, or `undefined`.
6147
+ *
6148
+ * `diagnostics.artifactId` is data on a result object, and a result can carry
6149
+ * an id that belongs to a DIFFERENT run — a rescore that degraded while
6150
+ * naming its source, or an imported blob. Committing or deleting on the
6151
+ * strength of that field alone lets one entry rewrite or destroy another
6152
+ * entry's evidence, so ownership is confirmed against the manifest before
6153
+ * any lifecycle write.
6154
+ */
6155
+ private ownedArtifactId;
4482
6156
  /**
4483
6157
  * Whether `config.trace.redact` is enabled on this runtime. A narrow
4484
6158
  * boolean getter is preferred over exposing the full config because:
@@ -4594,12 +6268,22 @@ declare class AxlRuntime extends EventEmitter {
4594
6268
  signal?: AbortSignal;
4595
6269
  /**
4596
6270
  * When `true`, populate `EvalItem.traces` on every item (success + failure
4597
- * paths). Forwards to `runEval({ captureTraces: true })`, which wraps each
4598
- * item's execution in `runtime.trackExecution({ captureTraces: true })`.
6271
+ * paths). Forwards to `runEval({ captureTraces: true })`, which opens each
6272
+ * item's accounting scope with `trackOutcome({ captureTraces: true })`.
4599
6273
  * Verbose-mode `agent_call_start.data.messages` snapshots are stripped from
4600
6274
  * captured traces to keep memory bounded.
4601
6275
  */
4602
6276
  captureTraces?: boolean;
6277
+ /**
6278
+ * Forwarded to `runEval({ captureRequests })`. Requires
6279
+ * `config.diagnostics.artifacts`; without it the run fails fast with
6280
+ * `AxlError('DIAGNOSTICS_UNAVAILABLE')` before any provider call.
6281
+ */
6282
+ captureRequests?: boolean | {
6283
+ maxRecordBytes?: number;
6284
+ maxRunBytes?: number;
6285
+ maxQueueBytes?: number;
6286
+ };
4603
6287
  }): Promise<unknown>;
4604
6288
  /** Get all execution info (running + completed + historical). */
4605
6289
  /** Normalize an execution loaded from a `StateStore` so the
@@ -4621,11 +6305,26 @@ declare class AxlRuntime extends EventEmitter {
4621
6305
  registerProvider(name: string, provider: Provider): void;
4622
6306
  /** Register a dedicated transcription adapter. It is never used for chat. */
4623
6307
  registerTranscriptionProvider(name: string, provider: TranscriptionProvider): void;
4624
- /** Resolve a provider:model URI to a Provider instance and model name. */
6308
+ /**
6309
+ * Resolve a `provider:model` URI to a provider and model name.
6310
+ *
6311
+ * **The returned provider is a scoped FACADE, not the registered instance.**
6312
+ * It forwards everything to the adapter — custom properties and accessors,
6313
+ * class private-field methods, property writes, capability methods, ordinary
6314
+ * `instanceof` — while routing `chat`/`stream` through accounting and budget
6315
+ * admission whenever a `trackOutcome` scope is active. Outside a scope it
6316
+ * delegates verbatim.
6317
+ *
6318
+ * Identity is stable per runtime per adapter (repeated calls return the same
6319
+ * facade), but `resolveProvider(uri).provider === registeredInstance` is now
6320
+ * `false`. See `docs/migration/eval-accounting.md`.
6321
+ */
4625
6322
  resolveProvider(uri: string): {
4626
6323
  provider: Provider;
4627
6324
  model: string;
4628
6325
  };
6326
+ /** One facade per raw adapter per runtime, so repeated resolution is stable. */
6327
+ private scopedProvider;
4629
6328
  /** Execute a workflow and return the result. */
4630
6329
  execute(name: string, input: unknown, options?: ExecuteOptions): Promise<unknown>;
4631
6330
  /** Execute a workflow and return a stream. */
@@ -4728,6 +6427,28 @@ declare class AxlRuntime extends EventEmitter {
4728
6427
  deleteEvalResult(id: string): Promise<boolean>;
4729
6428
  /** Get eval result history (most recent first). */
4730
6429
  getEvalHistory(): Promise<EvalHistoryEntry[]>;
6430
+ /**
6431
+ * One eval history entry by id, or `undefined`.
6432
+ *
6433
+ * A read that wants one entry should not have to materialize the whole
6434
+ * history to find it. Studio's diagnostics routes resolve an artifact through
6435
+ * a history id on every request, and `getEvalHistory()` copies the entire
6436
+ * array — every result's full `data` blob — before the caller discards all
6437
+ * but one of them.
6438
+ *
6439
+ * The lazy first load is still whole-history (that is how the cache is
6440
+ * populated); what this avoids is paying for a copy of it per request.
6441
+ *
6442
+ * The row is confirmed against the store's retention view before it is
6443
+ * served, and dropped when the store says it is gone. The cache outlives
6444
+ * whatever the store decided — a Redis TTL that elapsed, a delete elsewhere —
6445
+ * and this is the read a rescore resolves its SOURCE through, copying that
6446
+ * row's items into a brand-new result with a brand-new retention window. An
6447
+ * expired run could otherwise be republished indefinitely, one rescore at a
6448
+ * time. `getEvalHistory()` is still served from the cache unconfirmed; the
6449
+ * per-row check is affordable here because it is one row.
6450
+ */
6451
+ getEvalResult(id: string): Promise<EvalHistoryEntry | undefined>;
4731
6452
  /** List pending human decisions. */
4732
6453
  getPendingDecisions(): Promise<PendingDecision[]>;
4733
6454
  /** Resolve a pending human decision. */
@@ -4749,6 +6470,13 @@ declare class AxlRuntime extends EventEmitter {
4749
6470
  onProgress?: (event: EvalProgressEventShape) => void;
4750
6471
  signal?: AbortSignal;
4751
6472
  captureTraces?: boolean;
6473
+ /** Forwarded to `runEval({ captureRequests })`. See
6474
+ * {@link AxlRuntime.runRegisteredEval}. */
6475
+ captureRequests?: boolean | {
6476
+ maxRecordBytes?: number;
6477
+ maxRunBytes?: number;
6478
+ maxQueueBytes?: number;
6479
+ };
4752
6480
  }): Promise<unknown>;
4753
6481
  /**
4754
6482
  * Compare two evaluation results to detect regressions and improvements.
@@ -4770,26 +6498,55 @@ declare class AxlRuntime extends EventEmitter {
4770
6498
  unpriced: boolean;
4771
6499
  }>;
4772
6500
  /**
4773
- * Track cost and execution metadata across any runtime operations within the given function.
4774
- * Uses AsyncLocalStorage to scope attribution to specific execution IDs,
4775
- * making it correct with concurrent calls.
6501
+ * Run `fn` and ALWAYS return its outcome plus the authoritative
6502
+ * {@link Accounting} for everything paid that happened inside it.
6503
+ *
6504
+ * This is the accounting entry point. Unlike {@link trackExecution} it does
6505
+ * not throw on failure — a workflow that threw after a paid call still
6506
+ * returns that call's charge, which is what makes "what did the failed run
6507
+ * cost?" answerable. The thrown value is handed back verbatim in `error`:
6508
+ * primitives, frozen objects, `AbortError`, `ProviderError` and
6509
+ * `BudgetExceededError` all come back `===` what was thrown.
6510
+ *
6511
+ * Scopes nest. An operation settled inside a child `trackOutcome` is counted
6512
+ * exactly once in every enclosing scope, so a parent total is the sum of
6513
+ * disjoint operations. Concurrent `trackOutcome` calls on one runtime are
6514
+ * isolated: neither sees the other's operations, budget closure or spend.
6515
+ *
6516
+ * Pass `{ admission: new AdmissionController({ limit }) }` to stop admitting
6517
+ * new paid operations once known spend reaches the limit. Already-dispatched
6518
+ * work still settles and is still counted.
4776
6519
  *
4777
- * Returns cost (same as `trackCost`) plus metadata extracted from trace events:
4778
- * models (unique URIs), tokens (input/output/reasoning sums), and agent call count.
6520
+ * `metadata`, `modelTiming` and `traces` remain EVENT-derived and therefore
6521
+ * depend on trace configuration; `accounting` never does.
6522
+ */
6523
+ trackOutcome<T>(fn: () => Promise<T>, options?: TrackOutcomeOptions): Promise<TrackedOutcome<T>>;
6524
+ /**
6525
+ * Throwing compatibility wrapper over {@link trackOutcome}.
6526
+ *
6527
+ * Returns the same shape it always has, now derived from the accounting rail
6528
+ * rather than summed from trace events: `cost` is `accounting.knownCost` and
6529
+ * `unpriced` is `accounting.completeness !== 'complete'`. For instrumented
6530
+ * paths the numbers are identical; they differ only where the trace rail used
6531
+ * to lose a charge (notably a leaf that never settled). `accounting` is
6532
+ * exposed alongside them so callers can migrate incrementally.
6533
+ *
6534
+ * On failure it rethrows the ORIGINAL error and, under `captureTraces`,
6535
+ * attaches the non-enumerable `axlCapturedTraces` side channel — prefer
6536
+ * {@link trackOutcome}, which returns the accounting for a failed run too.
4779
6537
  *
4780
6538
  * ## Cost vs tokens semantics
4781
6539
  *
4782
- * - `cost` is the full aggregate across EVERY event with a top-level
4783
- * `event.cost` set: agent calls, tool calls, semantic memory ops, etc.
4784
- * This is the number to reconcile against your provider bill.
6540
+ * - `cost` covers EVERY settled operation: agent calls, tool calls, semantic
6541
+ * memory embeddings, transcriptions, declared external work. This is the
6542
+ * number to reconcile against your provider bill.
4785
6543
  *
4786
6544
  * - `metadata.tokens` is narrowly scoped to **agent** prompt/completion/
4787
6545
  * reasoning tokens. Embedder tokens from semantic `ctx.remember({embed:true})`
4788
6546
  * / `ctx.recall({query})` are deliberately NOT summed here — they're a
4789
6547
  * different category (input-only, different pricing, different model).
4790
- * Conflating them would make "prompt tokens" misleading in the UI. If you
4791
- * need embedder token counts, subscribe to `runtime.on('trace', ...)` and
4792
- * read `data.usage.tokens` on `memory_remember` / `memory_recall` events.
6548
+ * Conflating them would make "prompt tokens" misleading in the UI. Read
6549
+ * `accounting.usage` for the folded, category-separated totals.
4793
6550
  *
4794
6551
  * Pass `{ captureTraces: true }` to also collect the raw `AxlEvent[]` observed
4795
6552
  * during `fn()`. This is opt-in because it keeps every event in memory for the
@@ -4804,86 +6561,7 @@ declare class AxlRuntime extends EventEmitter {
4804
6561
  trackExecution<T>(fn: () => Promise<T>, options?: {
4805
6562
  captureTraces?: boolean;
4806
6563
  captureTimingSamples?: boolean;
4807
- }): Promise<{
4808
- result: T;
4809
- cost: number;
4810
- /** True when any tracked call was unpriced — `cost` is then a LOWER BOUND.
4811
- * Aggregate counterpart of `ExecutionInfo.unpriced`, via `isUnpricedLeaf`. */
4812
- unpriced: boolean;
4813
- /**
4814
- * Per-model sums of `agent_call_end.timing`, keyed by the same effective
4815
- * model URI as `metadata.modelCallCounts`. Present only when at least one
4816
- * tracked call reported timing, so absence means "nothing was instrumented"
4817
- * rather than "everything took zero ms".
4818
- *
4819
- * `calls` counts the SUCCESSFUL timed calls only — it can be lower than the
4820
- * same model's `modelCallCounts` entry when a provider omits `timing` or a
4821
- * call failed. Divide a sum by `calls`, never by `modelCallCounts`.
4822
- *
4823
- * Failed calls are excluded even though they now report timing: a non-2xx
4824
- * response is a measured round trip, but a rollup blending answers with
4825
- * failures describes neither, and a fast 429 would improve a model's
4826
- * apparent latency. The failures stay on the events themselves.
4827
- *
4828
- * `firstTokenMs` is streaming-only and is summed across the calls that
4829
- * reported it; it is omitted entirely when no call did, so a non-streaming
4830
- * model never reports a misleading `0`. Its denominator is `firstTokenCalls`,
4831
- * NOT `calls` — the two travel together and are present or absent together,
4832
- * so a mixed streaming/non-streaming model still yields an exact mean.
4833
- *
4834
- * Sums are per-call totals, so `wireMs` across concurrent calls can exceed
4835
- * the wall clock of `fn` — that is expected under fan-out and is why
4836
- * nothing in the core sums timing at the ask level.
4837
- *
4838
- * `samples` carries the raw per-call `CallTiming` blocks behind the sums, in
4839
- * `agent_call_end` order, so a caller that needs a DISTRIBUTION (percentiles,
4840
- * min/max) rather than a mean can compute one over real per-call values. It
4841
- * is the same data the sums were built from — `samples.length === calls` —
4842
- * and is deliberately not something callers should persist per item: the
4843
- * sums are the compact form, `samples` is the working form.
4844
- *
4845
- * It is **opt-in** via `captureTimingSamples` and the key is absent
4846
- * otherwise. Retaining it is O(calls) in memory for the lifetime of `fn`,
4847
- * which no caller should pay for a figure it never reads — the sums alone
4848
- * are O(models).
4849
- */
4850
- modelTiming?: Record<string, {
4851
- calls: number;
4852
- queuedMs: number;
4853
- retryMs: number;
4854
- wireMs: number;
4855
- firstTokenMs?: number;
4856
- /** Timed calls that reported a `firstTokenMs` — the denominator for it. */
4857
- firstTokenCalls?: number;
4858
- /** The per-call blocks the sums were built from, in event order.
4859
- * `length === calls`. Present only under `captureTimingSamples`. */
4860
- samples?: CallTiming[];
4861
- }>;
4862
- traces?: AxlEvent[];
4863
- metadata: {
4864
- models: string[];
4865
- modelCallCounts?: Record<string, number>;
4866
- /**
4867
- * Agent token totals only — does not include embedder tokens from
4868
- * semantic memory operations. See the method-level JSDoc above.
4869
- */
4870
- tokens: {
4871
- input: number;
4872
- output: number;
4873
- reasoning: number;
4874
- };
4875
- agentCalls: number;
4876
- /**
4877
- * Unique workflow names observed during execution, ordered by first
4878
- * appearance (outermost first for nested calls). Captured automatically
4879
- * from `workflow_start` trace events — callers don't need to declare
4880
- * anything. Parallel mechanism to `models`.
4881
- */
4882
- workflows: string[];
4883
- /** Call counts per workflow, if workflows.length > 0. */
4884
- workflowCallCounts?: Record<string, number>;
4885
- };
4886
- }>;
6564
+ }): Promise<TrackExecutionResult<T>>;
4887
6565
  /** Register an execution ID with the active cost scope for trackCost() attribution. */
4888
6566
  private registerWithCostScope;
4889
6567
  /**
@@ -5514,6 +7192,26 @@ declare const REDACTION_RULES: {
5514
7192
  declare function redactEvent(event: AxlEvent): AxlEvent;
5515
7193
  /** Redact either persisted schema without reinterpreting its lifecycle. */
5516
7194
  declare function redactHistoricalEvent(event: HistoricalAxlEvent): HistoricalAxlEvent;
7195
+ /**
7196
+ * Scrub a captured request record before it reaches a diagnostics sink.
7197
+ *
7198
+ * The same policy `agent_call_start` / `agent_call_end` / the gate events
7199
+ * already apply, expressed over the capture record's shape: prompts, responses,
7200
+ * gate reasons and correction text are user/LLM content and become
7201
+ * `[redacted]`; roles, tool NAMES, JSON-Schema parameters, option scalars,
7202
+ * usage, cost, timing and the `providerOptions` key list are structural and
7203
+ * survive, because a compliance-mode reader still has to be able to tell a
7204
+ * tool-choice bug from a token-budget bug.
7205
+ *
7206
+ * Rich media never had bytes in the record to begin with (the projection in
7207
+ * `diagnostics/capture.ts` replaces them with descriptors), but a descriptor's
7208
+ * `locator` and `label` can name a file or a URL, so they are scrubbed here
7209
+ * exactly as `redactInputDescriptor` scrubs them on the trace rail.
7210
+ *
7211
+ * Pure, like every other rule in this file: the caller decides whether
7212
+ * redaction is on, and this function unconditionally scrubs when called.
7213
+ */
7214
+ declare function redactCapturedRequest(record: CapturedRequestRecord): CapturedRequestRecord;
5517
7215
 
5518
7216
  /**
5519
7217
  * Typed provider errors + shared HTTP-failure classification.
@@ -5665,13 +7363,20 @@ declare class OpenAIResponsesProvider implements Provider {
5665
7363
  nativeStructuredOutputSupport(): 'schema';
5666
7364
  private baseUrl;
5667
7365
  private apiKeySource;
5668
- private governor?;
7366
+ /**
7367
+ * Namespaced so it cannot collide with a member a downstream subclass
7368
+ * declares. A plain property (not `#private` or a WeakMap keyed by `this`)
7369
+ * so a caller's own Proxy around the adapter still reaches it.
7370
+ */
7371
+ private readonly axlRateGovernors;
5669
7372
  constructor(options?: {
5670
7373
  apiKey?: ApiKeySource;
5671
7374
  baseUrl?: string;
5672
7375
  dangerouslyAllowInsecureHttp?: boolean;
5673
7376
  rateLimit?: RateLimitConfig;
5674
7377
  });
7378
+ /** The rate governor for one call to `model`, from the runtime's per-scope pool. */
7379
+ protected governorFor(model: string): ScopeGovernor;
5675
7380
  /** Resolve the API key for one request (supports an expiring-token callback). */
5676
7381
  private resolveKey;
5677
7382
  /** Build request headers from a resolved key (used by both chat and stream). */
@@ -5733,13 +7438,20 @@ declare class AnthropicProvider implements Provider {
5733
7438
  nativeStructuredOutputSupport(): 'unsupported';
5734
7439
  private baseUrl;
5735
7440
  private apiKeySource;
5736
- private governor?;
7441
+ /**
7442
+ * Namespaced so it cannot collide with a member a downstream subclass
7443
+ * declares. A plain property (not `#private` or a WeakMap keyed by `this`)
7444
+ * so a caller's own Proxy around the adapter still reaches it.
7445
+ */
7446
+ private readonly axlRateGovernors;
5737
7447
  constructor(options?: {
5738
7448
  apiKey?: ApiKeySource;
5739
7449
  baseUrl?: string;
5740
7450
  dangerouslyAllowInsecureHttp?: boolean;
5741
7451
  rateLimit?: RateLimitConfig;
5742
7452
  });
7453
+ /** The rate governor for one call to `model`, from the runtime's per-scope pool. */
7454
+ protected governorFor(model: string): ScopeGovernor;
5743
7455
  /** Resolve the API key for one request (supports an expiring-token callback). */
5744
7456
  private resolveKey;
5745
7457
  chat(messages: ChatMessage[], options: ChatOptions): Promise<ProviderResponse>;
@@ -5799,11 +7511,7 @@ declare class AnthropicProvider implements Provider {
5799
7511
  declare class GeminiProvider implements Provider {
5800
7512
  readonly name = "google";
5801
7513
  readonly reportsRequestLifecycle: true;
5802
- inputCapabilities(model: string): {
5803
- image?: {
5804
- sources: readonly InputMediaSource['type'][];
5805
- };
5806
- };
7514
+ inputCapabilities(model: string): InputModalitySupport;
5807
7515
  validateInput(request: ProviderInputValidationRequest): ProviderInputValidationResult;
5808
7516
  /** Gemini accepts a `responseSchema` but `sanitizeSchemaForGemini` strips
5809
7517
  * keywords it doesn't support (`$ref`/`$defs`/`additionalProperties`/…), so a
@@ -5817,13 +7525,20 @@ declare class GeminiProvider implements Provider {
5817
7525
  private baseUrl;
5818
7526
  private apiKeySource;
5819
7527
  private callCounter;
5820
- private governor?;
7528
+ /**
7529
+ * Namespaced so it cannot collide with a member a downstream subclass
7530
+ * declares. A plain property (not `#private` or a WeakMap keyed by `this`)
7531
+ * so a caller's own Proxy around the adapter still reaches it.
7532
+ */
7533
+ private readonly axlRateGovernors;
5821
7534
  constructor(options?: {
5822
7535
  apiKey?: ApiKeySource;
5823
7536
  baseUrl?: string;
5824
7537
  dangerouslyAllowInsecureHttp?: boolean;
5825
7538
  rateLimit?: RateLimitConfig;
5826
7539
  });
7540
+ /** The rate governor for one call to `model`, from the runtime's per-scope pool. */
7541
+ protected governorFor(model: string): ScopeGovernor;
5827
7542
  /** Resolve the API key for one request (supports an expiring-token callback). */
5828
7543
  private resolveKey;
5829
7544
  chat(messages: ChatMessage[], options: ChatOptions): Promise<ProviderResponse>;
@@ -5834,10 +7549,43 @@ declare class GeminiProvider implements Provider {
5834
7549
  private assertSafeInteractionOptions;
5835
7550
  private mapInteractionInput;
5836
7551
  private parseInteractionResponse;
7552
+ /**
7553
+ * Cost for one Interactions call, or `undefined` when any precondition
7554
+ * fails. Streaming and non-streaming share this one body, so identical usage
7555
+ * always produces an identical number on both paths.
7556
+ */
7557
+ private interactionCost;
7558
+ /**
7559
+ * Whether every model-produced step is text. A non-text reply part (image,
7560
+ * audio, inline data) is billed on an output track this adapter carries no
7561
+ * rate for, so the call stays unpriced. Echoed `user_input` and
7562
+ * Axl-authored `function_result` steps are not model output and are ignored.
7563
+ */
7564
+ private isTextOnlyInteractionOutput;
5837
7565
  private parseInteractionSSEStream;
5838
7566
  private assertInteractionTerminalStatus;
5839
7567
  private buildHeaders;
5840
7568
  private extractErrorMessage;
7569
+ /**
7570
+ * Pricing context for the stateless Interactions transport.
7571
+ *
7572
+ * The endpoint reports a per-modality input split, so a rich call IS
7573
+ * priceable — but only from the canonical endpoint, on a standard tier, with
7574
+ * a request whose billing is fully represented by the token totals.
7575
+ */
7576
+ private interactionPricingContext;
7577
+ /**
7578
+ * Whether an Interactions request body's billing is fully represented by the
7579
+ * reported token totals. `providerOptions` merges arbitrary keys into the
7580
+ * body, so anything that can move billing off the local totals — a
7581
+ * server-side cache, a server-executed tool, a residency/region override, a
7582
+ * non-text response modality — makes the call unpriced.
7583
+ *
7584
+ * Input CONTENT is deliberately not inspected: `input_tokens_by_modality` is
7585
+ * the authority on what the input billed as, and it unprices any modality
7586
+ * without a billing category (video, documents, anything new).
7587
+ */
7588
+ private isModeledInteractionRequest;
5841
7589
  private pricingContext;
5842
7590
  private requestModel;
5843
7591
  private assertSafeGemini3Continuation;
@@ -6089,8 +7837,15 @@ declare class MemoryStore implements StateStore {
6089
7837
  listExecutions(limit?: number): Promise<HistoricalExecutionInfo[]>;
6090
7838
  deleteExecution(executionId: string): Promise<boolean>;
6091
7839
  saveEvalResult(entry: EvalHistoryEntry): Promise<void>;
7840
+ /** Update-only write: see `StateStore.updateEvalResult`. */
7841
+ updateEvalResult(entry: EvalHistoryEntry): Promise<boolean>;
6092
7842
  listEvalResults(limit?: number): Promise<EvalHistoryEntry[]>;
6093
7843
  deleteEvalResult(id: string): Promise<boolean>;
7844
+ /** In-memory history never expires on its own — only existence is meaningful. */
7845
+ getEvalRetention(id: string): Promise<{
7846
+ exists: boolean;
7847
+ expiresAt?: number;
7848
+ }>;
6094
7849
  listSessions(): Promise<string[]>;
6095
7850
  private streamingEvents;
6096
7851
  appendStreamingEvents(executionId: string, events: AxlEvent[]): Promise<void>;
@@ -6177,8 +7932,20 @@ declare class SQLiteStore implements StateStore {
6177
7932
  listExecutions(limit?: number): Promise<HistoricalExecutionInfo[]>;
6178
7933
  deleteExecution(executionId: string): Promise<boolean>;
6179
7934
  saveEvalResult(entry: EvalHistoryEntry): Promise<void>;
7935
+ /**
7936
+ * Update-only write: see `StateStore.updateEvalResult`. `UPDATE ... WHERE id`
7937
+ * touches nothing when the row is gone, where `INSERT OR REPLACE` would
7938
+ * resurrect it — and SQLite history has no expiry, so a resurrection here is
7939
+ * permanent.
7940
+ */
7941
+ updateEvalResult(entry: EvalHistoryEntry): Promise<boolean>;
6180
7942
  listEvalResults(limit?: number): Promise<EvalHistoryEntry[]>;
6181
7943
  deleteEvalResult(id: string): Promise<boolean>;
7944
+ /** SQLite history has no automatic expiry — only existence is meaningful. */
7945
+ getEvalRetention(id: string): Promise<{
7946
+ exists: boolean;
7947
+ expiresAt?: number;
7948
+ }>;
6182
7949
  listSessions(): Promise<string[]>;
6183
7950
  saveMemory(scope: string, key: string, value: unknown): Promise<void>;
6184
7951
  getMemory(scope: string, key: string): Promise<unknown | null>;
@@ -6407,9 +8174,70 @@ declare class RedisStore implements StateStore {
6407
8174
  getExecution(executionId: string): Promise<HistoricalExecutionInfo | null>;
6408
8175
  listExecutions(limit?: number): Promise<HistoricalExecutionInfo[]>;
6409
8176
  deleteExecution(executionId: string): Promise<boolean>;
8177
+ /**
8178
+ * Write an eval history row.
8179
+ *
8180
+ * **Re-saving an existing eval result never extends its retention.** Only a
8181
+ * genuinely new key gets the configured window; an existing one keeps the
8182
+ * time it had left. A result is re-saved by corrections that have nothing to
8183
+ * do with the operator's retention policy — the diagnostics sweep rewriting a
8184
+ * row whose artifact it just reclaimed, a commit failure downgrading one —
8185
+ * and a plain `SET ... EX` would renew a full window on every one of them,
8186
+ * silently keeping item inputs, outputs and scores alive indefinitely on a
8187
+ * server that corrects rows often enough.
8188
+ *
8189
+ * The remaining window is read with `PTTL` and re-applied as `PX` rather than
8190
+ * using `KEEPTTL`. Both need an existence check the SET cannot share a
8191
+ * transaction with, so both race a key expiring in between — but the failure
8192
+ * modes differ, and only one is safe: `KEEPTTL` on a key that expired in the
8193
+ * gap recreates it with NO expiry at all, an immortal row holding exactly the
8194
+ * data retention exists to age out. Every `PX` branch leaves a bounded
8195
+ * lifetime, so the race costs at most one window's length, never the window
8196
+ * itself. It also carries no Redis version floor, where `KEEPTTL` needs 6.0.
8197
+ *
8198
+ * The index entries carry no TTL and age out lazily, as with `saveExecution`.
8199
+ */
6410
8200
  saveEvalResult(entry: EvalHistoryEntry): Promise<void>;
8201
+ /**
8202
+ * Update-only write: see `StateStore.updateEvalResult`.
8203
+ *
8204
+ * One `SET key value XX KEEPTTL`, which is atomic and therefore has no
8205
+ * check-then-write window at all: `XX` refuses a key that is not there, so a
8206
+ * delete or an expiry racing the correction wins, and `KEEPTTL` leaves the
8207
+ * remaining lifetime untouched, so the correction carries no retention
8208
+ * decision of its own.
8209
+ *
8210
+ * **Requires Redis 6.0 or newer** (`KEEPTTL`). An older server rejects the
8211
+ * option with a syntax error, which is translated into a
8212
+ * `REDIS_VERSION_UNSUPPORTED` {@link AxlError} naming the floor — a
8213
+ * misconfiguration, not a failed write, and the one thing in `RedisStore`
8214
+ * that needs 6.0. Silently resetting the retention window instead is the one
8215
+ * outcome this write exists to rule out.
8216
+ *
8217
+ * The index members are deliberately not re-added: the row already exists, so
8218
+ * they are already there, and writing them for a key that vanished is exactly
8219
+ * the resurrection this avoids.
8220
+ */
8221
+ updateEvalResult(entry: EvalHistoryEntry): Promise<boolean>;
6411
8222
  listEvalResults(limit?: number): Promise<EvalHistoryEntry[]>;
6412
8223
  deleteEvalResult(id: string): Promise<boolean>;
8224
+ /**
8225
+ * Existence + absolute expiry for one eval history row.
8226
+ *
8227
+ * `PTTL` is the authority here rather than the configured `ttls.evalHistory`:
8228
+ * the row may have been written under a different configuration, re-saved, or
8229
+ * had its TTL cleared out of band, and an artifact swept on a stale
8230
+ * assumption is deleted diagnostic evidence the user still owns.
8231
+ *
8232
+ * Redis expires the row server-side without telling anyone, so the absolute
8233
+ * `expiresAt` returned here is mirrored onto the artifact manifest and the
8234
+ * physical bytes are reclaimed by the next sweep (or by startup reconciliation
8235
+ * if the process was down when the key aged out) — eventual, not synchronous.
8236
+ */
8237
+ getEvalRetention(id: string): Promise<{
8238
+ exists: boolean;
8239
+ expiresAt?: number;
8240
+ }>;
6413
8241
  /**
6414
8242
  * Shared list-by-sorted-set implementation. Uses ZREVRANGEBYSCORE +
6415
8243
  * MGET when the ZSET is populated (post-migration). Falls back to the
@@ -6423,7 +8251,15 @@ declare class RedisStore implements StateStore {
6423
8251
  finalizeStreamingEvents(executionId: string): Promise<void>;
6424
8252
  listStreamingExecutions(): Promise<string[]>;
6425
8253
  getStreamingEvents(executionId: string): Promise<HistoricalAxlEvent[]>;
6426
- /** Close the Redis connection. */
8254
+ /**
8255
+ * Close the Redis connection.
8256
+ *
8257
+ * Idempotent: `runtime.shutdown()` closes the state store it was handed, so
8258
+ * a caller that also closes its own store is doing the ordinary thing and
8259
+ * must not get a rejected promise for it. `MemoryStore.close()` and
8260
+ * better-sqlite3's `close()` are both no-ops the second time; node-redis
8261
+ * instead rejects with "The client is closed", so absorb that here.
8262
+ */
6427
8263
  close(): Promise<void>;
6428
8264
  deleteCheckpoints(executionId: string): Promise<void>;
6429
8265
  }
@@ -6495,4 +8331,4 @@ declare class NoopSpanManager implements SpanManager {
6495
8331
  */
6496
8332
  declare function createSpanManager(config?: TelemetryConfig): Promise<SpanManager>;
6497
8333
 
6498
- export { AXL_EVENT_TYPES, AXL_EVENT_TYPES_V2, AXL_TOOL_LIFECYCLE_TYPES_V2, AZURE_PROFILE, type Agent, type AgentCallEndData, type AgentCallInfo, type AgentCallParams, type AgentCallStartData, type AgentConfig, AnthropicProvider, type AnyWorkflow, type ApiKeySource, type AskOptions, type AskScoped, type AuthHeader, type AwaitHumanData, type AwaitHumanOptions, type AwaitHumanResolvedData, type AxlConfig, AxlError, type AxlEvent, type AxlEventBase, AxlEventBus, type AxlEventOf, type AxlEventType, type AxlEventV2, type AxlEventV2Of, AxlRuntime, AxlStream, BEDROCK_PROFILE, BUILTIN_PROFILES, BudgetExceededError, type BudgetOptions, type BudgetResult, CONFIG_CANDIDATES, COST_BEARING_LEAF_TYPES, type CallTiming, type CapabilityFlags, type ChatMessage, type ChatOptions, type CheckpointEventData, type CoalescedPartialObject, type CreateContextOptions, DEEPSEEK_PROFILE, type DecisionCleanupFailedEvent, type DelegateData, type DelegateOptions, type Effort, type EffortResolution, type EmbedResult, type EmbedUsage, type Embedder, type EvalExecuteWorkflow, type EvalHistoryEntry, type EventStreamOptions, EventStreamOverflowError, type ExecuteOptions, type ExecutionInfo, type ExecutionInfoV1, type ExecutionInfoV2, type ExecutionState, GROQ_PROFILE, GeminiProvider, GeminiTranscriptionProvider, type GuardrailBlockHandler, type GuardrailData, GuardrailError, type GuardrailResult, type GuardrailsConfig, type HandoffDescriptor, type HandoffRecord, type HandoffReturnData, type HandoffStartData, type HistoricalAxlEvent, type HistoricalExecutionInfo, type HumanDecision, InMemoryVectorStore, type InputContentPart, type InputGuardrail, type InputImagePart, type InputMediaSource, type InputModalitySupport, type InputTextPart, InvalidModelInputError, InvalidTranscriptionInputError, LLAMACPP_PROFILE, LMSTUDIO_PROFILE, type LegacyAxlEventV1, type LegacyExecutionInfoV1, MAX_INLINE_MODEL_INPUT_BYTES, MAX_INLINE_TRANSCRIPTION_BYTES, MISTRAL_PROFILE, type MapOptions, MaxTurnsError, McpManager, type McpServer, type McpServerConfig, type McpToolDefinition, type McpToolResult, type MemoryConfig, type MemoryEventData, MemoryManager, MemoryStore, type ModelInput, type ModelInputDescriptor, NoConsensus, NoopSpanManager, OLLAMA_PROFILE, OPENAI_PRICING, OPENAI_PROFILE, OPENROUTER_PROFILE, type ObservationStatus, type OpenAICompatibleOptions, OpenAICompatibleProvider, OpenAIEmbedder, OpenAIProvider, OpenAIResponsesProvider, OpenAITranscriptionProvider, OpenRouterTranscriptionProvider, type OutputGuardrail, type OutputValidator, type PendingDecision, type PerModel, type PricingSource, type PricingTable, type Provider, type ProviderAdapter, type ProviderDiagnosticData, ProviderError, type ProviderInputValidationRequest, type ProviderInputValidationResult, type ProviderProfile, ProviderRegistry, type ProviderResponse, QuorumNotMet, REDACTED, REDACTION_RULES, type RaceOptions, type RateLimitConfig, RateLimiter, type ReasoningCapture, type ReasoningEmit, type ReasoningProfile, type ReasoningRoundTrip, type RecallOptions, type RecallResult, type RecordedAudioSource, RedisStore, type RememberOptions, type RememberResult, type ResolvedThinkingOptions, type Result, type RetryFeedbackHook, type RetryFeedbackInfo, type RetryFeedbackResult, type RetryPolicy, SGLANG_PROFILE, SQLiteStore, type SchemaCheckData, type SchemaDiagnosticData, type SchemaPromptOption, Session, type SessionOptions, type SpanHandle, type SpanManager, type SpawnOptions, SqliteVectorStore, StallTimeoutError, type StateStore, type StreamChunk, type StringDeltaData, type StringStreamEvent, type StringStreamFilter, type TelemetryConfig, ThinkTagScanner, type TimeoutBreakdown, TimeoutError, type Tool, type ToolApprovalData, type ToolArgumentIssue, type ToolCallCancellation, type ToolCallData, type ToolCallEndData, type ToolCallFailure, type ToolCallMessage, type ToolCallOutcome, type ToolCallRejectedData, type ToolCallRejectedEvent, type ToolCallStartData, type ToolCallStartDataV2, type ToolChoice, type ToolConfig, type ToolDeniedData, type ToolEventError, ToolFailure, type ToolFailureConstructor, type ToolFailureOptions, type ToolHooks, type ToolLifecycleEventV2, type ToolModelOutput, ToolModelOutputError, type Transcript, type TranscriptionCapabilities, TranscriptionOperationError, type TranscriptionProvider, TranscriptionProviderRegistry, type TranscriptionProviderRequest, type TranscriptionProviderResult, type TranscriptionRequest, UnsupportedEventSchemaVersionError, UnsupportedModelInputError, UnsupportedTranscriptionInputError, VLLM_PROFILE, type ValidateData, type ValidateResult, ValidationError, type VectorEntry, type VectorResult, type VectorStore, type VerifyData, VerifyError, type VerifyOptions, type VerifyRetry, type VoteOptions, type Workflow, type WorkflowConfig, WorkflowContext, type WorkflowContextInit, type WorkflowEndData, type WorkflowStartData, XAI_PROFILE, agent, createSpanManager, defineConfig, estimateOpenAICost, eventCostContribution, expandGlob, extractJson, extractThinkTags, findConfig, getEventSchemaVersion, getExecutionEventSchemaVersion, hasPositiveBillableWork, hasPositiveTokens, importModule, inputText, isCostBearingLeaf, isRetryableStatus, isRootLevel, isUnpricedLeaf, isUsableCost, needsTsxLoader, normalizeStoredExecution, openaiReasoningEmit, parsePartialJson, pickDefault, pickExport, priceFromTable, reasoningEffortEmit, reasoningObjectEmit, redactEvent, redactHistoricalEvent, registerConditions, resolveApiKey, resolvePerModel, resolveRuntime, resolveThinkingOptions, stringStreamFromEvents, tool, workflow, zodToJsonSchema };
8334
+ export { AXL_EVENT_TYPES, AXL_EVENT_TYPES_V2, AXL_TOOL_LIFECYCLE_TYPES_V2, AZURE_PROFILE, type Accounting, type AccountingCompleteness, type AccountingReason, type AccountingUsage, AdmissionController, AdmissionDeniedError, type Agent, type AgentCallEndData, type AgentCallInfo, type AgentCallParams, type AgentCallStartData, type AgentConfig, AnthropicProvider, type AnyWorkflow, type ApiKeySource, type ArtifactManifest, type ArtifactOwner, type ArtifactStatus, type ArtifactWriteResult, type AskOptions, type AskScoped, type AuthHeader, type AwaitHumanData, type AwaitHumanOptions, type AwaitHumanResolvedData, type AxlConfig, AxlError, type AxlEvent, type AxlEventBase, AxlEventBus, type AxlEventOf, type AxlEventType, type AxlEventV2, type AxlEventV2Of, AxlRuntime, AxlStream, BEDROCK_PROFILE, BUILTIN_PROFILES, BudgetExceededError, type BudgetOptions, type BudgetResult, CONFIG_CANDIDATES, COST_BEARING_LEAF_TYPES, type CallTiming, type CapabilityFlags, type CaptureCorrelation, type CapturedCorrection, type CapturedError, type CapturedMessage, type CapturedOperationRef, type CapturedPhase, type CapturedRequest, type CapturedRequestRecord, type CapturedResponse, type ChatMessage, type ChatOptions, type CheckpointEventData, type CoalescedPartialObject, type CostProvenance, type CreateContextOptions, DEEPSEEK_PROFILE, DEFAULT_MAX_QUEUE_BYTES, DEFAULT_MAX_RECORD_BYTES, DEFAULT_MAX_RUN_BYTES, type DecisionCleanupFailedEvent, type DelegateData, type DelegateOptions, type DiagnosticArtifactStore, type DiagnosticArtifactsConfig, type DiagnosticsConfig, type DispatchAdmission, type Effort, type EffortResolution, type EmbedResult, type EmbedUsage, type Embedder, type EvalExecuteWorkflow, type EvalHistoryEntry, type EvalProgressEventShape, type EventStreamOptions, EventStreamOverflowError, type ExecuteOptions, type ExecutionInfo, type ExecutionInfoV1, type ExecutionInfoV2, type ExecutionState, type ExternalOperationDescriptor, type ExternalOperationReport, FileDiagnosticArtifactStore, GROQ_PROFILE, GeminiProvider, GeminiTranscriptionProvider, type GuardrailBlockHandler, type GuardrailData, GuardrailError, type GuardrailResult, type GuardrailsConfig, type HandoffDescriptor, type HandoffRecord, type HandoffReturnData, type HandoffStartData, type HistoricalAxlEvent, type HistoricalExecutionInfo, type HumanDecision, InMemoryVectorStore, type InputAudioPart, type InputContentPart, type InputGuardrail, type InputImagePart, type InputMediaSource, type InputModalitySupport, type InputTextPart, InvalidModelInputError, InvalidTranscriptionInputError, LLAMACPP_PROFILE, LMSTUDIO_PROFILE, type LegacyAxlEventV1, type LegacyExecutionInfoV1, MAX_INLINE_MODEL_INPUT_BYTES, MAX_INLINE_TRANSCRIPTION_BYTES, MISTRAL_PROFILE, type MapOptions, MaxTurnsError, McpManager, type McpServer, type McpServerConfig, type McpToolDefinition, type McpToolResult, type MemoryConfig, type MemoryEventData, MemoryManager, MemoryStore, type ModelInput, type ModelInputDescriptor, type ModelTimingRollup, NoConsensus, NoopSpanManager, OLLAMA_PROFILE, OPENAI_PRICING, OPENAI_PROFILE, OPENROUTER_PROFILE, type ObservationStatus, type OpenAICompatibleOptions, OpenAICompatibleProvider, OpenAIEmbedder, OpenAIProvider, OpenAIResponsesProvider, OpenAITranscriptionProvider, OpenRouterTranscriptionProvider, type OpenedArtifact, type OperationKind, type OperationPurpose, type OutputGuardrail, type OutputValidator, type PendingDecision, type PerModel, type PricingSource, type PricingTable, type ProfileInputModalities, type Provider, type ProviderAdapter, type ProviderDiagnosticData, ProviderError, type ProviderInputValidationRequest, type ProviderInputValidationResult, type ProviderProfile, ProviderRegistry, type ProviderResponse, QuorumNotMet, REDACTED, REDACTION_RULES, type RaceOptions, type RateLimitConfig, RateLimiter, type ReasoningCapture, type ReasoningEmit, type ReasoningProfile, type ReasoningRoundTrip, type RecallOptions, type RecallResult, type RecordedAudioSource, RedisStore, type RememberOptions, type RememberResult, RequestCaptureChannel, type RequestCaptureOptions, type RequestCaptureSink, type RequestCaptureStatus, type ResolvedThinkingOptions, type Result, type RetryFeedbackHook, type RetryFeedbackInfo, type RetryFeedbackResult, type RetryPolicy, type RuntimeEvalConfigShape, SGLANG_PROFILE, SQLiteStore, type SchemaCheckData, type SchemaDiagnosticData, type SchemaPromptOption, Session, type SessionOptions, type SpanHandle, type SpanManager, type SpawnOptions, SqliteVectorStore, type StagedArtifact, StallTimeoutError, type StateStore, type StreamChunk, type StringDeltaData, type StringStreamEvent, type StringStreamFilter, type TelemetryConfig, ThinkTagScanner, type TimeoutBreakdown, TimeoutError, type Tool, type ToolApprovalData, type ToolArgumentIssue, type ToolCallCancellation, type ToolCallData, type ToolCallEndData, type ToolCallFailure, type ToolCallMessage, type ToolCallOutcome, type ToolCallRejectedData, type ToolCallRejectedEvent, type ToolCallStartData, type ToolCallStartDataV2, type ToolChoice, type ToolConfig, type ToolDeniedData, type ToolEventError, ToolFailure, type ToolFailureConstructor, type ToolFailureOptions, type ToolHooks, type ToolLifecycleEventV2, type ToolModelOutput, ToolModelOutputError, type TrackExecutionMetadata, type TrackExecutionResult, type TrackOutcomeOptions, type TrackedOutcome, type Transcript, type TranscriptionCapabilities, TranscriptionOperationError, type TranscriptionProvider, TranscriptionProviderRegistry, type TranscriptionProviderRequest, type TranscriptionProviderResult, type TranscriptionRequest, UnsupportedEventSchemaVersionError, UnsupportedModelInputError, UnsupportedTranscriptionInputError, VLLM_PROFILE, type ValidateData, type ValidateResult, ValidationError, type VectorEntry, type VectorResult, type VectorStore, type VerifyData, VerifyError, type VerifyOptions, type VerifyRetry, type VoteOptions, type Workflow, type WorkflowConfig, WorkflowContext, type WorkflowContextInit, type WorkflowEndData, type WorkflowStartData, XAI_PROFILE, agent, createSpanManager, defineConfig, estimateOpenAICost, eventCostContribution, expandGlob, externalOperation, extractJson, extractThinkTags, findConfig, getEventSchemaVersion, getExecutionEventSchemaVersion, hasPositiveBillableWork, hasPositiveTokens, importModule, inputText, isAdmissionDeniedError, isCostBearingLeaf, isRetryableStatus, isRootLevel, isUnpricedLeaf, isUsableCost, needsTsxLoader, normalizeStoredExecution, openaiReasoningEmit, parsePartialJson, pickDefault, pickExport, priceFromTable, reasoningEffortEmit, reasoningObjectEmit, redactCapturedRequest, redactEvent, redactHistoricalEvent, registerConditions, resolveApiKey, resolvePerModel, resolveRuntime, resolveThinkingOptions, stringStreamFromEvents, summarizeModelInput, tool, workflow, zodToJsonSchema };