@attest-ai/core 1.0.0 → 1.1.0-rc.1

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.
@@ -1,38 +1,18 @@
1
- import { InitializeResult, ProtocolLogger, ProtocolDiagnostic, Trace, Assertion, BatchBudget, BatchContext, EvaluateBatchResult, DriftReport, SimulatePersona, ConversationMessage, SimulateFaultConfig, Step, AssertionResult, Usage, Outcome } from './proto/index.js';
2
- export { AgentId, AssertionId, AssertionSpec, BiasProbe, ConstraintSpec, ContentSpec, ERR_ASSERTION_ERROR, ERR_ENGINE_ERROR, ERR_INVALID_TRACE, ERR_PROVIDER_ERROR, ERR_SESSION_ERROR, ERR_TIMEOUT, EffectSpec, EmbeddingSpec, EngineTimeoutError, ErrorData, EvaluateBatchParams, Exchange, FailureClass, GenerateUserMessageParams, GenerateUserMessageResult, Handoff, InitializeParams, JudgeAgreement, JudgeMetadata, LlmJudgeSpec, OTLPConversionError, OUTCOME_FAIL, OUTCOME_PASS, OUTCOME_SKIP, PluginSpec, ProtocolDesyncError, ProtocolDiagnosticBuffer, ProtocolDiagnosticKind, ProtocolError, QueryDriftParams, QueryDriftResult, REASON_BUDGET_EXHAUSTED, REASON_JUDGE_ABSTAINED, REASON_MISSING_EVIDENCE, REASON_PROVIDER_NOT_CONFIGURED, REASON_PROVIDER_UNAVAILABLE, REASON_REPLAY_MISS, REASON_SHORT_CIRCUITED, REASON_SIMULATION, RPCError, RPCRequest, RPCResponse, STATUS_HARD_FAIL, STATUS_NOT_EVALUATED, STATUS_PASS, STATUS_SOFT_FAIL, STEP_AGENT_CALL, STEP_LLM_CALL, STEP_RETRIEVAL, STEP_TOOL_CALL, SchemaSpec, ShutdownResult, StepError, SubmitPluginResultParams, TYPE_CONSTRAINT, TYPE_CONTENT, TYPE_EFFECT, TYPE_EMBEDDING, TYPE_LLM_JUDGE, TYPE_PLUGIN, TYPE_SCHEMA, TYPE_TRACE, TYPE_TRACE_TREE, TraceId, TraceMetadata, TraceSpec, TraceTreeSpec, Turn, agentId, assertionId, decodeResponse, encodeRequest, extractId, extractResult, layerForType, previewLine, traceId } from './proto/index.js';
1
+ import { ProtocolLogger, ProtocolDiagnostic, Trace, Assertion, BatchBudget, BatchContext, EvaluateBatchResult, DriftReport, SimulatePersona, ConversationMessage, SimulateFaultConfig, InitializeResult, Usage, Step, AssertionResult, Outcome } from './proto/index.js';
2
+ export { AgentId, AssertionId, AssertionSpec, BiasProbe, ConstraintSpec, ContentSpec, ERR_ASSERTION_ERROR, ERR_ENGINE_ERROR, ERR_INVALID_TRACE, ERR_PROVIDER_ERROR, ERR_SESSION_ERROR, ERR_TIMEOUT, EffectSpec, EmbeddingSpec, EngineTimeoutError, ErrorData, EvaluateBatchParams, Exchange, FailureClass, GenerateUserMessageParams, GenerateUserMessageResult, Handoff, InitializeParams, JudgeAgreement, JudgeMetadata, LlmJudgeSpec, OTLPConversionError, OUTCOME_FAIL, OUTCOME_PASS, OUTCOME_SKIP, PluginSpec, ProtocolDesyncError, ProtocolDiagnosticBuffer, ProtocolDiagnosticKind, ProtocolError, QueryDriftParams, QueryDriftResult, REASON_BUDGET_EXHAUSTED, REASON_JUDGE_ABSTAINED, REASON_JUDGE_MALFORMED, REASON_MISSING_EVIDENCE, REASON_PROVIDER_AUTH, REASON_PROVIDER_NOT_CONFIGURED, REASON_PROVIDER_UNAVAILABLE, REASON_REPLAY_MISS, REASON_SHORT_CIRCUITED, REASON_SIMULATION, RPCError, RPCRequest, RPCResponse, STATUS_HARD_FAIL, STATUS_NOT_EVALUATED, STATUS_PASS, STATUS_SOFT_FAIL, STEP_AGENT_CALL, STEP_LLM_CALL, STEP_RETRIEVAL, STEP_TOOL_CALL, SchemaSpec, ShutdownResult, StepError, SubmitPluginResultParams, TYPE_CONSTRAINT, TYPE_CONTENT, TYPE_EFFECT, TYPE_EMBEDDING, TYPE_LLM_JUDGE, TYPE_PLUGIN, TYPE_SCHEMA, TYPE_TRACE, TYPE_TRACE_TREE, TraceId, TraceMetadata, TraceSpec, TraceTreeSpec, Turn, agentId, assertionId, decodeResponse, encodeRequest, extractId, extractResult, layerForType, previewLine, traceId } from './proto/index.js';
3
3
  import { ChildProcess } from 'node:child_process';
4
4
  import { Interface } from 'node:readline';
5
5
  import { AsyncLocalStorage } from 'node:async_hooks';
6
6
  import { EventEmitter } from 'node:events';
7
7
 
8
- declare const VERSION = "1.0.0";
9
- declare const ENGINE_VERSION = "1.0.0";
8
+ declare const VERSION = "1.1.0-rc.1";
9
+ declare const ENGINE_VERSION = "1.1.0-rc.1";
10
10
 
