@responsibleai/agent-hooks 0.1.0-alpha.2
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 +47 -0
- package/agent-hooks.linux-x64-gnu.node +0 -0
- package/binding.d.ts +18 -0
- package/binding.js +327 -0
- package/dist/builder.d.ts +37 -0
- package/dist/builder.js +94 -0
- package/dist/ctk/index.d.ts +70 -0
- package/dist/ctk/index.js +18 -0
- package/dist/ctk/reference.d.ts +20 -0
- package/dist/ctk/reference.js +124 -0
- package/dist/ctk/runner.d.ts +26 -0
- package/dist/ctk/runner.js +144 -0
- package/dist/emitter.d.ts +111 -0
- package/dist/emitter.js +615 -0
- package/dist/index.d.ts +328 -0
- package/dist/index.js +273 -0
- package/dist/native.d.ts +28 -0
- package/dist/native.js +56 -0
- package/package.json +36 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-hooks: framework-neutral agent lifecycle hook contract.
|
|
3
|
+
* Implements AGENT-HOOKS-0.1. Lifted and adapted from
|
|
4
|
+
* `policy-engine/sdk/node/src/index.ts`.
|
|
5
|
+
*/
|
|
6
|
+
import { AgentHooksCoreError } from "./native";
|
|
7
|
+
export { AgentHooksCoreError };
|
|
8
|
+
/** Spec version this SDK implements (§4.1 `spec` field). */
|
|
9
|
+
export declare const SPEC_VERSION = "agent-hooks/0.1";
|
|
10
|
+
/** Name of the default identity provider (§10.1, §10.2). */
|
|
11
|
+
export declare const JCS_SHA256 = "jcs-sha256";
|
|
12
|
+
export type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
13
|
+
[key: string]: JsonValue;
|
|
14
|
+
};
|
|
15
|
+
/** The closed set of agent lifecycle interception points (§3). */
|
|
16
|
+
export declare const InterceptionPoint: Readonly<{
|
|
17
|
+
readonly AgentStartup: "agent_startup";
|
|
18
|
+
readonly Input: "input";
|
|
19
|
+
readonly PreModelCall: "pre_model_call";
|
|
20
|
+
readonly PostModelCall: "post_model_call";
|
|
21
|
+
readonly PreToolCall: "pre_tool_call";
|
|
22
|
+
readonly PostToolCall: "post_tool_call";
|
|
23
|
+
readonly Output: "output";
|
|
24
|
+
readonly AgentShutdown: "agent_shutdown";
|
|
25
|
+
}>;
|
|
26
|
+
export type InterceptionPoint = (typeof InterceptionPoint)[keyof typeof InterceptionPoint];
|
|
27
|
+
/** Whether a `transform` verdict is permitted at `hp` (§3, §4.3). */
|
|
28
|
+
export declare function transformPermitted(hp: InterceptionPoint): boolean;
|
|
29
|
+
/** Verdict decision values (§5.1). Three, closed: `warn` is `allow` +
|
|
30
|
+
* `warnings[]`; `escalate` is `deny` + an `approval` block. */
|
|
31
|
+
export declare const Decision: Readonly<{
|
|
32
|
+
readonly Allow: "allow";
|
|
33
|
+
readonly Deny: "deny";
|
|
34
|
+
readonly Transform: "transform";
|
|
35
|
+
}>;
|
|
36
|
+
export type Decision = (typeof Decision)[keyof typeof Decision];
|
|
37
|
+
/** Whether the action proceeds under `d` (§2 permit class). */
|
|
38
|
+
export declare function permits(d: Decision): boolean;
|
|
39
|
+
/** Whether the host acts on verdicts (§8). */
|
|
40
|
+
export declare const EnforcementMode: Readonly<{
|
|
41
|
+
readonly Enforce: "enforce";
|
|
42
|
+
readonly EvaluateOnly: "evaluate_only";
|
|
43
|
+
}>;
|
|
44
|
+
export type EnforcementMode = (typeof EnforcementMode)[keyof typeof EnforcementMode];
|
|
45
|
+
/** Reserved `host_error:*` reasons a host synthesizes (§11). */
|
|
46
|
+
export declare const HostError: Readonly<{
|
|
47
|
+
readonly ContextInvalid: "host_error:context_invalid";
|
|
48
|
+
readonly InterceptorFailed: "host_error:interceptor_failed";
|
|
49
|
+
readonly InterceptorTimeout: "host_error:interceptor_timeout";
|
|
50
|
+
readonly VerdictInvalid: "host_error:verdict_invalid";
|
|
51
|
+
readonly TransformInvalid: "host_error:transform_invalid";
|
|
52
|
+
readonly TransformTargetForbidden: "host_error:transform_target_forbidden";
|
|
53
|
+
readonly TransformConflict: "host_error:transform_conflict";
|
|
54
|
+
readonly CompositionDisagreement: "host_error:composition_disagreement";
|
|
55
|
+
readonly ApprovalResolverFailed: "host_error:approval_resolver_failed";
|
|
56
|
+
readonly ApprovalUnresolved: "host_error:approval_unresolved";
|
|
57
|
+
readonly ApprovalIdentityMismatch: "host_error:approval_identity_mismatch";
|
|
58
|
+
readonly AdapterUnsupported: "host_error:adapter_unsupported";
|
|
59
|
+
readonly StreamingUnsupported: "host_error:streaming_unsupported";
|
|
60
|
+
readonly NoInterceptor: "host_error:no_interceptor";
|
|
61
|
+
}>;
|
|
62
|
+
export type HostError = (typeof HostError)[keyof typeof HostError];
|
|
63
|
+
/** A single `$target`-rooted replacement (§5.2). */
|
|
64
|
+
export interface Transform {
|
|
65
|
+
/** Path rooted at `$target` (or the deprecated `$policy_target` alias). */
|
|
66
|
+
path: string;
|
|
67
|
+
value: JsonValue;
|
|
68
|
+
}
|
|
69
|
+
/** Opaque pointer to an offline-verifiable artefact (§5.3). */
|
|
70
|
+
export interface Evidence {
|
|
71
|
+
artefact?: string | null;
|
|
72
|
+
verification_pointers?: Record<string, string>;
|
|
73
|
+
}
|
|
74
|
+
/** A recorded concern that does not affect control flow (§5.1). */
|
|
75
|
+
export interface Warning {
|
|
76
|
+
reason?: string | null;
|
|
77
|
+
message?: string | null;
|
|
78
|
+
}
|
|
79
|
+
/** Interceptor return value (§5). */
|
|
80
|
+
export interface Verdict {
|
|
81
|
+
decision: Decision;
|
|
82
|
+
reason?: string | null;
|
|
83
|
+
message?: string | null;
|
|
84
|
+
/** Recorded concerns; permitted on any decision (§5.1). */
|
|
85
|
+
warnings?: Warning[];
|
|
86
|
+
/** Present only on `deny`: marks the deny as liftable by the approval
|
|
87
|
+
* seam (§9). MAY be empty; reserved for approver-facing parameters. */
|
|
88
|
+
approval?: Record<string, JsonValue>;
|
|
89
|
+
transform?: Transform;
|
|
90
|
+
evidence?: Evidence;
|
|
91
|
+
result_labels?: string[];
|
|
92
|
+
}
|
|
93
|
+
/** Constructor sugar for the three-verdict vocabulary (§5.1). Merges
|
|
94
|
+
* with the `Verdict` interface: `Verdict.warn(...)`, `Verdict.escalate(...)`. */
|
|
95
|
+
export declare const Verdict: Readonly<{
|
|
96
|
+
/** The trivial permit verdict. */
|
|
97
|
+
allow(): Verdict;
|
|
98
|
+
/** What earlier drafts called `warn`: an `allow` carrying one
|
|
99
|
+
* warning (§5.1). */
|
|
100
|
+
warn(reason?: string, message?: string): Verdict;
|
|
101
|
+
/** What earlier drafts called `escalate`: a liftable deny — denied
|
|
102
|
+
* as-is unless the approval seam lifts it (§5.1, §9). */
|
|
103
|
+
escalate(reason?: string, message?: string): Verdict;
|
|
104
|
+
}>;
|
|
105
|
+
/** The trivial permit verdict. */
|
|
106
|
+
export declare const ALLOW: Readonly<Verdict>;
|
|
107
|
+
/** Host-synthesized deny verdict for a §11 failure. */
|
|
108
|
+
export declare function hostErrorVerdict(err: HostError, message?: string): Verdict;
|
|
109
|
+
/** Host-synthesized **liftable** deny (§7.5 `"approval"` knob value):
|
|
110
|
+
* the failure is consultable rather than final. */
|
|
111
|
+
export declare function hostErrorLiftable(err: HostError, message?: string): Verdict;
|
|
112
|
+
/** A deny carrying an `approval` block (§5.1). */
|
|
113
|
+
export declare function isLiftable(v: Verdict): boolean;
|
|
114
|
+
/** The closed profile set (§7.2). */
|
|
115
|
+
export declare const CompositionProfile: Readonly<{
|
|
116
|
+
readonly SequentialFirstDeny: "sequential/first_deny";
|
|
117
|
+
readonly SequentialRunAll: "sequential/run_all";
|
|
118
|
+
readonly ParallelStrictest: "parallel/strictest";
|
|
119
|
+
readonly ParallelUnanimous: "parallel/unanimous";
|
|
120
|
+
}>;
|
|
121
|
+
export type CompositionProfile = (typeof CompositionProfile)[keyof typeof CompositionProfile];
|
|
122
|
+
/** `sequential/first_deny` knob (§7.4): what a permit resolution does
|
|
123
|
+
* to the rest of the fold. */
|
|
124
|
+
export type OnApproval = "stop" | "resume";
|
|
125
|
+
/** `"deny" | "approval"` knob value (§7.5): synthesize a plain deny, or
|
|
126
|
+
* a liftable one and consult the seam. */
|
|
127
|
+
export type SynthesisPolicy = "deny" | "approval";
|
|
128
|
+
/** The composition profile and knobs in effect for one emission
|
|
129
|
+
* (§7.1, §10.3). Serialized verbatim into the record's `composition`
|
|
130
|
+
* block. */
|
|
131
|
+
export interface CompositionConfig {
|
|
132
|
+
profile: CompositionProfile;
|
|
133
|
+
/** `sequential/first_deny` only. */
|
|
134
|
+
on_approval?: OnApproval;
|
|
135
|
+
/** `parallel/unanimous` only. */
|
|
136
|
+
on_disagreement?: SynthesisPolicy;
|
|
137
|
+
/** Parallel profiles only. */
|
|
138
|
+
on_transform_conflict?: SynthesisPolicy;
|
|
139
|
+
}
|
|
140
|
+
/** Constructors for the closed profile set (§7.2). */
|
|
141
|
+
export declare const Composition: Readonly<{
|
|
142
|
+
/** The default: `sequential/first_deny` with `on_approval: stop`. A
|
|
143
|
+
* default, not a conformance baseline — no profile is mandatory. */
|
|
144
|
+
default(): CompositionConfig;
|
|
145
|
+
firstDeny(onApproval?: OnApproval): CompositionConfig;
|
|
146
|
+
runAll(): CompositionConfig;
|
|
147
|
+
strictest(onTransformConflict?: SynthesisPolicy): CompositionConfig;
|
|
148
|
+
unanimous(onDisagreement?: SynthesisPolicy, onTransformConflict?: SynthesisPolicy): CompositionConfig;
|
|
149
|
+
}>;
|
|
150
|
+
/** The host-declared identity provider (§10.1):
|
|
151
|
+
*
|
|
152
|
+
* - `"jcs-sha256"` — the shipped default (§10.2); fail-closed I-JSON
|
|
153
|
+
* domain; in effect unless the host configures otherwise.
|
|
154
|
+
* - `{name, fn}` — a host-supplied pure function. The echo and record
|
|
155
|
+
* rules (§10.1) still apply; the golden vectors do not.
|
|
156
|
+
* - `null` — identity-unbound: approvals bind by correlation only;
|
|
157
|
+
* records carry `null` identities and self-describe as unbound. */
|
|
158
|
+
export type IdentityProvider = typeof JCS_SHA256 | {
|
|
159
|
+
name: string;
|
|
160
|
+
fn: (ctx: AgentContext) => string;
|
|
161
|
+
} | null;
|
|
162
|
+
/** Wire-shaped agent context (§4). Required core typed; conditional and
|
|
163
|
+
* optional fields indexed. */
|
|
164
|
+
export interface AgentContext {
|
|
165
|
+
spec: string;
|
|
166
|
+
interception_point: InterceptionPoint;
|
|
167
|
+
timestamp: string;
|
|
168
|
+
sequence: number;
|
|
169
|
+
agent: {
|
|
170
|
+
id: string;
|
|
171
|
+
framework: string;
|
|
172
|
+
name?: string;
|
|
173
|
+
version?: string;
|
|
174
|
+
};
|
|
175
|
+
session: {
|
|
176
|
+
id: string;
|
|
177
|
+
started_at?: string;
|
|
178
|
+
turn?: number;
|
|
179
|
+
};
|
|
180
|
+
target: JsonValue;
|
|
181
|
+
extensions?: Record<string, JsonValue>;
|
|
182
|
+
[conditional: string]: JsonValue | undefined;
|
|
183
|
+
}
|
|
184
|
+
/** Payload-free per-interceptor summary on the record (§10.3). */
|
|
185
|
+
export interface VerdictSummary {
|
|
186
|
+
index: number;
|
|
187
|
+
decision: Decision;
|
|
188
|
+
reason?: string | null;
|
|
189
|
+
}
|
|
190
|
+
/** Host-side record of one emission (§10.3).
|
|
191
|
+
*
|
|
192
|
+
* Payload-free by design: the identities (when a provider is declared)
|
|
193
|
+
* bind the record to the exact pre/post-composition context without
|
|
194
|
+
* duplicating the (possibly sensitive) payload into audit storage.
|
|
195
|
+
* `composition` makes the record interpretable without out-of-band
|
|
196
|
+
* knowledge of host configuration. */
|
|
197
|
+
export interface InterceptionRecord {
|
|
198
|
+
interception_point: InterceptionPoint;
|
|
199
|
+
mode: EnforcementMode;
|
|
200
|
+
/** The combined verdict (§7.3), possibly host-synthesized or
|
|
201
|
+
* approval-substituted. */
|
|
202
|
+
verdict: Verdict;
|
|
203
|
+
/** Provider output before dispatch; `null` iff `identity_provider` is
|
|
204
|
+
* `null` (or the provider itself rejected the context). */
|
|
205
|
+
input_identity: string | null;
|
|
206
|
+
/** Provider output after composition completes. */
|
|
207
|
+
enforced_identity: string | null;
|
|
208
|
+
/** The declared identity provider (§10.1). */
|
|
209
|
+
identity_provider: string | null;
|
|
210
|
+
/** `ctx.session.id` — correlates records across a session. */
|
|
211
|
+
session_id: string;
|
|
212
|
+
/** `ctx.sequence` — total order within the session (§12.2.3). */
|
|
213
|
+
sequence: number;
|
|
214
|
+
/** Registration index of the interceptor whose verdict won the
|
|
215
|
+
* aggregation or whose liftable deny was consulted (§7.6); `null`
|
|
216
|
+
* for a pure-allow combination or a host-synthesized verdict. */
|
|
217
|
+
decided_by: number | null;
|
|
218
|
+
/** The composition profile and knobs in effect (§7.1). */
|
|
219
|
+
composition: CompositionConfig;
|
|
220
|
+
/** Per-interceptor summary; populated in multi-verdict profiles
|
|
221
|
+
* (`sequential/run_all`, `parallel/*`). */
|
|
222
|
+
verdicts?: VerdictSummary[];
|
|
223
|
+
/** `true` iff one or more registered interceptors were never invoked
|
|
224
|
+
* in this emission. Defined only for `sequential/first_deny` (§7.4). */
|
|
225
|
+
fold_truncated?: boolean;
|
|
226
|
+
/** `"approval"` iff an approval resolution substituted for a verdict
|
|
227
|
+
* in this emission (§7.6). */
|
|
228
|
+
resolved_by?: "approval" | null;
|
|
229
|
+
}
|
|
230
|
+
/** Whether the guarded action executes (§6, §8). */
|
|
231
|
+
export declare function proceeds(r: InterceptionRecord): boolean;
|
|
232
|
+
/** Interceptor protocol (§7). */
|
|
233
|
+
export interface Interceptor {
|
|
234
|
+
intercept(context: AgentContext): Verdict | Promise<Verdict>;
|
|
235
|
+
}
|
|
236
|
+
/** Approval seam (§9). */
|
|
237
|
+
export declare const ApprovalOutcome: Readonly<{
|
|
238
|
+
readonly Approve: "approve";
|
|
239
|
+
readonly Reject: "reject";
|
|
240
|
+
readonly Unresolved: "unresolved";
|
|
241
|
+
}>;
|
|
242
|
+
export type ApprovalOutcome = (typeof ApprovalOutcome)[keyof typeof ApprovalOutcome];
|
|
243
|
+
/** `context_identity` is `null` when the identity provider is `null`
|
|
244
|
+
* (§10.1) — the approval is then identity-unbound. */
|
|
245
|
+
export interface ApprovalRequest {
|
|
246
|
+
context_identity: string | null;
|
|
247
|
+
interception_point: InterceptionPoint;
|
|
248
|
+
verdict: Verdict;
|
|
249
|
+
context: AgentContext;
|
|
250
|
+
}
|
|
251
|
+
/** The resolution's `context_identity` MUST echo the request's byte for
|
|
252
|
+
* byte (`null` echoes as `null`, §9 echo rule). */
|
|
253
|
+
export interface ApprovalResolution {
|
|
254
|
+
outcome: ApprovalOutcome;
|
|
255
|
+
context_identity: string | null;
|
|
256
|
+
verdict?: Verdict;
|
|
257
|
+
}
|
|
258
|
+
export interface ApprovalResolver {
|
|
259
|
+
resolve(request: ApprovalRequest): ApprovalResolution | Promise<ApprovalResolution>;
|
|
260
|
+
}
|
|
261
|
+
/** Depth-first scan for non-finite numbers. Returns the dotted path of
|
|
262
|
+
* the first offender, or `null` when the value is clean. */
|
|
263
|
+
export declare function findNonFinite(v: unknown, path?: string): string | null;
|
|
264
|
+
/** §4.4 remediation detail for a non-finite number at `path`. */
|
|
265
|
+
export declare function nonFiniteDetail(path: string): string;
|
|
266
|
+
/** Serialize per §10.2 (RFC 8785). Implemented by the Rust core. */
|
|
267
|
+
export declare function canonicalJson(v: JsonValue): string;
|
|
268
|
+
/** `"sha256:" + hex(SHA-256(canonicalJson(ctx_rc)))` (§10.2). Fails
|
|
269
|
+
* closed (`host_error:context_invalid` with remediation detail) on a
|
|
270
|
+
* non-I-JSON projection — integrals beyond ±(2^53−1), non-finite
|
|
271
|
+
* numbers. Rust core. */
|
|
272
|
+
export declare function contextIdentity(ctx: AgentContext): string;
|
|
273
|
+
/** §5: validate an interceptor's wire return value. Rust core. */
|
|
274
|
+
export declare function validateVerdict(v: Verdict): void;
|
|
275
|
+
/** §5.2: apply a `$target`-rooted transform. Returns a new object. Rust core. */
|
|
276
|
+
export declare function applyTransform(target: JsonValue, path: string, value: JsonValue): JsonValue;
|
|
277
|
+
/** §7.4 fold-through: apply one transform to the context's `target` (and
|
|
278
|
+
* its conditional alias) so the next interceptor sees the effect. Returns
|
|
279
|
+
* the updated context. Rust core. */
|
|
280
|
+
export declare function applyTransformCtx(ctx: AgentContext, path: string, value: JsonValue): AgentContext;
|
|
281
|
+
/** §8 `evaluate_only`: validate a transform against the context's current
|
|
282
|
+
* target without applying it. Rust core. */
|
|
283
|
+
export declare function validateTransformCtx(ctx: AgentContext, path: string, value: JsonValue): void;
|
|
284
|
+
/** Everything {@link finalize} needs beyond the context and combined
|
|
285
|
+
* verdict (§10.3). */
|
|
286
|
+
export interface FinalizeMeta {
|
|
287
|
+
/** Provider output computed **before** dispatch; `null` when the
|
|
288
|
+
* identity provider is `null` or rejected the context. */
|
|
289
|
+
input_identity?: string | null;
|
|
290
|
+
/** The declared provider name (§10.1). When `"jcs-sha256"`, the core
|
|
291
|
+
* computes `enforced_identity` from the post-fold context itself; a
|
|
292
|
+
* custom provider's host passes `enforced_identity` explicitly. */
|
|
293
|
+
identity_provider?: string | null;
|
|
294
|
+
/** Pre-computed post-composition identity for custom providers.
|
|
295
|
+
* Ignored when `identity_provider == "jcs-sha256"`. */
|
|
296
|
+
enforced_identity?: string | null;
|
|
297
|
+
decided_by?: number | null;
|
|
298
|
+
/** REQUIRED: the profile and knobs in effect (§7.1). */
|
|
299
|
+
composition: CompositionConfig;
|
|
300
|
+
/** Per-interceptor summaries (multi-verdict profiles, §10.3). */
|
|
301
|
+
verdicts?: VerdictSummary[] | null;
|
|
302
|
+
/** `sequential/first_deny` only (§7.4). */
|
|
303
|
+
fold_truncated?: boolean | null;
|
|
304
|
+
/** `"approval"` iff a resolution substituted for a verdict (§7.6). */
|
|
305
|
+
resolved_by?: "approval" | null;
|
|
306
|
+
}
|
|
307
|
+
/** §10.3: build the `InterceptionRecord` for one completed emission.
|
|
308
|
+
* `meta.input_identity` MUST have been computed before interceptor
|
|
309
|
+
* dispatch; transforms were already applied during the §7.4 fold. Rust
|
|
310
|
+
* core. */
|
|
311
|
+
export declare function finalize(ctx: AgentContext, verdict: Verdict, mode: EnforcementMode, meta: FinalizeMeta): InterceptionRecord;
|
|
312
|
+
/** §7.3/§7.5 aggregation for the multi-verdict profiles: severity-max
|
|
313
|
+
* winner (or synthesized conflict/disagreement verdict) with the §7.3
|
|
314
|
+
* metadata unions. Rust core. */
|
|
315
|
+
export declare function composeAggregate(composition: CompositionConfig, verdicts: Verdict[]): {
|
|
316
|
+
combined: Verdict;
|
|
317
|
+
decided_by: number | null;
|
|
318
|
+
consult: boolean;
|
|
319
|
+
apply_transform: boolean;
|
|
320
|
+
verdicts: VerdictSummary[];
|
|
321
|
+
};
|
|
322
|
+
export { AgentContextBuilder } from "./builder";
|
|
323
|
+
export { InterceptionEmitter } from "./emitter";
|
|
324
|
+
/** Raised by a host when a verdict blocks the guarded action (§6). */
|
|
325
|
+
export declare class InterceptionBlocked extends Error {
|
|
326
|
+
readonly result: InterceptionRecord;
|
|
327
|
+
constructor(result: InterceptionRecord);
|
|
328
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) Microsoft Corporation.
|
|
3
|
+
// Licensed under the MIT License.
|
|
4
|
+
/**
|
|
5
|
+
* agent-hooks: framework-neutral agent lifecycle hook contract.
|
|
6
|
+
* Implements AGENT-HOOKS-0.1. Lifted and adapted from
|
|
7
|
+
* `policy-engine/sdk/node/src/index.ts`.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.InterceptionBlocked = exports.InterceptionEmitter = exports.AgentContextBuilder = exports.ApprovalOutcome = exports.Composition = exports.CompositionProfile = exports.ALLOW = exports.Verdict = exports.HostError = exports.EnforcementMode = exports.Decision = exports.InterceptionPoint = exports.JCS_SHA256 = exports.SPEC_VERSION = exports.AgentHooksCoreError = void 0;
|
|
11
|
+
exports.transformPermitted = transformPermitted;
|
|
12
|
+
exports.permits = permits;
|
|
13
|
+
exports.hostErrorVerdict = hostErrorVerdict;
|
|
14
|
+
exports.hostErrorLiftable = hostErrorLiftable;
|
|
15
|
+
exports.isLiftable = isLiftable;
|
|
16
|
+
exports.proceeds = proceeds;
|
|
17
|
+
exports.findNonFinite = findNonFinite;
|
|
18
|
+
exports.nonFiniteDetail = nonFiniteDetail;
|
|
19
|
+
exports.canonicalJson = canonicalJson;
|
|
20
|
+
exports.contextIdentity = contextIdentity;
|
|
21
|
+
exports.validateVerdict = validateVerdict;
|
|
22
|
+
exports.applyTransform = applyTransform;
|
|
23
|
+
exports.applyTransformCtx = applyTransformCtx;
|
|
24
|
+
exports.validateTransformCtx = validateTransformCtx;
|
|
25
|
+
exports.finalize = finalize;
|
|
26
|
+
exports.composeAggregate = composeAggregate;
|
|
27
|
+
const native_1 = require("./native");
|
|
28
|
+
Object.defineProperty(exports, "AgentHooksCoreError", { enumerable: true, get: function () { return native_1.AgentHooksCoreError; } });
|
|
29
|
+
/** Spec version this SDK implements (§4.1 `spec` field). */
|
|
30
|
+
exports.SPEC_VERSION = "agent-hooks/0.1";
|
|
31
|
+
/** Name of the default identity provider (§10.1, §10.2). */
|
|
32
|
+
exports.JCS_SHA256 = "jcs-sha256";
|
|
33
|
+
/** The closed set of agent lifecycle interception points (§3). */
|
|
34
|
+
exports.InterceptionPoint = Object.freeze({
|
|
35
|
+
AgentStartup: "agent_startup",
|
|
36
|
+
Input: "input",
|
|
37
|
+
PreModelCall: "pre_model_call",
|
|
38
|
+
PostModelCall: "post_model_call",
|
|
39
|
+
PreToolCall: "pre_tool_call",
|
|
40
|
+
PostToolCall: "post_tool_call",
|
|
41
|
+
Output: "output",
|
|
42
|
+
AgentShutdown: "agent_shutdown",
|
|
43
|
+
});
|
|
44
|
+
/** Whether a `transform` verdict is permitted at `hp` (§3, §4.3). */
|
|
45
|
+
function transformPermitted(hp) {
|
|
46
|
+
return hp !== exports.InterceptionPoint.AgentStartup && hp !== exports.InterceptionPoint.AgentShutdown;
|
|
47
|
+
}
|
|
48
|
+
/** Verdict decision values (§5.1). Three, closed: `warn` is `allow` +
|
|
49
|
+
* `warnings[]`; `escalate` is `deny` + an `approval` block. */
|
|
50
|
+
exports.Decision = Object.freeze({
|
|
51
|
+
Allow: "allow",
|
|
52
|
+
Deny: "deny",
|
|
53
|
+
Transform: "transform",
|
|
54
|
+
});
|
|
55
|
+
/** Whether the action proceeds under `d` (§2 permit class). */
|
|
56
|
+
function permits(d) {
|
|
57
|
+
return d === exports.Decision.Allow || d === exports.Decision.Transform;
|
|
58
|
+
}
|
|
59
|
+
/** Whether the host acts on verdicts (§8). */
|
|
60
|
+
exports.EnforcementMode = Object.freeze({
|
|
61
|
+
Enforce: "enforce",
|
|
62
|
+
EvaluateOnly: "evaluate_only",
|
|
63
|
+
});
|
|
64
|
+
/** Reserved `host_error:*` reasons a host synthesizes (§11). */
|
|
65
|
+
exports.HostError = Object.freeze({
|
|
66
|
+
ContextInvalid: "host_error:context_invalid",
|
|
67
|
+
InterceptorFailed: "host_error:interceptor_failed",
|
|
68
|
+
InterceptorTimeout: "host_error:interceptor_timeout",
|
|
69
|
+
VerdictInvalid: "host_error:verdict_invalid",
|
|
70
|
+
TransformInvalid: "host_error:transform_invalid",
|
|
71
|
+
TransformTargetForbidden: "host_error:transform_target_forbidden",
|
|
72
|
+
TransformConflict: "host_error:transform_conflict",
|
|
73
|
+
CompositionDisagreement: "host_error:composition_disagreement",
|
|
74
|
+
ApprovalResolverFailed: "host_error:approval_resolver_failed",
|
|
75
|
+
ApprovalUnresolved: "host_error:approval_unresolved",
|
|
76
|
+
ApprovalIdentityMismatch: "host_error:approval_identity_mismatch",
|
|
77
|
+
AdapterUnsupported: "host_error:adapter_unsupported",
|
|
78
|
+
StreamingUnsupported: "host_error:streaming_unsupported",
|
|
79
|
+
NoInterceptor: "host_error:no_interceptor",
|
|
80
|
+
});
|
|
81
|
+
/** Constructor sugar for the three-verdict vocabulary (§5.1). Merges
|
|
82
|
+
* with the `Verdict` interface: `Verdict.warn(...)`, `Verdict.escalate(...)`. */
|
|
83
|
+
exports.Verdict = Object.freeze({
|
|
84
|
+
/** The trivial permit verdict. */
|
|
85
|
+
allow() {
|
|
86
|
+
return { decision: exports.Decision.Allow };
|
|
87
|
+
},
|
|
88
|
+
/** What earlier drafts called `warn`: an `allow` carrying one
|
|
89
|
+
* warning (§5.1). */
|
|
90
|
+
warn(reason, message) {
|
|
91
|
+
return { decision: exports.Decision.Allow, warnings: [{ reason, message }] };
|
|
92
|
+
},
|
|
93
|
+
/** What earlier drafts called `escalate`: a liftable deny — denied
|
|
94
|
+
* as-is unless the approval seam lifts it (§5.1, §9). */
|
|
95
|
+
escalate(reason, message) {
|
|
96
|
+
return { decision: exports.Decision.Deny, reason, message, approval: {} };
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
/** The trivial permit verdict. */
|
|
100
|
+
exports.ALLOW = Object.freeze({ decision: exports.Decision.Allow });
|
|
101
|
+
/** Host-synthesized deny verdict for a §11 failure. */
|
|
102
|
+
function hostErrorVerdict(err, message) {
|
|
103
|
+
return { decision: exports.Decision.Deny, reason: err, message };
|
|
104
|
+
}
|
|
105
|
+
/** Host-synthesized **liftable** deny (§7.5 `"approval"` knob value):
|
|
106
|
+
* the failure is consultable rather than final. */
|
|
107
|
+
function hostErrorLiftable(err, message) {
|
|
108
|
+
return { ...hostErrorVerdict(err, message), approval: {} };
|
|
109
|
+
}
|
|
110
|
+
/** A deny carrying an `approval` block (§5.1). */
|
|
111
|
+
function isLiftable(v) {
|
|
112
|
+
return v.decision === exports.Decision.Deny && v.approval != null;
|
|
113
|
+
}
|
|
114
|
+
// ---- Composition (§7) -------------------------------------------------------
|
|
115
|
+
/** The closed profile set (§7.2). */
|
|
116
|
+
exports.CompositionProfile = Object.freeze({
|
|
117
|
+
SequentialFirstDeny: "sequential/first_deny",
|
|
118
|
+
SequentialRunAll: "sequential/run_all",
|
|
119
|
+
ParallelStrictest: "parallel/strictest",
|
|
120
|
+
ParallelUnanimous: "parallel/unanimous",
|
|
121
|
+
});
|
|
122
|
+
/** Constructors for the closed profile set (§7.2). */
|
|
123
|
+
exports.Composition = Object.freeze({
|
|
124
|
+
/** The default: `sequential/first_deny` with `on_approval: stop`. A
|
|
125
|
+
* default, not a conformance baseline — no profile is mandatory. */
|
|
126
|
+
default() {
|
|
127
|
+
return { profile: exports.CompositionProfile.SequentialFirstDeny, on_approval: "stop" };
|
|
128
|
+
},
|
|
129
|
+
firstDeny(onApproval = "stop") {
|
|
130
|
+
return { profile: exports.CompositionProfile.SequentialFirstDeny, on_approval: onApproval };
|
|
131
|
+
},
|
|
132
|
+
runAll() {
|
|
133
|
+
return { profile: exports.CompositionProfile.SequentialRunAll };
|
|
134
|
+
},
|
|
135
|
+
strictest(onTransformConflict = "deny") {
|
|
136
|
+
return {
|
|
137
|
+
profile: exports.CompositionProfile.ParallelStrictest,
|
|
138
|
+
on_transform_conflict: onTransformConflict,
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
unanimous(onDisagreement = "deny", onTransformConflict = "deny") {
|
|
142
|
+
return {
|
|
143
|
+
profile: exports.CompositionProfile.ParallelUnanimous,
|
|
144
|
+
on_disagreement: onDisagreement,
|
|
145
|
+
on_transform_conflict: onTransformConflict,
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
/** Whether the guarded action executes (§6, §8). */
|
|
150
|
+
function proceeds(r) {
|
|
151
|
+
return r.mode === exports.EnforcementMode.EvaluateOnly || permits(r.verdict.decision);
|
|
152
|
+
}
|
|
153
|
+
/** Approval seam (§9). */
|
|
154
|
+
exports.ApprovalOutcome = Object.freeze({
|
|
155
|
+
Approve: "approve",
|
|
156
|
+
Reject: "reject",
|
|
157
|
+
Unresolved: "unresolved",
|
|
158
|
+
});
|
|
159
|
+
// ---- Non-finite guard (§4.4, §10.2) -----------------------------------------
|
|
160
|
+
//
|
|
161
|
+
// `JSON.stringify` silently corrupts NaN/±Infinity to `null`, so every
|
|
162
|
+
// marshalling point below pre-scans and fails closed
|
|
163
|
+
// (`host_error:context_invalid`) instead of letting the corrupted value
|
|
164
|
+
// cross the boundary. The core never sees a non-finite number (it is
|
|
165
|
+
// unrepresentable in JSON), which is exactly why the guard must live on
|
|
166
|
+
// the JS side of the funnel.
|
|
167
|
+
/** Depth-first scan for non-finite numbers. Returns the dotted path of
|
|
168
|
+
* the first offender, or `null` when the value is clean. */
|
|
169
|
+
function findNonFinite(v, path = "$") {
|
|
170
|
+
if (typeof v === "number")
|
|
171
|
+
return Number.isFinite(v) ? null : path;
|
|
172
|
+
if (Array.isArray(v)) {
|
|
173
|
+
for (let i = 0; i < v.length; i++) {
|
|
174
|
+
const hit = findNonFinite(v[i], `${path}[${i}]`);
|
|
175
|
+
if (hit !== null)
|
|
176
|
+
return hit;
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
if (v !== null && typeof v === "object") {
|
|
181
|
+
for (const [k, item] of Object.entries(v)) {
|
|
182
|
+
const hit = findNonFinite(item, `${path}.${k}`);
|
|
183
|
+
if (hit !== null)
|
|
184
|
+
return hit;
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
/** §4.4 remediation detail for a non-finite number at `path`. */
|
|
191
|
+
function nonFiniteDetail(path) {
|
|
192
|
+
return `${path}: non-finite number (NaN/Infinity) is not representable in JSON; remove or string-encode it, see spec §4.4`;
|
|
193
|
+
}
|
|
194
|
+
/** Marshalling guard: `JSON.stringify` that fails closed
|
|
195
|
+
* (`host_error:context_invalid`) on non-finite numbers instead of
|
|
196
|
+
* silently corrupting them to `null` (§4.4). */
|
|
197
|
+
function marshal(v) {
|
|
198
|
+
const hit = findNonFinite(v);
|
|
199
|
+
if (hit !== null)
|
|
200
|
+
throw new native_1.AgentHooksCoreError(exports.HostError.ContextInvalid, nonFiniteDetail(hit));
|
|
201
|
+
return JSON.stringify(v);
|
|
202
|
+
}
|
|
203
|
+
// ---- Canonical JSON & context identity (§10) -------------------------------
|
|
204
|
+
//
|
|
205
|
+
// Delegates to the Rust core via napi-rs so every SDK produces
|
|
206
|
+
// byte-identical output. The pure-TS implementation was removed once the
|
|
207
|
+
// core became canonical (see sdk/rust/core/src/canonical.rs).
|
|
208
|
+
/** Serialize per §10.2 (RFC 8785). Implemented by the Rust core. */
|
|
209
|
+
function canonicalJson(v) {
|
|
210
|
+
return native_1.native.canonicalJson(marshal(v));
|
|
211
|
+
}
|
|
212
|
+
/** `"sha256:" + hex(SHA-256(canonicalJson(ctx_rc)))` (§10.2). Fails
|
|
213
|
+
* closed (`host_error:context_invalid` with remediation detail) on a
|
|
214
|
+
* non-I-JSON projection — integrals beyond ±(2^53−1), non-finite
|
|
215
|
+
* numbers. Rust core. */
|
|
216
|
+
function contextIdentity(ctx) {
|
|
217
|
+
return native_1.native.contextIdentity(marshal(ctx));
|
|
218
|
+
}
|
|
219
|
+
/** §5: validate an interceptor's wire return value. Rust core. */
|
|
220
|
+
function validateVerdict(v) {
|
|
221
|
+
native_1.native.validateVerdict(marshal(v));
|
|
222
|
+
}
|
|
223
|
+
/** §5.2: apply a `$target`-rooted transform. Returns a new object. Rust core. */
|
|
224
|
+
function applyTransform(target, path, value) {
|
|
225
|
+
return JSON.parse(native_1.native.applyTransform(marshal(target), path, marshal(value)));
|
|
226
|
+
}
|
|
227
|
+
/** §7.4 fold-through: apply one transform to the context's `target` (and
|
|
228
|
+
* its conditional alias) so the next interceptor sees the effect. Returns
|
|
229
|
+
* the updated context. Rust core. */
|
|
230
|
+
function applyTransformCtx(ctx, path, value) {
|
|
231
|
+
return JSON.parse(native_1.native.applyTransformCtx(marshal(ctx), path, marshal(value)));
|
|
232
|
+
}
|
|
233
|
+
/** §8 `evaluate_only`: validate a transform against the context's current
|
|
234
|
+
* target without applying it. Rust core. */
|
|
235
|
+
function validateTransformCtx(ctx, path, value) {
|
|
236
|
+
native_1.native.validateTransformCtx(marshal(ctx), path, marshal(value));
|
|
237
|
+
}
|
|
238
|
+
/** §10.3: build the `InterceptionRecord` for one completed emission.
|
|
239
|
+
* `meta.input_identity` MUST have been computed before interceptor
|
|
240
|
+
* dispatch; transforms were already applied during the §7.4 fold. Rust
|
|
241
|
+
* core. */
|
|
242
|
+
function finalize(ctx, verdict, mode, meta) {
|
|
243
|
+
return JSON.parse(native_1.native.finalize(marshal(ctx), marshal(verdict), mode, JSON.stringify({
|
|
244
|
+
input_identity: meta.input_identity ?? null,
|
|
245
|
+
identity_provider: meta.identity_provider ?? null,
|
|
246
|
+
enforced_identity: meta.enforced_identity ?? null,
|
|
247
|
+
decided_by: meta.decided_by ?? null,
|
|
248
|
+
composition: meta.composition,
|
|
249
|
+
verdicts: meta.verdicts ?? null,
|
|
250
|
+
fold_truncated: meta.fold_truncated ?? null,
|
|
251
|
+
resolved_by: meta.resolved_by ?? null,
|
|
252
|
+
})));
|
|
253
|
+
}
|
|
254
|
+
/** §7.3/§7.5 aggregation for the multi-verdict profiles: severity-max
|
|
255
|
+
* winner (or synthesized conflict/disagreement verdict) with the §7.3
|
|
256
|
+
* metadata unions. Rust core. */
|
|
257
|
+
function composeAggregate(composition, verdicts) {
|
|
258
|
+
return JSON.parse(native_1.native.composeAggregate(JSON.stringify(composition), marshal(verdicts)));
|
|
259
|
+
}
|
|
260
|
+
var builder_1 = require("./builder");
|
|
261
|
+
Object.defineProperty(exports, "AgentContextBuilder", { enumerable: true, get: function () { return builder_1.AgentContextBuilder; } });
|
|
262
|
+
var emitter_1 = require("./emitter");
|
|
263
|
+
Object.defineProperty(exports, "InterceptionEmitter", { enumerable: true, get: function () { return emitter_1.InterceptionEmitter; } });
|
|
264
|
+
/** Raised by a host when a verdict blocks the guarded action (§6). */
|
|
265
|
+
class InterceptionBlocked extends Error {
|
|
266
|
+
result;
|
|
267
|
+
constructor(result) {
|
|
268
|
+
super(`${result.interception_point} blocked: ${result.verdict.decision} (${result.verdict.reason ?? "no reason"})`);
|
|
269
|
+
this.result = result;
|
|
270
|
+
this.name = "InterceptionBlocked";
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
exports.InterceptionBlocked = InterceptionBlocked;
|
package/dist/native.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loader for the napi-rs native module + typed re-export.
|
|
3
|
+
*
|
|
4
|
+
* `@napi-rs/cli build` emits `agent-hooks.<platform>.node` alongside a
|
|
5
|
+
* generated `index.js` loader; this module re-exports it with types and
|
|
6
|
+
* the `AgentHooksCoreError` unwrapping so callers get `.code`.
|
|
7
|
+
*/
|
|
8
|
+
/** Thrown by every native function on failure. `.code` is the §11
|
|
9
|
+
* `host_error:*` wire string. */
|
|
10
|
+
export declare class AgentHooksCoreError extends Error {
|
|
11
|
+
readonly code: string;
|
|
12
|
+
constructor(code: string, detail: string);
|
|
13
|
+
}
|
|
14
|
+
export declare const native: {
|
|
15
|
+
specVersion: () => string;
|
|
16
|
+
canonicalJson: (valueJson: string) => string;
|
|
17
|
+
contextIdentity: (ctxJson: string) => string;
|
|
18
|
+
validateVerdict: (verdictJson: string) => string;
|
|
19
|
+
applyTransform: (targetJson: string, path: string, valueJson: string) => string;
|
|
20
|
+
applyTransformCtx: (ctxJson: string, path: string, valueJson: string) => string;
|
|
21
|
+
validateTransformCtx: (ctxJson: string, path: string, valueJson: string) => string;
|
|
22
|
+
finalize: (ctxJson: string, verdictJson: string, mode: string, optionsJson: string) => string;
|
|
23
|
+
composeAggregate: (compositionJson: string, verdictsJson: string) => string;
|
|
24
|
+
ctkScriptedIntercept: (rulesJson: string, ctxJson: string) => string;
|
|
25
|
+
ctkScriptedResolve: (rulesJson: string, ctxJson: string, identity: string) => string;
|
|
26
|
+
ctkShouldSkip: (vectorJson: string, capsJson: string) => string;
|
|
27
|
+
ctkAssert: (vectorJson: string, recordedJson: string, runRecordJson: string) => string;
|
|
28
|
+
};
|