@osama.ansar/agent-loopguard 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +280 -2
- package/dist/index.cjs +932 -0
- package/dist/index.d.cts +441 -0
- package/dist/index.d.ts +441 -0
- package/dist/index.js +921 -0
- package/package.json +82 -4
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for agent-loopguard.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is plain data: steps go in, serialisable verdicts come out.
|
|
5
|
+
*/
|
|
6
|
+
/** One tool invocation made during a step. */
|
|
7
|
+
interface GuardToolCall {
|
|
8
|
+
/** Tool name as the model called it. */
|
|
9
|
+
name: string;
|
|
10
|
+
/** Arguments the model supplied. Any JSON-ish (or not-so-JSON-ish) value. */
|
|
11
|
+
args?: unknown;
|
|
12
|
+
/** Value the tool returned, if you have it. Used by result-aware rules. */
|
|
13
|
+
result?: unknown;
|
|
14
|
+
/** Error the tool raised, if any. A non-null `error` marks the call as failed. */
|
|
15
|
+
error?: unknown;
|
|
16
|
+
}
|
|
17
|
+
/** Token and cost numbers for one step. All supplied by the caller; nothing is estimated. */
|
|
18
|
+
interface GuardUsage {
|
|
19
|
+
inputTokens?: number | undefined;
|
|
20
|
+
outputTokens?: number | undefined;
|
|
21
|
+
totalTokens?: number | undefined;
|
|
22
|
+
/** Cost in USD as computed by the caller. The package contains no price table. */
|
|
23
|
+
costUsd?: number | undefined;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* One model step: what the model produced and what happened as a result.
|
|
27
|
+
* Every field is optional; supply what you have.
|
|
28
|
+
*/
|
|
29
|
+
interface GuardStep {
|
|
30
|
+
toolCalls?: readonly GuardToolCall[] | undefined;
|
|
31
|
+
/** Assistant text produced in this step. */
|
|
32
|
+
text?: string | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Why the model stopped generating. Provider-agnostic: `'stop' | 'length' | 'tool-calls' |
|
|
35
|
+
* 'content-filter' | 'error' | 'other' | 'unknown'` or any other string. Compared after
|
|
36
|
+
* lower-casing and turning `_` into `-`.
|
|
37
|
+
*/
|
|
38
|
+
finishReason?: string | undefined;
|
|
39
|
+
usage?: GuardUsage | undefined;
|
|
40
|
+
/** How long the step took, in milliseconds. Summed by the `maxDurationMs` budget. */
|
|
41
|
+
durationMs?: number | undefined;
|
|
42
|
+
/** When the step happened (epoch ms). Informational: recorded in history, not used by detectors. */
|
|
43
|
+
timestamp?: number | undefined;
|
|
44
|
+
}
|
|
45
|
+
type StopReason = 'max-steps' | 'repeated-tool-call' | 'oscillation' | 'no-progress' | 'finish-reason' | 'budget' | 'custom';
|
|
46
|
+
type BudgetMetric = 'totalTokens' | 'inputTokens' | 'outputTokens' | 'costUsd' | 'durationMs';
|
|
47
|
+
/** Stable machine-readable codes. Safe to switch on; new codes are only added in minor versions. */
|
|
48
|
+
type BuiltinStopCode = 'MAX_STEPS' | 'REPEATED_TOOL_CALL' | 'OSCILLATION' | 'NO_PROGRESS' | 'FINISH_TERMINAL' | 'FINISH_OTHER' | 'FINISH_OTHER_TEXT_ONLY' | 'BUDGET_TOTAL_TOKENS' | 'BUDGET_INPUT_TOKENS' | 'BUDGET_OUTPUT_TOKENS' | 'BUDGET_COST_USD' | 'BUDGET_DURATION_MS';
|
|
49
|
+
interface MaxStepsEvidence {
|
|
50
|
+
steps: number;
|
|
51
|
+
limit: number;
|
|
52
|
+
}
|
|
53
|
+
interface RepeatedToolCallEvidence {
|
|
54
|
+
tool: string;
|
|
55
|
+
/** Canonical form of the arguments, truncated for display. */
|
|
56
|
+
argsPreview: string;
|
|
57
|
+
/** Length of the run of identical calls. */
|
|
58
|
+
count: number;
|
|
59
|
+
threshold: number;
|
|
60
|
+
/** Indexes (0-based) of the steps that contain the run; at most the last 10. */
|
|
61
|
+
stepIndexes: number[];
|
|
62
|
+
/** True when the rule was configured to require identical results too. */
|
|
63
|
+
identicalResults: boolean;
|
|
64
|
+
}
|
|
65
|
+
interface OscillationEvidence {
|
|
66
|
+
/** Number of tool calls in one cycle. */
|
|
67
|
+
period: number;
|
|
68
|
+
/** How many consecutive times the cycle was observed. */
|
|
69
|
+
repeats: number;
|
|
70
|
+
threshold: number;
|
|
71
|
+
/** One cycle, as `tool(args)` labels. */
|
|
72
|
+
cycle: string[];
|
|
73
|
+
}
|
|
74
|
+
interface NoProgressEvidence {
|
|
75
|
+
/** Consecutive stalled steps with identical text. */
|
|
76
|
+
steps: number;
|
|
77
|
+
threshold: number;
|
|
78
|
+
/** The repeated text (normalised), truncated for display. */
|
|
79
|
+
textPreview: string;
|
|
80
|
+
}
|
|
81
|
+
interface FinishReasonEvidence {
|
|
82
|
+
/** The finish reason exactly as supplied. */
|
|
83
|
+
finishReason: string;
|
|
84
|
+
/** Lower-cased, `_` replaced by `-`. */
|
|
85
|
+
normalized: string;
|
|
86
|
+
policy: 'terminal' | 'other-stop' | 'other-text-only';
|
|
87
|
+
hasText: boolean;
|
|
88
|
+
toolCallCount: number;
|
|
89
|
+
}
|
|
90
|
+
interface BudgetEvidence {
|
|
91
|
+
metric: BudgetMetric;
|
|
92
|
+
value: number;
|
|
93
|
+
limit: number;
|
|
94
|
+
}
|
|
95
|
+
interface StopBase<R extends StopReason, C extends string, E> {
|
|
96
|
+
stop: true;
|
|
97
|
+
reason: R;
|
|
98
|
+
/** Stable machine-readable code. */
|
|
99
|
+
code: C;
|
|
100
|
+
/** One human-readable sentence. */
|
|
101
|
+
detail: string;
|
|
102
|
+
/** Structured, JSON-serialisable facts behind the decision. */
|
|
103
|
+
evidence: E;
|
|
104
|
+
/** Non-fatal warnings raised in the same step, if any. */
|
|
105
|
+
warnings?: GuardWarning[];
|
|
106
|
+
}
|
|
107
|
+
type MaxStepsVerdict = StopBase<'max-steps', 'MAX_STEPS', MaxStepsEvidence>;
|
|
108
|
+
type RepeatedToolCallVerdict = StopBase<'repeated-tool-call', 'REPEATED_TOOL_CALL', RepeatedToolCallEvidence>;
|
|
109
|
+
type OscillationVerdict = StopBase<'oscillation', 'OSCILLATION', OscillationEvidence>;
|
|
110
|
+
type NoProgressVerdict = StopBase<'no-progress', 'NO_PROGRESS', NoProgressEvidence>;
|
|
111
|
+
type FinishReasonVerdict = StopBase<'finish-reason', 'FINISH_TERMINAL' | 'FINISH_OTHER' | 'FINISH_OTHER_TEXT_ONLY', FinishReasonEvidence>;
|
|
112
|
+
type BudgetVerdict = StopBase<'budget', 'BUDGET_TOTAL_TOKENS' | 'BUDGET_INPUT_TOKENS' | 'BUDGET_OUTPUT_TOKENS' | 'BUDGET_COST_USD' | 'BUDGET_DURATION_MS', BudgetEvidence>;
|
|
113
|
+
/** Verdict produced by a user-supplied `custom` detector. */
|
|
114
|
+
type CustomVerdict = StopBase<'custom', string, unknown>;
|
|
115
|
+
type StopVerdict = MaxStepsVerdict | RepeatedToolCallVerdict | OscillationVerdict | NoProgressVerdict | FinishReasonVerdict | BudgetVerdict | CustomVerdict;
|
|
116
|
+
interface GuardWarning {
|
|
117
|
+
code: 'BUDGET_WARNING';
|
|
118
|
+
metric: BudgetMetric;
|
|
119
|
+
value: number;
|
|
120
|
+
limit: number;
|
|
121
|
+
/** `value / limit`. */
|
|
122
|
+
ratio: number;
|
|
123
|
+
detail: string;
|
|
124
|
+
}
|
|
125
|
+
interface ContinueVerdict {
|
|
126
|
+
stop: false;
|
|
127
|
+
/** Present only when at least one warning is active. */
|
|
128
|
+
warnings?: GuardWarning[];
|
|
129
|
+
}
|
|
130
|
+
type Verdict = ContinueVerdict | StopVerdict;
|
|
131
|
+
interface RepeatedToolCallOptions {
|
|
132
|
+
/** Stop when the same call occurs this many times in a row. Integer >= 2. Default 3. */
|
|
133
|
+
threshold?: number;
|
|
134
|
+
/**
|
|
135
|
+
* Count a repeat only when the call also returned an identical result (calls without a result
|
|
136
|
+
* compare equal). Use for polling-style tools. Default false.
|
|
137
|
+
*/
|
|
138
|
+
requireIdenticalResults?: boolean;
|
|
139
|
+
/** Drop failed calls (non-null `error`) from the sequence before counting. Default false. */
|
|
140
|
+
ignoreErrors?: boolean;
|
|
141
|
+
}
|
|
142
|
+
interface OscillationOptions {
|
|
143
|
+
/** Longest cycle to look for (cycles of length 2..maxPeriod). Integer >= 2. Default 3. */
|
|
144
|
+
maxPeriod?: number;
|
|
145
|
+
/** Stop when a cycle was seen this many times in a row. Integer >= 2. Default 3. */
|
|
146
|
+
repeats?: number;
|
|
147
|
+
}
|
|
148
|
+
interface NoProgressOptions {
|
|
149
|
+
/** Consecutive stalled steps with identical text. Integer >= 2. Default 3. */
|
|
150
|
+
threshold?: number;
|
|
151
|
+
}
|
|
152
|
+
type OtherFinishPolicy = 'stop' | 'continue' | 'stop-if-text-only';
|
|
153
|
+
interface FinishReasonOptions {
|
|
154
|
+
/** Finish reasons that end the run. Default `['stop', 'length', 'content-filter', 'error']`. */
|
|
155
|
+
terminal?: readonly string[];
|
|
156
|
+
/**
|
|
157
|
+
* What to do with a finish reason that is neither terminal nor `tool-calls` (`other`, `unknown`,
|
|
158
|
+
* or anything unfamiliar). Default `'stop-if-text-only'`.
|
|
159
|
+
*/
|
|
160
|
+
otherPolicy?: OtherFinishPolicy;
|
|
161
|
+
}
|
|
162
|
+
/** A limit, optionally with a warning threshold. */
|
|
163
|
+
type BudgetLimit = number | {
|
|
164
|
+
limit: number;
|
|
165
|
+
warnAt?: number;
|
|
166
|
+
};
|
|
167
|
+
interface BudgetOptions {
|
|
168
|
+
maxTotalTokens?: BudgetLimit;
|
|
169
|
+
maxInputTokens?: BudgetLimit;
|
|
170
|
+
maxOutputTokens?: BudgetLimit;
|
|
171
|
+
maxCostUsd?: BudgetLimit;
|
|
172
|
+
maxDurationMs?: BudgetLimit;
|
|
173
|
+
/** Default warning ratio (0 < r < 1) for limits that do not set their own `warnAt`. */
|
|
174
|
+
warnAt?: number;
|
|
175
|
+
}
|
|
176
|
+
/** What a custom detector returns to stop the run. */
|
|
177
|
+
interface CustomStop {
|
|
178
|
+
stop: true;
|
|
179
|
+
/** Stable machine-readable code of your own. Default `'CUSTOM'`. */
|
|
180
|
+
code?: string;
|
|
181
|
+
/** One human-readable sentence. */
|
|
182
|
+
detail?: string;
|
|
183
|
+
/** Anything JSON-serialisable that explains the decision. */
|
|
184
|
+
evidence?: unknown;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* User-supplied detector. Receives the full history (including `step`, last) and may return a
|
|
188
|
+
* stop verdict. Return `undefined` (or `{ stop: false }`) to let the run continue. Errors it
|
|
189
|
+
* throws propagate out of `guard.check`.
|
|
190
|
+
*/
|
|
191
|
+
type CustomDetector = (history: readonly GuardStep[], step: GuardStep) => CustomStop | {
|
|
192
|
+
stop: false;
|
|
193
|
+
} | undefined;
|
|
194
|
+
interface GuardOptions {
|
|
195
|
+
/** Stop after this many steps. Integer >= 1. Default 50. `false` disables. */
|
|
196
|
+
maxSteps?: number | false;
|
|
197
|
+
/** Default: enabled with default options. `false` disables. */
|
|
198
|
+
repeatedToolCall?: RepeatedToolCallOptions | false;
|
|
199
|
+
/** Default: enabled with default options. `false` disables. */
|
|
200
|
+
oscillation?: OscillationOptions | false;
|
|
201
|
+
/** Default: enabled with default options. `false` disables. */
|
|
202
|
+
noProgress?: NoProgressOptions | false;
|
|
203
|
+
/** Default: enabled with default options. `false` disables. */
|
|
204
|
+
finishReason?: FinishReasonOptions | false;
|
|
205
|
+
/** Default: no limits. */
|
|
206
|
+
budget?: BudgetOptions | false;
|
|
207
|
+
/** Extra detectors, run after the built-in ones. */
|
|
208
|
+
custom?: CustomDetector | readonly CustomDetector[];
|
|
209
|
+
/** Clock used for wall-clock duration budgets. Default `Date.now`. */
|
|
210
|
+
now?: () => number;
|
|
211
|
+
}
|
|
212
|
+
interface GuardTotals {
|
|
213
|
+
inputTokens: number;
|
|
214
|
+
outputTokens: number;
|
|
215
|
+
totalTokens: number;
|
|
216
|
+
costUsd: number;
|
|
217
|
+
/** Sum of the `durationMs` values supplied on steps. */
|
|
218
|
+
durationMs: number;
|
|
219
|
+
}
|
|
220
|
+
interface GuardSummary {
|
|
221
|
+
steps: number;
|
|
222
|
+
toolCalls: number;
|
|
223
|
+
failedToolCalls: number;
|
|
224
|
+
totals: GuardTotals;
|
|
225
|
+
/** `null` for a detector that is disabled. */
|
|
226
|
+
detectors: {
|
|
227
|
+
maxSteps: {
|
|
228
|
+
limit: number;
|
|
229
|
+
remaining: number;
|
|
230
|
+
} | null;
|
|
231
|
+
repeatedToolCall: {
|
|
232
|
+
threshold: number;
|
|
233
|
+
currentRun: {
|
|
234
|
+
tool: string;
|
|
235
|
+
length: number;
|
|
236
|
+
} | null;
|
|
237
|
+
longestRun: number;
|
|
238
|
+
} | null;
|
|
239
|
+
oscillation: {
|
|
240
|
+
maxPeriod: number;
|
|
241
|
+
repeats: number;
|
|
242
|
+
current: {
|
|
243
|
+
period: number;
|
|
244
|
+
repeats: number;
|
|
245
|
+
} | null;
|
|
246
|
+
} | null;
|
|
247
|
+
noProgress: {
|
|
248
|
+
threshold: number;
|
|
249
|
+
streak: number;
|
|
250
|
+
} | null;
|
|
251
|
+
finishReason: {
|
|
252
|
+
lastFinishReason: string | null;
|
|
253
|
+
} | null;
|
|
254
|
+
budget: {
|
|
255
|
+
limits: Partial<Record<BudgetMetric, number>>;
|
|
256
|
+
used: Record<BudgetMetric, number>;
|
|
257
|
+
} | null;
|
|
258
|
+
};
|
|
259
|
+
/** The verdict returned by the most recent `check`, or `null` before the first one. */
|
|
260
|
+
lastVerdict: Verdict | null;
|
|
261
|
+
}
|
|
262
|
+
interface Guard {
|
|
263
|
+
/** Record a step and decide whether the run should continue. Never throws on odd step data. */
|
|
264
|
+
check(step: GuardStep): Verdict;
|
|
265
|
+
/** Forget everything and start a fresh run (options are kept). */
|
|
266
|
+
reset(): void;
|
|
267
|
+
/** Counts, totals and per-detector state. Contains nothing time-dependent. */
|
|
268
|
+
summary(): GuardSummary;
|
|
269
|
+
/** Frozen shallow copy of the steps recorded so far. */
|
|
270
|
+
readonly history: readonly GuardStep[];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Default for `maxSteps`: a backstop, not a tuned value. Raise it for long-running agents. */
|
|
274
|
+
declare const DEFAULT_MAX_STEPS = 50;
|
|
275
|
+
/**
|
|
276
|
+
* Create a guard. Feed it one step at a time with `check`; it answers `{ stop: false }` or a
|
|
277
|
+
* typed verdict saying why the run should end.
|
|
278
|
+
*
|
|
279
|
+
* Detectors run in this order and the first stop wins: finish reason, budget, max steps,
|
|
280
|
+
* repeated tool call, oscillation, no progress, then custom detectors. All of them see every step
|
|
281
|
+
* even after one has stopped, so their state stays consistent.
|
|
282
|
+
*
|
|
283
|
+
* Invalid options throw a `TypeError` here, never later from `check`.
|
|
284
|
+
*/
|
|
285
|
+
declare function createGuard(options?: GuardOptions): Guard;
|
|
286
|
+
|
|
287
|
+
/** True for a stop verdict that is not just the model finishing normally (`finishReason: 'stop'`). */
|
|
288
|
+
declare function isFault(verdict: Verdict): verdict is StopVerdict;
|
|
289
|
+
/** Turn a verdict into readable text for logs and error messages. Never throws. */
|
|
290
|
+
declare function explain(verdict: Verdict): string;
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Canonicalisation: turn any JavaScript value into a deterministic string so that two
|
|
294
|
+
* "equal" tool-call arguments compare equal regardless of key order, and so that nothing
|
|
295
|
+
* a model can send (NaN, bigint, circular objects, Maps...) can make the guard throw.
|
|
296
|
+
*
|
|
297
|
+
* This is NOT JSON. JSON loses too much (undefined, NaN, bigint, Map, Set, Date vs string,
|
|
298
|
+
* cycles). The output is only meant for equality checks and short human previews.
|
|
299
|
+
*/
|
|
300
|
+
/**
|
|
301
|
+
* Deterministic, key-order-insensitive string form of `value`. Never throws.
|
|
302
|
+
*
|
|
303
|
+
* Rules:
|
|
304
|
+
* - Object keys are sorted; properties whose value is `undefined` are omitted (like JSON).
|
|
305
|
+
* - Array items keep their order; `undefined` items are kept.
|
|
306
|
+
* - `NaN`, `Infinity`, `-Infinity` and bigint (`10n`) are distinct from strings and numbers
|
|
307
|
+
* that print the same; `-0` is normalised to `0`.
|
|
308
|
+
* - Dates are written as ISO strings (or `invalid`); Maps and Sets are sorted by the canonical
|
|
309
|
+
* form of their entries, so insertion order does not matter.
|
|
310
|
+
* - A reference back to an ancestor is written `[Circular:<depth of that ancestor>]`.
|
|
311
|
+
* - Objects with a `toJSON` method are written as the result of calling it.
|
|
312
|
+
* - If something throws while reading the value (hostile getters, revoked proxies) the result is
|
|
313
|
+
* a unique placeholder, so such values are never considered equal to anything.
|
|
314
|
+
*/
|
|
315
|
+
declare function canonicalize(value: unknown): string;
|
|
316
|
+
/**
|
|
317
|
+
* Stable, non-cryptographic hash of any value: two independent 53-bit hashes of its canonical
|
|
318
|
+
* form (28 hex characters). Equal values always hash equally; a collision between different values
|
|
319
|
+
* is possible in principle but not a practical concern for loop detection. Do not use it for
|
|
320
|
+
* anything security-related.
|
|
321
|
+
*/
|
|
322
|
+
declare function stableHash(value: unknown): string;
|
|
323
|
+
|
|
324
|
+
interface AiSdkToolCallLike {
|
|
325
|
+
readonly toolCallId?: string | undefined;
|
|
326
|
+
readonly toolName: string;
|
|
327
|
+
readonly input?: unknown;
|
|
328
|
+
readonly args?: unknown;
|
|
329
|
+
}
|
|
330
|
+
interface AiSdkToolResultLike {
|
|
331
|
+
readonly toolCallId?: string | undefined;
|
|
332
|
+
readonly toolName?: string | undefined;
|
|
333
|
+
readonly output?: unknown;
|
|
334
|
+
readonly result?: unknown;
|
|
335
|
+
}
|
|
336
|
+
interface AiSdkStepLike {
|
|
337
|
+
readonly text?: string | undefined;
|
|
338
|
+
readonly finishReason?: string | undefined;
|
|
339
|
+
readonly toolCalls?: ReadonlyArray<AiSdkToolCallLike> | undefined;
|
|
340
|
+
readonly toolResults?: ReadonlyArray<AiSdkToolResultLike> | undefined;
|
|
341
|
+
/** Scanned for `{ type: 'tool-error', toolCallId, error }` parts. */
|
|
342
|
+
readonly content?: ReadonlyArray<{
|
|
343
|
+
readonly type: string;
|
|
344
|
+
}> | undefined;
|
|
345
|
+
readonly usage?: {
|
|
346
|
+
readonly inputTokens?: number | undefined;
|
|
347
|
+
readonly outputTokens?: number | undefined;
|
|
348
|
+
readonly totalTokens?: number | undefined;
|
|
349
|
+
} | undefined;
|
|
350
|
+
readonly performance?: {
|
|
351
|
+
readonly stepTimeMs?: number | undefined;
|
|
352
|
+
} | undefined;
|
|
353
|
+
}
|
|
354
|
+
/** Map one AI SDK step to a `GuardStep`. Tool results and errors are matched to calls by `toolCallId`. */
|
|
355
|
+
declare function toGuardStep(step: AiSdkStepLike): GuardStep;
|
|
356
|
+
interface ToStopWhenOptions {
|
|
357
|
+
/** Called when the guard decides to stop, so you can log or record why. May be called more than once for the same stop. */
|
|
358
|
+
onStop?: (verdict: StopVerdict) => void;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Build an AI SDK `stopWhen` condition from a guard.
|
|
362
|
+
*
|
|
363
|
+
* ```ts
|
|
364
|
+
* generateText({ model, tools, stopWhen: toStopWhen(() => createGuard()) });
|
|
365
|
+
* ```
|
|
366
|
+
*
|
|
367
|
+
* The condition is idempotent: on every call it creates a fresh guard from `guardFactory` and
|
|
368
|
+
* replays the whole `steps` array, so it gives the same answer for the same steps however often
|
|
369
|
+
* the SDK calls it, and it holds no state between calls or runs. Only the verdict for the latest
|
|
370
|
+
* step counts. Wall-clock budgets are meaningless in a replay, so use durations from the steps
|
|
371
|
+
* (`performance.stepTimeMs`, summed) rather than a `now()`-based limit here.
|
|
372
|
+
*
|
|
373
|
+
* You may pass `GuardOptions` instead of a factory.
|
|
374
|
+
*/
|
|
375
|
+
declare function toStopWhen<S extends AiSdkStepLike = AiSdkStepLike>(guardFactory?: (() => Guard) | GuardOptions, hooks?: ToStopWhenOptions): (options: {
|
|
376
|
+
steps: ReadonlyArray<S>;
|
|
377
|
+
}) => boolean;
|
|
378
|
+
|
|
379
|
+
/** The one thing we need from an `AbortSignal`; keeps the package free of DOM/Node type deps. */
|
|
380
|
+
interface AbortSignalLike {
|
|
381
|
+
readonly aborted: boolean;
|
|
382
|
+
}
|
|
383
|
+
/** A step as returned by your `step` function: a `GuardStep` plus an optional end-of-run flag. */
|
|
384
|
+
interface LoopStep extends GuardStep {
|
|
385
|
+
/**
|
|
386
|
+
* `true` when the agent has finished. `false` forces the loop to continue. When omitted, a step
|
|
387
|
+
* with no tool calls counts as final (there is nothing left to execute).
|
|
388
|
+
*/
|
|
389
|
+
done?: boolean | undefined;
|
|
390
|
+
}
|
|
391
|
+
interface RunAgentLoopOptions<S extends LoopStep> {
|
|
392
|
+
/** Perform step `index` (call the model, run its tools) and describe what happened. */
|
|
393
|
+
step: (index: number) => S | Promise<S>;
|
|
394
|
+
/** Defaults to `createGuard()`. */
|
|
395
|
+
guard?: Guard;
|
|
396
|
+
/** Checked before every step and after a step that throws; an in-flight step is not cancelled. */
|
|
397
|
+
signal?: AbortSignalLike;
|
|
398
|
+
/** Called after every step with that step's verdict, including non-stopping ones with warnings. */
|
|
399
|
+
onVerdict?: (verdict: Verdict, step: S, index: number) => void;
|
|
400
|
+
}
|
|
401
|
+
interface LoopResult<S extends LoopStep> {
|
|
402
|
+
/** Every step that ran, in order. */
|
|
403
|
+
steps: S[];
|
|
404
|
+
/** The last verdict: the stop verdict that ended the loop, or `{ stop: false }`. */
|
|
405
|
+
verdict: Verdict;
|
|
406
|
+
/**
|
|
407
|
+
* True when the agent ended the run itself: the step was final and no fault was raised, or the
|
|
408
|
+
* finish reason was a normal `stop`. False when the guard cut it short, the finish reason
|
|
409
|
+
* was something like `length` or `error`, or the signal aborted.
|
|
410
|
+
*/
|
|
411
|
+
completed: boolean;
|
|
412
|
+
/** True when the loop ended because the signal was aborted. */
|
|
413
|
+
aborted: boolean;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* A tiny agent loop: call `step(0)`, `step(1)`, ... until the agent is done or the guard says stop.
|
|
417
|
+
* Throws whatever `step` throws (unless the signal was aborted, in which case it returns).
|
|
418
|
+
*
|
|
419
|
+
* If you disable `maxSteps` and your `step` never finishes, this loop never ends; that is on you.
|
|
420
|
+
*/
|
|
421
|
+
declare function runAgentLoop<S extends LoopStep>(options: RunAgentLoopOptions<S>): Promise<LoopResult<S>>;
|
|
422
|
+
|
|
423
|
+
/** An async iterable of steps that ends itself when the guard says stop. */
|
|
424
|
+
interface GuardedSteps<S extends GuardStep> extends AsyncIterable<S> {
|
|
425
|
+
/** The verdict for the most recent step yielded, or `undefined` before the first one. */
|
|
426
|
+
readonly verdict: Verdict | undefined;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Wrap a (sync or async) iterable of steps. Each step is passed to `guard.check` and then yielded
|
|
430
|
+
* to you; if the verdict says stop, the step that triggered it is still yielded (it already
|
|
431
|
+
* happened) and iteration ends right after, closing the source. Read `.verdict` afterwards to find out why:
|
|
432
|
+
*
|
|
433
|
+
* ```ts
|
|
434
|
+
* const steps = withGuard(agentSteps(), guard);
|
|
435
|
+
* for await (const step of steps) render(step);
|
|
436
|
+
* if (steps.verdict?.stop) console.log(explain(steps.verdict));
|
|
437
|
+
* ```
|
|
438
|
+
*/
|
|
439
|
+
declare function withGuard<S extends GuardStep>(source: AsyncIterable<S> | Iterable<S>, guard: Guard): GuardedSteps<S>;
|
|
440
|
+
|
|
441
|
+
export { type AbortSignalLike, type AiSdkStepLike, type AiSdkToolCallLike, type AiSdkToolResultLike, type BudgetEvidence, type BudgetLimit, type BudgetMetric, type BudgetOptions, type BudgetVerdict, type BuiltinStopCode, type ContinueVerdict, type CustomDetector, type CustomStop, type CustomVerdict, DEFAULT_MAX_STEPS, type FinishReasonEvidence, type FinishReasonOptions, type FinishReasonVerdict, type Guard, type GuardOptions, type GuardStep, type GuardSummary, type GuardToolCall, type GuardTotals, type GuardUsage, type GuardWarning, type GuardedSteps, type LoopResult, type LoopStep, type MaxStepsEvidence, type MaxStepsVerdict, type NoProgressEvidence, type NoProgressOptions, type NoProgressVerdict, type OscillationEvidence, type OscillationOptions, type OscillationVerdict, type OtherFinishPolicy, type RepeatedToolCallEvidence, type RepeatedToolCallOptions, type RepeatedToolCallVerdict, type RunAgentLoopOptions, type StopReason, type StopVerdict, type ToStopWhenOptions, type Verdict, canonicalize, createGuard, explain, isFault, runAgentLoop, stableHash, toGuardStep, toStopWhen, withGuard };
|