11
- declare class EngineManager {
12
- private enginePath;
13
- private readonly logLevel;
14
- private process;
15
- private reader;
16
- private initialized;
17
- private requestId;
18
- private initResult;
19
- constructor(options?: {
20
- enginePath?: string;
21
- logLevel?: string;
22
- });
23
- start(): Promise<InitializeResult>;
24
- /** Send initialize and check the engine's version and capabilities. */
25
- private initialize;
26
- stop(): Promise<void>;
27
- sendRequest(method: string, params: Record<string, unknown>): Promise<unknown>;
28
- private sendRequestInternal;
29
- get isRunning(): boolean;
30
- /** Expose process for AttestClient reader loop access */
31
- get childProcess(): ChildProcess | null;
32
- /** Expose readline interface for AttestClient reader loop */
33
- get readlineInterface(): Interface | null;
11
+ /** Budget a timed-out request may have consumed; null means the whole remaining budget. */
12
+ interface BudgetReservation {
13
+ readonly judgeCalls: number | null;
14
+ readonly usd: number | null;
34
15
  }
35
-
36
16
  interface AttestClientOptions {
37
17
  /** Logger override. When omitted, warnings only emit if ATTEST_DEBUG_PROTOCOL=1. */
38
18
  logger?: ProtocolLogger;
@@ -62,6 +42,7 @@ declare class AttestClient {
62
42
  private writeChain;
63
43
  private readerActive;
64
44
  private lineHandler;
45
+ private attachedReader;
65
46
  private readonly logger;
66
47
  private readonly diagnosticBuffer;
67
48
  private readonly diagnosticListeners;
@@ -73,24 +54,46 @@ declare class AttestClient {
73
54
  onProtocolDiagnostic(listener: (diagnostic: ProtocolDiagnostic) => void): () => void;
74
55
  /** Snapshot of the diagnostic ring buffer (most recent last). */
75
56
  protocolDiagnostics(): readonly ProtocolDiagnostic[];
57
+ /**
58
+ * Attach the id-routed reader to the engine's stdout. Idempotent; `sendRequest` starts it
59
+ * on first use, so calling it is optional.
60
+ */
76
61
  startReader(): void;
77
62
  stopReader(): void;
78
63
  private recordDiagnostic;
79
64
  private maybeTriggerDesync;
80
65
  private handleLine;
81
66
  private handleDecodeFailure;
67
+ /** Reject every request still waiting for a response. */
68
+ rejectPending(err: Error): void;
82
69
  private failAll;
70
+ /**
71
+ * Send one request and resolve with the result of the response that carries its id.
72
+ *
73
+ * After a timeout the engine is restarted (its late response can no longer be matched),
74
+ * every other pending request is rejected, and the new engine gets the remaining session
75
+ * budget.
76
+ */
83
77
  sendRequest(method: string, params: Record<string, unknown>, timeoutMs?: number): Promise<unknown>;
78
+ /** `sendRequest` without the restart-on-timeout step, for the engine's own handshake. */
79
+ sendWithoutRestart(method: string, params: Record<string, unknown>, timeoutMs: number): Promise<unknown>;
80
+ private send;
81
+ /** A failed pipe write names the engine's stderr tail; the original error stays as `cause` and `code`. */
82
+ private describeWriteError;
83
+ private recoverFromTimeout;
84
84
  evaluateBatch(trace: Trace, assertions: readonly Assertion[], options?: {
85
85
  timeout?: number;
86
86
  budget?: BatchBudget;
87
87
  context?: BatchContext;
88
88
  }): Promise<EvaluateBatchResult>;
89
+ /** Reject a response whose assertion ids differ from the ids that were sent. */
90
+ private requireSentAssertionIds;
89
91
  submitPluginResult(traceId: string, pluginName: string, assertionId: string, status: string, score: number, explanation: string, options?: {
90
92
  timeout?: number;
91
93
  }): Promise<boolean>;
92
94
  queryDrift(assertionId: string, windowSize?: number, options?: {
93
95
  timeout?: number;
96
+ staticThreshold?: number;
94
97
  }): Promise<DriftReport>;
95
98
  generateUserMessage(persona: SimulatePersona, conversationHistory: readonly ConversationMessage[], faultConfig?: SimulateFaultConfig, options?: {
96
99
  timeout?: number;
@@ -103,6 +106,70 @@ declare class AttestClient {
103
106
  */
104
107
  declare function requireSimulationCapability(init: InitializeResult | null | undefined): void;
105
108
 
109
+ declare class EngineManager {
110
+ private enginePath;
111
+ private readonly logLevel;
112
+ private readonly initializeTimeoutMs;
113
+ private process;
114
+ private reader;
115
+ private initialized;
116
+ private initResult;
117
+ private routedClient;
118
+ /** The running engine's stderr tail; each launch owns a fresh one so a killed engine cannot write into it. */
119
+ private stderr;
120
+ private restarting;
121
+ private configuredBudget;
122
+ private usedJudgeCalls;
123
+ private usedUsd;
124
+ private heldJudgeCalls;
125
+ private heldUsd;
126
+ /** Spend the SDK cannot observe may have happened; a restart then leaves no budget. */
127
+ private unknownSpend;
128
+ constructor(options?: {
129
+ enginePath?: string;
130
+ logLevel?: string;
131
+ initializeTimeoutMs?: number;
132
+ });
133
+ /**
134
+ * Start the engine and complete the `initialize` handshake. Rejects when the engine exits,
135
+ * closes its pipes or fails to answer within the initialize timeout; the error carries the
136
+ * tail of the engine's stderr.
137
+ */
138
+ start(): Promise<InitializeResult>;
139
+ /** Spawn the engine and run `initialize` on it; the engine is stopped when that fails. */
140
+ private launch;
141
+ /** Send initialize and check the engine's version and capabilities. */
142
+ private initialize;
143
+ stop(): Promise<void>;
144
+ /** Close the pipes, reject pending requests and stop the engine process (SIGKILL when `force`). */
145
+ private terminate;
146
+ /**
147
+ * Replace the engine after a request timed out: its late response can no longer be told
148
+ * apart from a new one. The new engine gets the remaining session budget: the configured
149
+ * limit minus the usage already returned, minus what the abandoned requests may still have
150
+ * reserved. A restart that leaves no budget makes later model calls
151
+ * `not_evaluated(budget_exhausted)`.
152
+ */
153
+ restartAfterTimeout(abandoned: readonly BudgetReservation[]): Promise<void>;
154
+ /** An engine reply the SDK cannot account for (no usage, an RPC error, a dropped reply) may have spent budget. */
155
+ markUnknownSpend(): void;
156
+ private restart;
157
+ /** Count the usage an evaluate_batch result reported against the session budget. */
158
+ recordUsage(usage: Usage): void;
159
+ /** Resolves once an engine restart in progress has finished. */
160
+ whenReady(): Promise<void>;
161
+ /** Tail of the engine's stderr: the reason an engine died. */
162
+ get stderrTail(): string;
163
+ /** The client that routes this engine's responses by id; replaced when the engine restarts. */
164
+ get client(): AttestClient | undefined;
165
+ sendRequest(method: string, params: Record<string, unknown>): Promise<unknown>;
166
+ get isRunning(): boolean;
167
+ /** Expose process for AttestClient reader loop access */
168
+ get childProcess(): ChildProcess | null;
169
+ /** Expose readline interface for AttestClient reader loop */
170
+ get readlineInterface(): Interface | null;
171
+ }
172
+
106
173
  declare class TraceBuilder {
107
174
  private traceId;
108
175
  private agentId;
@@ -291,11 +358,23 @@ declare class WorldChecks<C> {
291
358
  where?: Where;
292
359
  }): C;
293
360
  }
361
+ type ChainObserver = (chain: ExpectChain) => void;
362
+ /**
363
+ * Register the function called with every new {@link ExpectChain}. The Vitest integration uses
364
+ * it to fail a test that builds a chain it never evaluates. One observer at a time; pass
365
+ * `undefined` to remove it.
366
+ */
367
+ declare function setChainObserver(observer: ChainObserver | undefined): void;
294
368
  declare class ExpectChain {
295
369
  private readonly _result;
296
370
  private readonly _assertions;
297
371
  private _worldBytes;
372
+ private _evaluated;
298
373
  constructor(result: AgentResult);
374
+ /** True once the chain was handed to an evaluation. */
375
+ get evaluated(): boolean;
376
+ /** Record that the chain was handed to an evaluation. */
377
+ markEvaluated(): void;
299
378
  get assertions(): Assertion[];
300
379
  get trace(): Trace;
301
380
  private add;
@@ -1005,6 +1084,35 @@ declare class ExpectChain {
1005
1084
  */
1006
1085
  declare function attestExpect(result: AgentResult | Trace): ExpectChain;
1007
1086
 
1087
+ /**
1088
+ * Assertion ids, id scheme 2 (contract C3). Normative text:
1089
+ * proto/attest/v1/protocol-spec.md, "Assertion ids (id scheme 2)"; executable form:
1090
+ * protocol-tests/ids/assertion-ids.json.
1091
+ */
1092
+ declare const ID_SCHEME = 2;
1093
+ /** What an id is computed for; names the assertion in rejection errors. */
1094
+ interface AssertionIdentity {
1095
+ readonly scopeId: string;
1096
+ readonly type: string;
1097
+ readonly spec: unknown;
1098
+ }
1099
+ /** RFC 8785 (JCS) serialization of an assertion spec; no spec is `{}`. */
1100
+ declare function canonicalSpec(spec: unknown, identity?: AssertionIdentity): string;
1101
+ /** The exact bytes (as text) that are hashed: the JCS array `[scope_id, type, spec, occurrence_index]`. */
1102
+ declare function canonicalIdInput(scopeId: string, type: string, spec: unknown, occurrenceIndex: number): string;
1103
+ /** `a2_` plus the first 32 hex characters of SHA-256 over the UTF-8 bytes of the id input. */
1104
+ declare function assertionIdOf(scopeId: string, type: string, spec: unknown, occurrenceIndex: number): string;
1105
+ /**
1106
+ * Assigns ids inside one scope. The occurrence index of an assertion is the number of earlier
1107
+ * assertions given an id here with the same (type, canonical spec), in call order.
1108
+ */
1109
+ declare class AssertionIdAssigner {
1110
+ readonly scopeId: string;
1111
+ private readonly seen;
1112
+ constructor(scopeId: string);
1113
+ next(type: string, spec: unknown): string;
1114
+ }
1115
+
1008
1116
  /**
1009
1117
  * Capture: turn a run into a verified fixture, and fixtures into tests.
1010
1118
  *
@@ -1015,7 +1123,9 @@ declare function attestExpect(result: AgentResult | Trace): ExpectChain;
1015
1123
  * - `record()` captures the code in a callback in this process.
1016
1124
  * - `loadFixture()` reads a fixture (`proto/attest/v1/fixture-v1.schema.json`).
1017
1125
  * - `emitVitest()` writes the fixture's drafted contracts as a Vitest file, using the
1018
- * `attestExpect()` DSL. `attest capture` and `record()` call it.
1126
+ * `attestExpect()` DSL. `attest capture` and `record()` call it. For a fixture captured
1127
+ * from a command, the test re-runs that command under strict replay (`replayRun()`) and
1128
+ * evaluates the contracts on the new run's trace.
1019
1129
  */
1020
1130
 
1021
1131
  /** A capture did not produce a fixture, or produced one that is not verified. */
@@ -1067,6 +1177,15 @@ interface Fixture {
1067
1177
  declare function loadFixture(file: string | URL): Fixture;
1068
1178
  /** The `ExpectChain` method call that emits `contract`'s spec; throws when there is none. */
1069
1179
  declare function dslCall(contract: Contract): string;
1180
+ /**
1181
+ * Where a drafted replay test re-runs the fixture's source command: `cwd` is the directory
1182
+ * the capture ran in (the command's relative paths resolve there), `world` the world file
1183
+ * the capture hosted, if any.
1184
+ */
1185
+ interface Rerun {
1186
+ cwd: string;
1187
+ world?: string;
1188
+ }
1070
1189
  /**
1071
1190
  * Where the drafted test for a fixture goes: `tests/attest/<name>.test.ts` for
1072
1191
  * `tests/attest/fixtures/<name>.attest.json`.
@@ -1075,18 +1194,56 @@ declare function testPathFor(fixturePath: string): string;
1075
1194
  /**
1076
1195
  * Render a fixture's drafted contracts as a Vitest module.
1077
1196
  *
1078
- * A verified fixture becomes a test that enforces every contract with `evaluate`. An
1079
- * incident reproduction (`expect_fail`) becomes a test that checks, with `inspect`, that
1080
- * the expected failures still fail and the other contracts still pass; it stays green
1081
- * while it reproduces the incident.
1197
+ * With `rerun` and a fixture captured from a command, the test re-runs that command under
1198
+ * strict replay and evaluates every contract on the new run's trace (an incident
1199
+ * reproduction included: it passes once the agent no longer reproduces the incident).
1200
+ * Otherwise a verified fixture becomes a test that enforces every contract on the recorded
1201
+ * trace with `evaluate`, and an incident reproduction (`expect_fail`) a test that checks,
1202
+ * with `inspect`, that the expected failures still fail and the other contracts still
1203
+ * pass; it stays green while it reproduces the incident.
1082
1204
  */
1083
- declare function renderVitest(fixture: Fixture, fixturePath: string, testPath: string): string;
1205
+ declare function renderVitest(fixture: Fixture, fixturePath: string, testPath: string, rerun?: Rerun): string;
1084
1206
  /**
1085
1207
  * Write the drafted Vitest file for a fixture unless it already exists. Returns the path
1086
1208
  * written, or null when a test file is already there (re-capturing never overwrites an
1087
- * edited test).
1209
+ * edited test). `rerun` makes the test of a command fixture re-run the command under strict
1210
+ * replay (see `renderVitest`).
1088
1211
  */
1089
- declare function emitVitest(fixturePath: string): string | null;
1212
+ declare function emitVitest(fixturePath: string, rerun?: Rerun): string | null;
1213
+ interface ReplayRunOptions {
1214
+ /** The argv to run, written in the reviewed test; must equal the fixture's `source.command`. */
1215
+ command: readonly string[];
1216
+ /** Directory to run the command in (the one the capture ran in). */
1217
+ cwd?: string | URL;
1218
+ /** World file the capture hosted, when the fixture has a `world` section. */
1219
+ world?: string | URL;
1220
+ /** Engine binary; default is the SDK's discovery order. */
1221
+ enginePath?: string;
1222
+ /** Milliseconds before the run is abandoned (default 300000). */
1223
+ timeoutMs?: number;
1224
+ }
1225
+ /**
1226
+ * Re-run a fixture's source command under strict replay and return the new run's trace.
1227
+ *
1228
+ * Runs `attest-engine replay --mode strict --no-live --trace-out ... -- <command>`: the
1229
+ * OpenAI- and Anthropic-compatible calls the command makes are answered from the fixture's
1230
+ * recorded exchanges, and a call that matches no recording fails. Other providers are not
1231
+ * blocked: the replay proxy redirects only `OPENAI_BASE_URL` and `ANTHROPIC_BASE_URL`, and
1232
+ * `ATTEST_EGRESS=block` only affects processes that install attest's guard.
1233
+ * `ANTHROPIC_API_KEY`, `GOOGLE_API_KEY` and `GEMINI_API_KEY` are removed from the
1234
+ * environment and `OPENAI_API_KEY` is a placeholder; run without other providers'
1235
+ * credentials. The new run's trace is written unredacted to a private temporary directory
1236
+ * removed afterwards.
1237
+ *
1238
+ * The fixture file can be changed by a pull request, so its `source.command` is never run:
1239
+ * `options.command` is, and the call rejects when the fixture's differs, or when it has
1240
+ * redacted or templated (`{{id_N}}`) arguments.
1241
+ *
1242
+ * Rejects with `CaptureError` when the fixture has no replayable source command, a request
1243
+ * matched no recording, the command exited differently than when recorded, or no trace came
1244
+ * out.
1245
+ */
1246
+ declare function replayRun(fixture: string | URL, options: ReplayRunOptions): Promise<Trace>;
1090
1247
  /** A running in-process capture. */
1091
1248
  interface Capture {
1092
1249
  name: string;
@@ -1345,6 +1502,17 @@ declare class PluginRegistry {
1345
1502
  listPlugins(pluginType?: string): string[];
1346
1503
  }
1347
1504
  declare function executePluginAssertion(plugin: AttestPlugin, trace: Trace, spec: Record<string, unknown>, client: AttestClient, traceId: string, assertionId: string, timeoutMs?: number): Promise<PluginResult>;
1505
+ /**
1506
+ * Run the registered plugin of every `plugin` assertion and submit its result to the engine
1507
+ * (`submit_plugin_result`) so the following `evaluate_batch` finds it. Order matters: submit
1508
+ * before evaluate. A plugin assertion whose plugin is not registered is left without a
1509
+ * submission, so the engine reports it `not_evaluated` (`missing_evidence`) instead of a failure.
1510
+ */
1511
+ declare function submitPluginResults(registry: PluginRegistry, client: AttestClient, trace: Trace, assertions: readonly {
1512
+ assertion_id: string;
1513
+ type: string;
1514
+ spec: Record<string, unknown>;
1515
+ }[]): Promise<void>;
1348
1516
  /** Explicitly register a plugin instance into the registry. */
1349
1517
  declare function registerPlugin(registry: PluginRegistry, name: string, plugin: AttestPlugin): void;
1350
1518
  /**
@@ -1365,16 +1533,26 @@ declare class Sampler {
1365
1533
  constructor(rate: number);
1366
1534
  shouldSample(): boolean;
1367
1535
  }
1536
+ type AlertStatus = "drift_detected" | "hard_fail";
1537
+ /** Webhook JSON body shared by both SDK runners. Every key is always present. */
1368
1538
  interface AlertPayload {
1369
- drift_type: string;
1370
- score: number | string;
1371
- trace_id: string;
1372
- [key: string]: unknown;
1539
+ runner: string;
1540
+ assertion_id: string;
1541
+ /** Alert trigger: `hard_fail` wins over `drift_detected` when both hold. */
1542
+ status: AlertStatus;
1543
+ /** Status of the evaluated assertion result. */
1544
+ result_status: "pass" | "soft_fail" | "hard_fail";
1545
+ score: number | null;
1546
+ baseline_mean: number | null;
1547
+ baseline_count: number | null;
1548
+ static_threshold: number;
1549
+ trace_id: string | null;
1373
1550
  }
1374
1551
  declare class AlertDispatcher extends EventEmitter {
1375
1552
  private readonly webhookUrl;
1376
1553
  private readonly slackUrl;
1377
- constructor(webhookUrl?: string, slackUrl?: string);
1554
+ private readonly timeoutMs;
1555
+ constructor(webhookUrl?: string, slackUrl?: string, timeoutMs?: number);
1378
1556
  dispatch(alert: AlertPayload): Promise<void>;
1379
1557
  private postJson;
1380
1558
  }
@@ -1385,22 +1563,53 @@ declare class ContinuousEvalRunner {
1385
1563
  private readonly dispatcher;
1386
1564
  private readonly queue;
1387
1565
  private readonly maxSize;
1566
+ private readonly name;
1567
+ private readonly drainTimeoutMs;
1568
+ private readonly onError;
1569
+ private readonly inFlight;
1570
+ private chain;
1388
1571
  private running;
1389
1572
  private loopTimer;
1390
- constructor(client: AttestClient, assertions: readonly Assertion[], options?: {
1573
+ constructor(client: AttestClient, assertions: readonly Assertion[], options: {
1574
+ /** Id scope of the assertions (C3): their ids derive from this name, so keep it stable. */
1575
+ name: string;
1391
1576
  sampleRate?: number;
1392
1577
  alertWebhook?: string;
1393
1578
  alertSlackUrl?: string;
1394
1579
  maxSize?: number;
1580
+ /** Called with every evaluation, drift-read and dispatch error (also logged and emitted as `alertError`). */
1581
+ onError?: (error: unknown) => void;
1582
+ /** Bound on one alert POST (default 10000). */
1583
+ dispatchTimeoutMs?: number;
1584
+ /** Bound on how long `stop()` waits for in-flight work (default 15000). */
1585
+ drainTimeoutMs?: number;
1395
1586
  });
1396
1587
  get alertEmitter(): AlertDispatcher;
1588
+ /** Runs `fn` after every previously queued evaluation, so evaluate → query_drift pairs never interleave. */
1589
+ private serialize;
1590
+ /** Surfaces an error to the log and `onError` without ever throwing or leaving a rejected promise. */
1591
+ private reportError;
1592
+ private logObserverFailure;
1593
+ private emitError;
1397
1594
  evaluateTrace(trace: Trace): Promise<EvaluateBatchResult | null>;
1595
+ private evaluateAndAlert;
1596
+ private staticThresholdFor;
1597
+ private alertIfNeeded;
1398
1598
  submit(trace: Trace): boolean;
1399
1599
  start(): void;
1400
- stop(): void;
1600
+ /**
1601
+ * Stops the loop, then processes the traces already queued and waits for in-flight evaluations
1602
+ * and dispatches, all bounded by `drainTimeoutMs`. At the bound the whole remaining queue is
1603
+ * discarded (nothing is evaluated after the timeout and nothing is left for a restart), the
1604
+ * evaluation already in flight is left to finish, and the timeout with the number of discarded
1605
+ * traces is reported through `onError`.
1606
+ */
1607
+ stop(): Promise<void>;
1608
+ private drainQueue;
1401
1609
  get queueLength(): number;
1402
1610
  get isRunning(): boolean;
1403
1611
  private scheduleNext;
1612
+ private tick;
1404
1613
  }
1405
1614
 
1406
1615
  /**
@@ -1594,34 +1803,81 @@ declare class ManualAdapter {
1594
1803
  createBuilder(): TraceBuilder;
1595
1804
  }
1596
1805
 
1597
- interface OpenAIUsage {
1806
+ /** Options every single-call provider adapter accepts. */
1807
+ interface ProviderCallOptions {
1808
+ inputMessages?: Record<string, unknown>[];
1809
+ costUsd?: number;
1810
+ /** Explicit latency; wins over the span between `startedAtMs` and `endedAtMs`. */
1811
+ latencyMs?: number;
1812
+ /** Wall-clock ms when the request was sent. */
1813
+ startedAtMs?: number;
1814
+ /** Wall-clock ms when the response was received. */
1815
+ endedAtMs?: number;
1816
+ }
1817
+
1818
+ interface ChatUsage {
1819
+ prompt_tokens?: number;
1820
+ completion_tokens?: number;
1598
1821
  total_tokens?: number;
1599
1822
  }
1600
- interface OpenAIToolCall {
1823
+ interface ChatToolCall {
1601
1824
  function: {
1602
1825
  name: string;
1603
1826
  arguments: string;
1604
1827
  };
1605
1828
  }
1606
- interface OpenAIMessage {
1829
+ interface ChatMessage {
1607
1830
  content: string | null;
1608
- tool_calls?: OpenAIToolCall[];
1609
- }
1610
- interface OpenAIChoice {
1611
- message: OpenAIMessage;
1831
+ refusal?: string | null;
1832
+ tool_calls?: ChatToolCall[];
1612
1833
  }
1834
+ /** An OpenAI Chat Completions response. */
1613
1835
  interface OpenAIChatCompletion {
1836
+ object?: string;
1837
+ model?: string;
1838
+ usage?: ChatUsage;
1839
+ choices: {
1840
+ message: ChatMessage;
1841
+ }[];
1842
+ }
1843
+ type ResponsesContentPart = {
1844
+ type: "output_text";
1845
+ text: string;
1846
+ } | {
1847
+ type: "refusal";
1848
+ refusal: string;
1849
+ };
1850
+ type ResponsesOutputItem = {
1851
+ type: "message";
1852
+ content: ResponsesContentPart[];
1853
+ } | {
1854
+ type: "function_call";
1855
+ name: string;
1856
+ arguments: string;
1857
+ } | {
1858
+ type: string;
1859
+ };
1860
+ /** An OpenAI Responses API response. */
1861
+ interface OpenAIResponse {
1862
+ object: "response";
1614
1863
  model?: string;
1615
- usage?: OpenAIUsage;
1616
- choices: OpenAIChoice[];
1864
+ usage?: {
1865
+ input_tokens?: number;
1866
+ output_tokens?: number;
1867
+ total_tokens?: number;
1868
+ };
1869
+ output: ResponsesOutputItem[];
1617
1870
  }
1871
+ /**
1872
+ * Captures OpenAI Chat Completions and Responses API calls into Attest traces.
1873
+ *
1874
+ * A model refusal is the response's message: it becomes the completion and the trace
1875
+ * output, and is also recorded as `refusal` on the llm_call step.
1876
+ */
1618
1877
  declare class OpenAIAdapter {
1619
1878
  private readonly agentId;
1620
1879
  constructor(agentId?: string);
1621
- traceFromResponse(response: OpenAIChatCompletion, options?: {
1622
- inputMessages?: Record<string, unknown>[];
1623
- costUsd?: number;
1624
- latencyMs?: number;
1880
+ traceFromResponse(response: OpenAIChatCompletion | OpenAIResponse, options?: ProviderCallOptions & {
1625
1881
  structuredOutput?: Record<string, unknown>;
1626
1882
  }): Trace;
1627
1883
  }
@@ -1630,29 +1886,29 @@ interface AnthropicTextBlock {
1630
1886
  type: "text";
1631
1887
  text: string;
1632
1888
  }
1889
+ /** A client-executed (`tool_use`) or Anthropic-executed (`server_tool_use`) tool call. */
1633
1890
  interface AnthropicToolUseBlock {
1634
- type: "tool_use";
1891
+ type: "tool_use" | "server_tool_use";
1635
1892
  name: string;
1636
1893
  input: Record<string, unknown>;
1637
1894
  }
1638
- type AnthropicContentBlock = AnthropicTextBlock | AnthropicToolUseBlock;
1639
- interface AnthropicUsage {
1640
- input_tokens: number;
1641
- output_tokens: number;
1895
+ /** Any other block (tool results, thinking, ...): carries no text or call of its own. */
1896
+ interface AnthropicOtherBlock {
1897
+ type: string;
1642
1898
  }
1899
+ type AnthropicContentBlock = AnthropicTextBlock | AnthropicToolUseBlock | AnthropicOtherBlock;
1643
1900
  interface AnthropicMessage {
1644
1901
  model?: string;
1645
- usage?: AnthropicUsage;
1902
+ usage?: {
1903
+ input_tokens: number;
1904
+ output_tokens: number;
1905
+ };
1646
1906
  content: AnthropicContentBlock[];
1647
1907
  }
1648
1908
  declare class AnthropicAdapter {
1649
1909
  private readonly agentId;
1650
1910
  constructor(agentId?: string);
1651
- traceFromResponse(response: AnthropicMessage, options?: {
1652
- inputMessages?: Record<string, unknown>[];
1653
- costUsd?: number;
1654
- latencyMs?: number;
1655
- }): Trace;
1911
+ traceFromResponse(response: AnthropicMessage, options?: ProviderCallOptions): Trace;
1656
1912
  }
1657
1913
 
1658
1914
  interface GeminiFunctionCall {
@@ -1661,6 +1917,8 @@ interface GeminiFunctionCall {
1661
1917
  }
1662
1918
  interface GeminiPart {
1663
1919
  text?: string;
1920
+ /** Thought-summary parts are the model's reasoning, not the answer. */
1921
+ thought?: boolean;
1664
1922
  functionCall?: GeminiFunctionCall;
1665
1923
  }
1666
1924
  interface GeminiCandidate {
@@ -1668,25 +1926,39 @@ interface GeminiCandidate {
1668
1926
  parts: GeminiPart[];
1669
1927
  };
1670
1928
  }
1929
+ interface GeminiUsage {
1930
+ promptTokenCount?: number;
1931
+ candidatesTokenCount?: number;
1932
+ totalTokenCount?: number;
1933
+ }
1671
1934
  interface GeminiResponse {
1672
1935
  text?: string;
1936
+ modelVersion?: string;
1937
+ usageMetadata?: GeminiUsage;
1673
1938
  candidates?: GeminiCandidate[];
1674
1939
  }
1940
+ interface GeminiOptions extends ProviderCallOptions {
1941
+ /** A plain-text prompt; `inputMessages` is the standard way to pass the input. */
1942
+ inputText?: string;
1943
+ /** Overrides the response's `modelVersion`. */
1944
+ model?: string;
1945
+ }
1675
1946
  declare class GeminiAdapter {
1676
1947
  private readonly agentId;
1677
1948
  constructor(agentId?: string);
1678
- traceFromResponse(response: GeminiResponse, options?: {
1679
- inputText?: string;
1680
- costUsd?: number;
1681
- latencyMs?: number;
1682
- model?: string;
1683
- }): Trace;
1949
+ traceFromResponse(response: GeminiResponse, options?: GeminiOptions): Trace;
1684
1950
  }
1685
1951
 
1686
1952
  interface OllamaResponse {
1687
1953
  model?: string;
1688
1954
  message?: {
1689
1955
  content?: string;
1956
+ tool_calls?: {
1957
+ function?: {
1958
+ name?: string;
1959
+ arguments?: Record<string, unknown>;
1960
+ };
1961
+ }[];
1690
1962
  };
1691
1963
  eval_count?: number;
1692
1964
  prompt_eval_count?: number;
@@ -1694,10 +1966,7 @@ interface OllamaResponse {
1694
1966
  declare class OllamaAdapter {
1695
1967
  private readonly agentId;
1696
1968
  constructor(agentId?: string);
1697
- traceFromResponse(response: OllamaResponse, options?: {
1698
- inputMessages?: Record<string, unknown>[];
1699
- latencyMs?: number;
1700
- }): Trace;
1969
+ traceFromResponse(response: OllamaResponse, options?: Omit<ProviderCallOptions, "costUsd">): Trace;
1701
1970
  }
1702
1971
 
1703
1972
  type AttributeValue = string | number | boolean | readonly (string | number | boolean | null | undefined)[];
@@ -1748,58 +2017,78 @@ declare class OTelAdapter {
1748
2017
  }
1749
2018
 
1750
2019
  /**
1751
- * Minimal type definitions for `@langchain/core` callback handler.
1752
- * These avoid a hard dependency on `@langchain/core` — the actual types
1753
- * are checked at runtime when the user provides real LangChain objects.
2020
+ * The part of `@langchain/core`'s `BaseCallbackHandler` this adapter relies on. It is
2021
+ * declared locally so the published typings do not import an optional peer dependency.
1754
2022
  */
1755
- interface LangChainLLMResult {
1756
- generations: Array<Array<{
1757
- text: string;
1758
- [key: string]: unknown;
1759
- }>>;
1760
- llmOutput?: Record<string, unknown>;
1761
- }
1762
- interface LangChainToolEnd {
1763
- name?: string;
1764
- output: string;
1765
- [key: string]: unknown;
1766
- }
1767
- interface LangChainChainValues {
1768
- [key: string]: unknown;
1769
- }
1770
- declare class LangChainAdapter {
2023
+ interface CallbackHandlerBase {
2024
+ name: string;
2025
+ awaitHandlers: boolean;
2026
+ raiseError: boolean;
2027
+ }
2028
+ type CallbackHandlerConstructor = new () => CallbackHandlerBase;
2029
+ declare const BaseCallbackHandler: CallbackHandlerConstructor;
2030
+ type Values = Record<string, unknown>;
2031
+ interface Message {
2032
+ content?: unknown;
2033
+ usage_metadata?: {
2034
+ input_tokens?: number;
2035
+ output_tokens?: number;
2036
+ total_tokens?: number;
2037
+ };
2038
+ }
2039
+ interface LLMResult {
2040
+ generations: {
2041
+ text?: string;
2042
+ message?: Message;
2043
+ }[][];
2044
+ llmOutput?: Values;
2045
+ }
2046
+ /**
2047
+ * Captures a LangChain.js run into an Attest trace.
2048
+ *
2049
+ * A `BaseCallbackHandler` from `@langchain/core`: pass it in `callbacks` and call
2050
+ * `buildTrace()` once the run resolves. Model and tool runs are keyed by `runId`, so
2051
+ * interleaved runs are attributed correctly; steps are recorded in completion order.
2052
+ *
2053
+ * ```ts
2054
+ * const adapter = new LangChainAdapter("my-agent");
2055
+ * await agent.invoke(input, { callbacks: [adapter] });
2056
+ * const trace = adapter.buildTrace();
2057
+ * ```
2058
+ */
2059
+ declare class LangChainAdapter extends BaseCallbackHandler {
2060
+ name: string;
1771
2061
  private readonly agentId;
1772
- private readonly llmCalls;
1773
- private readonly toolCalls;
1774
- private chainOutput;
1775
- private startTime;
2062
+ private input;
2063
+ private output;
2064
+ private steps;
2065
+ private llmStarts;
2066
+ private toolStarts;
2067
+ private totalTokens;
2068
+ private model;
2069
+ private startedAtMs;
2070
+ private endedAtMs;
1776
2071
  constructor(agentId?: string);
1777
- /**
1778
- * Reset accumulated state for a new trace capture session.
1779
- */
2072
+ /** Handlers are shared by the callback manager, never cloned: a clone would lose the capture. */
2073
+ copy(): this;
2074
+ /** Reset accumulated state for a new capture session. */
1780
2075
  reset(): void;
2076
+ private now;
2077
+ handleChainStart(_chain: unknown, inputs: Values, _runId: string, parentRunId?: string): void;
2078
+ handleChainEnd(outputs: Values, _runId: string, parentRunId?: string): void;
2079
+ handleChatModelStart(_llm: unknown, _messages: unknown[][], runId: string, _parentRunId?: string, extraParams?: Values): void;
2080
+ handleLLMStart(_llm: unknown, _prompts: string[], runId: string, _parentRunId?: string, extraParams?: Values): void;
2081
+ handleLLMEnd(output: LLMResult, runId: string): void;
2082
+ handleToolStart(tool: unknown, input: string, runId: string, _parentRunId?: string, _tags?: string[], _metadata?: Values, runName?: string): void;
2083
+ handleToolEnd(output: unknown, runId: string): void;
2084
+ handleToolError(error: unknown, runId: string): void;
2085
+ private finishTool;
2086
+ private step;
1781
2087
  /**
1782
- * Call this when a chain starts (maps to `handleChainStart`).
1783
- */
1784
- handleChainStart(): void;
1785
- /**
1786
- * Call this when an LLM call completes (maps to `handleLLMEnd`).
1787
- */
1788
- handleLLMEnd(output: LangChainLLMResult): void;
1789
- /**
1790
- * Call this when a tool call completes (maps to `handleToolEnd`).
1791
- */
1792
- handleToolEnd(output: string, metadata?: LangChainToolEnd): void;
1793
- /**
1794
- * Call this when a chain completes (maps to `handleChainEnd`).
1795
- */
1796
- handleChainEnd(output: LangChainChainValues): void;
1797
- /**
1798
- * Build a Trace from accumulated callback events.
2088
+ * Build a Trace from the captured callback events.
1799
2089
  *
1800
- * @param options.costUsd - Total cost override.
2090
+ * @param options.costUsd - Total cost.
1801
2091
  * @param options.model - Model name override.
1802
- * @returns A finalized Trace object.
1803
2092
  */
1804
2093
  buildTrace(options?: {
1805
2094
  costUsd?: number;
@@ -1811,5 +2100,5 @@ interface TraceAdapter {
1811
2100
  traceFromResponse(response: unknown, options?: Record<string, unknown>): Trace;
1812
2101
  }
1813
2102
 
1814
- export { ADVERSARIAL_USER, Agent, AgentResult, AlertDispatcher, AnthropicAdapter, Assertion, AssertionResult, AttestClient, BatchBudget, BatchContext, CONFUSED_USER, COOPERATIVE_USER, CaptureError, ContinuousEvalRunner, ConversationMessage, DriftReport, ENGINE_VERSION, EgressBlockedError, EngineManager, EvaluateBatchResult, ExpectChain, FRIENDLY_USER, GeminiAdapter, InitializeResult, LangChainAdapter, ManualAdapter, MockToolRegistry, OTelAdapter, OllamaAdapter, OpenAIAdapter, Outcome, PluginRegistry, ProtocolDiagnostic, ProtocolLogger, RepeatResult, ReplayError, ReplayMissError, ReplayNotEvaluatedError, Sampler, ScenarioResult, SimulateFaultConfig, SimulatePersona, Step, TIER_1, TIER_2, TIER_3, Trace, TraceBuilder, TraceTree, Usage, VERSION, WorldChecks, WorldError, activeBuilder, addUsage, agent, attestExpect, computeAgreement, config, delegate, dslCall, egressBlockRequested, emitVitest, executePluginAssertion, faultInject, firstMiss, formatUsage, getSampleRate, installEgressGuard, isProviderHost, isSimulationMode, liveLine, loadFixture, loadLabelsCSV, loadLabelsJSONL, loadPackagePlugins, mockTool, parseUsage, promptHash, record, registerPlugin, renderAssertionFailure, renderDiagnostics, renderSummary, renderVerdictDiagnostics, renderVitest, repeat, requireSimulationCapability, resetConfig, resolveMode, scenario, simulationEnvEnabled, startReplay, startWorld, sumUsage, testPathFor, tier, uninstallEgressGuard, usageSummaryLine, zeroUsage };
1815
- export type { AgreementResult, AlertPayload, AttestClientOptions, AttestPlugin, Capture, Contract, ContractResult, FaultInjectOptions, Fixture, LabelPair, LabeledRecord, MockTool, MockToolFn, PluginResult, RecordOptions, RecordResult, RenderOptions, ReplayMode, ReplayModeSetting, ReplayOptions, ReplayRecord, ReplaySession, ReplaySummary, ScenarioConfig, ScenarioContext, ScenarioOptions, TraceAdapter, Verdict, WorldOptions, WorldSession, WorldSnapshot };
2103
+ export { ADVERSARIAL_USER, Agent, AgentResult, AlertDispatcher, AnthropicAdapter, Assertion, AssertionIdAssigner, AssertionResult, AttestClient, BatchBudget, BatchContext, CONFUSED_USER, COOPERATIVE_USER, CaptureError, ContinuousEvalRunner, ConversationMessage, DriftReport, ENGINE_VERSION, EgressBlockedError, EngineManager, EvaluateBatchResult, ExpectChain, FRIENDLY_USER, GeminiAdapter, ID_SCHEME, InitializeResult, LangChainAdapter, ManualAdapter, MockToolRegistry, OTelAdapter, OllamaAdapter, OpenAIAdapter, Outcome, PluginRegistry, ProtocolDiagnostic, ProtocolLogger, RepeatResult, ReplayError, ReplayMissError, ReplayNotEvaluatedError, Sampler, ScenarioResult, SimulateFaultConfig, SimulatePersona, Step, TIER_1, TIER_2, TIER_3, Trace, TraceBuilder, TraceTree, Usage, VERSION, WorldChecks, WorldError, activeBuilder, addUsage, agent, assertionIdOf, attestExpect, canonicalIdInput, canonicalSpec, computeAgreement, config, delegate, dslCall, egressBlockRequested, emitVitest, executePluginAssertion, faultInject, firstMiss, formatUsage, getSampleRate, installEgressGuard, isProviderHost, isSimulationMode, liveLine, loadFixture, loadLabelsCSV, loadLabelsJSONL, loadPackagePlugins, mockTool, parseUsage, promptHash, record, registerPlugin, renderAssertionFailure, renderDiagnostics, renderSummary, renderVerdictDiagnostics, renderVitest, repeat, replayRun, requireSimulationCapability, resetConfig, resolveMode, scenario, setChainObserver, simulationEnvEnabled, startReplay, startWorld, submitPluginResults, sumUsage, testPathFor, tier, uninstallEgressGuard, usageSummaryLine, zeroUsage };
2104
+ export type { AgreementResult, AlertPayload, AttestClientOptions, AttestPlugin, Capture, Contract, ContractResult, FaultInjectOptions, Fixture, LabelPair, LabeledRecord, MockTool, MockToolFn, PluginResult, RecordOptions, RecordResult, RenderOptions, ReplayMode, ReplayModeSetting, ReplayOptions, ReplayRecord, ReplayRunOptions, ReplaySession, ReplaySummary, Rerun, ScenarioConfig, ScenarioContext, ScenarioOptions, TraceAdapter, Verdict, WorldOptions, WorldSession, WorldSnapshot };