@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/README.md +38 -3
- package/dist/{chunk-NVPA4RY6.js → chunk-O7Y6WXE2.js} +3 -1
- package/dist/{chunk-NVPA4RY6.js.map → chunk-O7Y6WXE2.js.map} +1 -1
- package/dist/index.cjs +4730 -910
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2034 -198
- package/dist/index.d.ts +2034 -198
- package/dist/index.js +4718 -898
- package/dist/index.js.map +1 -1
- package/dist/span-manager-WZHJOY7N.js +9 -0
- package/package.json +2 -2
- package/dist/span-manager-74ZCGELO.js +0 -7
- /package/dist/{span-manager-74ZCGELO.js.map → span-manager-WZHJOY7N.js.map} +0 -0
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
|
-
|
|
35
|
-
|
|
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
|
-
*
|
|
1629
|
-
* `minIntervalMs` spacing
|
|
1630
|
-
*
|
|
1631
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
1638
|
-
*
|
|
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
|
-
*
|
|
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
|
-
* - **
|
|
2325
|
-
*
|
|
2326
|
-
*
|
|
2327
|
-
*
|
|
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
|
|
2336
|
-
*
|
|
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
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
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
|
-
/**
|
|
3137
|
+
/** No-op; the per-scope governor overrides it to read quota headers. */
|
|
2392
3138
|
observe(_res: Response): void;
|
|
2393
|
-
/**
|
|
2394
|
-
|
|
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
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
3019
|
-
* {@link RateLimitConfig})
|
|
3020
|
-
*
|
|
3021
|
-
*
|
|
3022
|
-
*
|
|
3023
|
-
*
|
|
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? }
|
|
4335
|
-
*
|
|
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
|
|
4598
|
-
* item's
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
4774
|
-
*
|
|
4775
|
-
*
|
|
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
|
-
*
|
|
4778
|
-
*
|
|
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`
|
|
4783
|
-
*
|
|
4784
|
-
*
|
|
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.
|
|
4791
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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 };
|