aira-sdk 0.4.0 → 2.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/README.md +302 -108
- package/dist/client.d.ts +112 -4
- package/dist/client.js +162 -8
- package/dist/extras/index.d.ts +49 -2
- package/dist/extras/index.js +90 -3
- package/dist/extras/langchain.d.ts +69 -18
- package/dist/extras/langchain.js +118 -35
- package/dist/extras/mcp.d.ts +24 -3
- package/dist/extras/mcp.js +75 -10
- package/dist/extras/openai-agents.d.ts +48 -16
- package/dist/extras/openai-agents.js +85 -29
- package/dist/extras/vercel-ai.d.ts +54 -17
- package/dist/extras/vercel-ai.js +104 -30
- package/dist/index.d.ts +1 -1
- package/dist/session.d.ts +15 -4
- package/dist/session.js +11 -3
- package/dist/types.d.ts +74 -20
- package/dist/types.js +15 -4
- package/package.json +2 -2
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ActionReceipt, ActionDetail, AgentDetail, AgentVersion, EvidencePackage, ComplianceSnapshot, EscrowAccount, EscrowTransaction, VerifyResult, PaginatedList } from "./types";
|
|
1
|
+
import { Authorization, ActionReceipt, ActionDetail, AgentDetail, AgentVersion, CosignResult, EvidencePackage, ComplianceSnapshot, EscrowAccount, EscrowTransaction, VerifyResult, PaginatedList } from "./types";
|
|
2
2
|
import { AiraSession } from "./session";
|
|
3
3
|
export interface AiraOptions {
|
|
4
4
|
apiKey: string;
|
|
@@ -18,19 +18,48 @@ export declare class Aira {
|
|
|
18
18
|
private put;
|
|
19
19
|
private del;
|
|
20
20
|
private paginated;
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Step 1 — Authorize an action BEFORE it executes.
|
|
23
|
+
*
|
|
24
|
+
* Returns an `Authorization` with a status:
|
|
25
|
+
* - "authorized" → safe to execute the action, then call `notarize()`
|
|
26
|
+
* - "pending_approval" → enqueue `action_id` and wait for human approval
|
|
27
|
+
*
|
|
28
|
+
* If a policy denies the action, this throws `AiraError` with code
|
|
29
|
+
* `POLICY_DENIED` (HTTP 403). Duplicate idempotent requests throw
|
|
30
|
+
* `DUPLICATE_REQUEST` (HTTP 409).
|
|
31
|
+
*/
|
|
32
|
+
authorize(params: {
|
|
22
33
|
actionType: string;
|
|
23
34
|
details: string;
|
|
24
35
|
agentId?: string;
|
|
25
36
|
agentVersion?: string;
|
|
37
|
+
instructionHash?: string;
|
|
26
38
|
modelId?: string;
|
|
27
39
|
modelVersion?: string;
|
|
28
|
-
instructionHash?: string;
|
|
29
40
|
parentActionId?: string;
|
|
41
|
+
endpointUrl?: string;
|
|
30
42
|
storeDetails?: boolean;
|
|
31
43
|
idempotencyKey?: string;
|
|
32
44
|
requireApproval?: boolean;
|
|
33
45
|
approvers?: string[];
|
|
46
|
+
systemPromptHash?: string;
|
|
47
|
+
toolInputsHash?: string;
|
|
48
|
+
modelParams?: Record<string, unknown>;
|
|
49
|
+
executionEnv?: Record<string, unknown>;
|
|
50
|
+
}): Promise<Authorization>;
|
|
51
|
+
/**
|
|
52
|
+
* Step 2 — Notarize the outcome of an already-authorized action.
|
|
53
|
+
*
|
|
54
|
+
* Call this AFTER executing the action. Outcome is "completed" by default;
|
|
55
|
+
* pass "failed" if the action ran but failed so the audit trail captures
|
|
56
|
+
* the failure. The returned `ActionReceipt` carries the Ed25519 signature
|
|
57
|
+
* and RFC 3161 timestamp token when the status is "notarized".
|
|
58
|
+
*/
|
|
59
|
+
notarize(params: {
|
|
60
|
+
actionId: string;
|
|
61
|
+
outcome?: "completed" | "failed";
|
|
62
|
+
outcomeDetails?: string;
|
|
34
63
|
}): Promise<ActionReceipt>;
|
|
35
64
|
getAction(actionId: string): Promise<ActionDetail>;
|
|
36
65
|
listActions(params?: {
|
|
@@ -40,7 +69,17 @@ export declare class Aira {
|
|
|
40
69
|
agentId?: string;
|
|
41
70
|
status?: string;
|
|
42
71
|
}): Promise<PaginatedList<ActionDetail>>;
|
|
43
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Add a human co-signature to an action that already exists.
|
|
74
|
+
*
|
|
75
|
+
* This is distinct from the authorization gate (it runs against `/cosign`,
|
|
76
|
+
* not `/authorize`). It records that a specific human has acknowledged or
|
|
77
|
+
* signed off on an action that was already authorized and notarized.
|
|
78
|
+
* Requires JWT auth (dashboard user, not an API key).
|
|
79
|
+
*/
|
|
80
|
+
cosign(params: {
|
|
81
|
+
actionId: string;
|
|
82
|
+
}): Promise<CosignResult>;
|
|
44
83
|
setLegalHold(actionId: string): Promise<Record<string, unknown>>;
|
|
45
84
|
releaseLegalHold(actionId: string): Promise<Record<string, unknown>>;
|
|
46
85
|
getActionChain(actionId: string): Promise<Record<string, unknown>[]>;
|
|
@@ -163,6 +202,75 @@ export declare class Aira {
|
|
|
163
202
|
attestReputation(slug: string, counterpartyDid: string, actionId: string, attestation: string, signature: string): Promise<Record<string, unknown>>;
|
|
164
203
|
/** Verify a reputation score by returning inputs and score_hash. */
|
|
165
204
|
verifyReputation(slug: string): Promise<Record<string, unknown>>;
|
|
205
|
+
/**
|
|
206
|
+
* Get all reproducibility metadata stored for an action.
|
|
207
|
+
*
|
|
208
|
+
* Returns the system_prompt_hash, tool_inputs_hash, model_params,
|
|
209
|
+
* execution_env, and other knobs that an external replay tool
|
|
210
|
+
* needs to confirm it has the same inputs as the original run.
|
|
211
|
+
*/
|
|
212
|
+
getReplayContext(actionId: string): Promise<Record<string, unknown>>;
|
|
213
|
+
/**
|
|
214
|
+
* Seal a regulator-ready evidence bundle for a date range.
|
|
215
|
+
*
|
|
216
|
+
* `framework` must be one of: `eu_ai_act_art12`, `iso_42001`,
|
|
217
|
+
* `aiuc_1`, `soc_2_cc7`, `raw`.
|
|
218
|
+
*/
|
|
219
|
+
createComplianceBundle(params: {
|
|
220
|
+
framework: "eu_ai_act_art12" | "iso_42001" | "aiuc_1" | "soc_2_cc7" | "raw";
|
|
221
|
+
periodStart: string;
|
|
222
|
+
periodEnd: string;
|
|
223
|
+
title?: string;
|
|
224
|
+
agentFilter?: string[];
|
|
225
|
+
/**
|
|
226
|
+
* Client-supplied key (unique per org) — retrying with the same key
|
|
227
|
+
* returns the original bundle and does NOT charge a second operation.
|
|
228
|
+
* Use this if your job runner may replay the call on network flakes.
|
|
229
|
+
*/
|
|
230
|
+
idempotencyKey?: string;
|
|
231
|
+
}): Promise<Record<string, unknown>>;
|
|
232
|
+
listComplianceBundles(page?: number, perPage?: number): Promise<PaginatedList<Record<string, unknown>>>;
|
|
233
|
+
getComplianceBundle(bundleId: string): Promise<Record<string, unknown>>;
|
|
234
|
+
/**
|
|
235
|
+
* Download the self-contained JSON document for the bundle. The
|
|
236
|
+
* exported document inlines every receipt's signed payload + signature
|
|
237
|
+
* and the JWKS URL so an auditor can re-verify offline.
|
|
238
|
+
*/
|
|
239
|
+
exportComplianceBundle(bundleId: string): Promise<Record<string, unknown>>;
|
|
240
|
+
getBundleInclusionProof(bundleId: string, receiptId: string): Promise<Record<string, unknown>>;
|
|
241
|
+
/**
|
|
242
|
+
* Score the agent's recent behavior against its active baseline.
|
|
243
|
+
* Read-only — does NOT persist an alert. Use this for dashboards.
|
|
244
|
+
*/
|
|
245
|
+
getDriftStatus(agentId: string, lookbackHours?: number): Promise<Record<string, unknown>>;
|
|
246
|
+
/** Compute a behavioral baseline from production action history. */
|
|
247
|
+
computeDriftBaseline(params: {
|
|
248
|
+
agentId: string;
|
|
249
|
+
windowStart: string;
|
|
250
|
+
windowEnd: string;
|
|
251
|
+
activate?: boolean;
|
|
252
|
+
}): Promise<Record<string, unknown>>;
|
|
253
|
+
/** Seed a baseline from a config dict (for cold-start agents). */
|
|
254
|
+
seedSyntheticBaseline(params: {
|
|
255
|
+
agentId: string;
|
|
256
|
+
expectedDistribution: Record<string, number>;
|
|
257
|
+
expectedActionsPerDay: number;
|
|
258
|
+
activate?: boolean;
|
|
259
|
+
}): Promise<Record<string, unknown>>;
|
|
260
|
+
/** Score the current window and persist an alert if it exceeds the threshold. */
|
|
261
|
+
runDriftCheck(agentId: string, lookbackHours?: number): Promise<Record<string, unknown> | null>;
|
|
262
|
+
listDriftAlerts(agentId: string, page?: number, acknowledged?: boolean): Promise<PaginatedList<Record<string, unknown>>>;
|
|
263
|
+
acknowledgeDriftAlert(agentId: string, alertId: string): Promise<Record<string, unknown>>;
|
|
264
|
+
/**
|
|
265
|
+
* Seal every unsettled receipt for the org into a new settlement.
|
|
266
|
+
* Admin-only. Returns the new settlement, or null if there were no
|
|
267
|
+
* unsettled receipts (no-op).
|
|
268
|
+
*/
|
|
269
|
+
createSettlement(): Promise<Record<string, unknown> | null>;
|
|
270
|
+
listSettlements(page?: number, perPage?: number): Promise<PaginatedList<Record<string, unknown>>>;
|
|
271
|
+
getSettlement(settlementId: string): Promise<Record<string, unknown>>;
|
|
272
|
+
/** Get the Merkle inclusion proof for one receipt in its settlement. */
|
|
273
|
+
getSettlementInclusionProof(receiptId: string): Promise<Record<string, unknown>>;
|
|
166
274
|
/** Create a scoped session with pre-filled defaults. */
|
|
167
275
|
session(agentId: string, defaults?: Record<string, unknown>): AiraSession;
|
|
168
276
|
/** Number of queued offline requests. */
|
package/dist/client.js
CHANGED
|
@@ -10,7 +10,7 @@ const MAX_DETAILS_LENGTH = 50_000;
|
|
|
10
10
|
function buildBody(obj) {
|
|
11
11
|
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined && v !== null));
|
|
12
12
|
}
|
|
13
|
-
function
|
|
13
|
+
function truncateDetails(text) {
|
|
14
14
|
return text.length > MAX_DETAILS_LENGTH ? text.slice(0, MAX_DETAILS_LENGTH) + "...[truncated]" : text;
|
|
15
15
|
}
|
|
16
16
|
class Aira {
|
|
@@ -46,7 +46,8 @@ class Aira {
|
|
|
46
46
|
return {};
|
|
47
47
|
const data = (await res.json().catch(() => ({ error: res.statusText, code: "UNKNOWN" })));
|
|
48
48
|
if (!res.ok) {
|
|
49
|
-
|
|
49
|
+
const message = data.message ?? res.statusText;
|
|
50
|
+
throw new types_1.AiraError(res.status, data.code ?? "UNKNOWN", message, data.details ?? {});
|
|
50
51
|
}
|
|
51
52
|
return data;
|
|
52
53
|
}
|
|
@@ -69,33 +70,72 @@ class Aira {
|
|
|
69
70
|
return this.request("POST", path, body);
|
|
70
71
|
}
|
|
71
72
|
put(path, body) {
|
|
73
|
+
if (this.queue) {
|
|
74
|
+
const qid = this.queue.enqueue("PUT", path, body);
|
|
75
|
+
return Promise.resolve({ _offline: true, _queue_id: qid });
|
|
76
|
+
}
|
|
72
77
|
return this.request("PUT", path, body);
|
|
73
78
|
}
|
|
74
79
|
del(path) {
|
|
80
|
+
if (this.queue) {
|
|
81
|
+
const qid = this.queue.enqueue("DELETE", path, {});
|
|
82
|
+
return Promise.resolve({ _offline: true, _queue_id: qid });
|
|
83
|
+
}
|
|
75
84
|
return this.request("DELETE", path);
|
|
76
85
|
}
|
|
77
86
|
paginated(data) {
|
|
78
87
|
const p = data.pagination;
|
|
79
88
|
return { data: data.data, total: p.total, page: p.page, per_page: p.per_page, has_more: p.has_more };
|
|
80
89
|
}
|
|
81
|
-
// ==================== Actions ====================
|
|
82
|
-
|
|
90
|
+
// ==================== Actions (two-step: authorize → notarize) ====================
|
|
91
|
+
/**
|
|
92
|
+
* Step 1 — Authorize an action BEFORE it executes.
|
|
93
|
+
*
|
|
94
|
+
* Returns an `Authorization` with a status:
|
|
95
|
+
* - "authorized" → safe to execute the action, then call `notarize()`
|
|
96
|
+
* - "pending_approval" → enqueue `action_id` and wait for human approval
|
|
97
|
+
*
|
|
98
|
+
* If a policy denies the action, this throws `AiraError` with code
|
|
99
|
+
* `POLICY_DENIED` (HTTP 403). Duplicate idempotent requests throw
|
|
100
|
+
* `DUPLICATE_REQUEST` (HTTP 409).
|
|
101
|
+
*/
|
|
102
|
+
async authorize(params) {
|
|
83
103
|
const body = buildBody({
|
|
84
104
|
action_type: params.actionType,
|
|
85
|
-
details:
|
|
105
|
+
details: truncateDetails(params.details),
|
|
86
106
|
agent_id: params.agentId,
|
|
87
107
|
agent_version: params.agentVersion,
|
|
108
|
+
instruction_hash: params.instructionHash,
|
|
88
109
|
model_id: params.modelId,
|
|
89
110
|
model_version: params.modelVersion,
|
|
90
|
-
instruction_hash: params.instructionHash,
|
|
91
111
|
parent_action_id: params.parentActionId,
|
|
112
|
+
endpoint_url: params.endpointUrl,
|
|
92
113
|
store_details: params.storeDetails || undefined,
|
|
93
114
|
idempotency_key: params.idempotencyKey,
|
|
94
115
|
require_approval: params.requireApproval || undefined,
|
|
95
116
|
approvers: params.approvers,
|
|
117
|
+
system_prompt_hash: params.systemPromptHash,
|
|
118
|
+
tool_inputs_hash: params.toolInputsHash,
|
|
119
|
+
model_params: params.modelParams,
|
|
120
|
+
execution_env: params.executionEnv,
|
|
96
121
|
});
|
|
97
122
|
return this.post("/actions", body);
|
|
98
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Step 2 — Notarize the outcome of an already-authorized action.
|
|
126
|
+
*
|
|
127
|
+
* Call this AFTER executing the action. Outcome is "completed" by default;
|
|
128
|
+
* pass "failed" if the action ran but failed so the audit trail captures
|
|
129
|
+
* the failure. The returned `ActionReceipt` carries the Ed25519 signature
|
|
130
|
+
* and RFC 3161 timestamp token when the status is "notarized".
|
|
131
|
+
*/
|
|
132
|
+
async notarize(params) {
|
|
133
|
+
const body = buildBody({
|
|
134
|
+
outcome: params.outcome ?? "completed",
|
|
135
|
+
outcome_details: params.outcomeDetails,
|
|
136
|
+
});
|
|
137
|
+
return this.post(`/actions/${params.actionId}/notarize`, body);
|
|
138
|
+
}
|
|
99
139
|
async getAction(actionId) {
|
|
100
140
|
return this.get(`/actions/${actionId}`);
|
|
101
141
|
}
|
|
@@ -105,8 +145,16 @@ class Aira {
|
|
|
105
145
|
}));
|
|
106
146
|
return this.paginated(data);
|
|
107
147
|
}
|
|
108
|
-
|
|
109
|
-
|
|
148
|
+
/**
|
|
149
|
+
* Add a human co-signature to an action that already exists.
|
|
150
|
+
*
|
|
151
|
+
* This is distinct from the authorization gate (it runs against `/cosign`,
|
|
152
|
+
* not `/authorize`). It records that a specific human has acknowledged or
|
|
153
|
+
* signed off on an action that was already authorized and notarized.
|
|
154
|
+
* Requires JWT auth (dashboard user, not an API key).
|
|
155
|
+
*/
|
|
156
|
+
async cosign(params) {
|
|
157
|
+
return this.post(`/actions/${params.actionId}/cosign`, {});
|
|
110
158
|
}
|
|
111
159
|
async setLegalHold(actionId) {
|
|
112
160
|
return this.post(`/actions/${actionId}/hold`, {});
|
|
@@ -333,6 +381,112 @@ class Aira {
|
|
|
333
381
|
async verifyReputation(slug) {
|
|
334
382
|
return this.get(`/agents/${slug}/reputation/verify`);
|
|
335
383
|
}
|
|
384
|
+
// ==================== Replay context (F10) ====================
|
|
385
|
+
/**
|
|
386
|
+
* Get all reproducibility metadata stored for an action.
|
|
387
|
+
*
|
|
388
|
+
* Returns the system_prompt_hash, tool_inputs_hash, model_params,
|
|
389
|
+
* execution_env, and other knobs that an external replay tool
|
|
390
|
+
* needs to confirm it has the same inputs as the original run.
|
|
391
|
+
*/
|
|
392
|
+
async getReplayContext(actionId) {
|
|
393
|
+
return this.get(`/actions/${actionId}/replay-context`);
|
|
394
|
+
}
|
|
395
|
+
// ==================== Compliance bundles ====================
|
|
396
|
+
/**
|
|
397
|
+
* Seal a regulator-ready evidence bundle for a date range.
|
|
398
|
+
*
|
|
399
|
+
* `framework` must be one of: `eu_ai_act_art12`, `iso_42001`,
|
|
400
|
+
* `aiuc_1`, `soc_2_cc7`, `raw`.
|
|
401
|
+
*/
|
|
402
|
+
async createComplianceBundle(params) {
|
|
403
|
+
const body = buildBody({
|
|
404
|
+
framework: params.framework,
|
|
405
|
+
period_start: params.periodStart,
|
|
406
|
+
period_end: params.periodEnd,
|
|
407
|
+
title: params.title,
|
|
408
|
+
agent_filter: params.agentFilter,
|
|
409
|
+
idempotency_key: params.idempotencyKey,
|
|
410
|
+
});
|
|
411
|
+
return this.post("/compliance/bundles", body);
|
|
412
|
+
}
|
|
413
|
+
async listComplianceBundles(page = 1, perPage = 20) {
|
|
414
|
+
const data = await this.get(`/compliance/bundles?page=${page}&per_page=${perPage}`);
|
|
415
|
+
return this.paginated(data);
|
|
416
|
+
}
|
|
417
|
+
async getComplianceBundle(bundleId) {
|
|
418
|
+
return this.get(`/compliance/bundles/${bundleId}`);
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Download the self-contained JSON document for the bundle. The
|
|
422
|
+
* exported document inlines every receipt's signed payload + signature
|
|
423
|
+
* and the JWKS URL so an auditor can re-verify offline.
|
|
424
|
+
*/
|
|
425
|
+
async exportComplianceBundle(bundleId) {
|
|
426
|
+
return this.get(`/compliance/bundles/${bundleId}/export`);
|
|
427
|
+
}
|
|
428
|
+
async getBundleInclusionProof(bundleId, receiptId) {
|
|
429
|
+
return this.get(`/compliance/bundles/${bundleId}/inclusion-proof/${receiptId}`);
|
|
430
|
+
}
|
|
431
|
+
// ==================== Drift detection ====================
|
|
432
|
+
/**
|
|
433
|
+
* Score the agent's recent behavior against its active baseline.
|
|
434
|
+
* Read-only — does NOT persist an alert. Use this for dashboards.
|
|
435
|
+
*/
|
|
436
|
+
async getDriftStatus(agentId, lookbackHours = 24) {
|
|
437
|
+
return this.get(`/agents/${agentId}/drift?lookback_hours=${lookbackHours}`);
|
|
438
|
+
}
|
|
439
|
+
/** Compute a behavioral baseline from production action history. */
|
|
440
|
+
async computeDriftBaseline(params) {
|
|
441
|
+
return this.post(`/agents/${params.agentId}/drift/baseline`, buildBody({
|
|
442
|
+
window_start: params.windowStart,
|
|
443
|
+
window_end: params.windowEnd,
|
|
444
|
+
activate: params.activate ?? true,
|
|
445
|
+
}));
|
|
446
|
+
}
|
|
447
|
+
/** Seed a baseline from a config dict (for cold-start agents). */
|
|
448
|
+
async seedSyntheticBaseline(params) {
|
|
449
|
+
return this.post(`/agents/${params.agentId}/drift/baseline/synthetic`, buildBody({
|
|
450
|
+
expected_distribution: params.expectedDistribution,
|
|
451
|
+
expected_actions_per_day: params.expectedActionsPerDay,
|
|
452
|
+
activate: params.activate ?? true,
|
|
453
|
+
}));
|
|
454
|
+
}
|
|
455
|
+
/** Score the current window and persist an alert if it exceeds the threshold. */
|
|
456
|
+
async runDriftCheck(agentId, lookbackHours = 24) {
|
|
457
|
+
return this.post(`/agents/${agentId}/drift/check?lookback_hours=${lookbackHours}`, {});
|
|
458
|
+
}
|
|
459
|
+
async listDriftAlerts(agentId, page = 1, acknowledged) {
|
|
460
|
+
let params = `page=${page}&per_page=50`;
|
|
461
|
+
if (acknowledged !== undefined) {
|
|
462
|
+
params += `&acknowledged=${acknowledged}`;
|
|
463
|
+
}
|
|
464
|
+
const data = await this.get(`/agents/${agentId}/drift/alerts?${params}`);
|
|
465
|
+
return this.paginated(data);
|
|
466
|
+
}
|
|
467
|
+
async acknowledgeDriftAlert(agentId, alertId) {
|
|
468
|
+
return this.post(`/agents/${agentId}/drift/alerts/${alertId}/acknowledge`, {});
|
|
469
|
+
}
|
|
470
|
+
// ==================== Merkle settlement (F8) ====================
|
|
471
|
+
/**
|
|
472
|
+
* Seal every unsettled receipt for the org into a new settlement.
|
|
473
|
+
* Admin-only. Returns the new settlement, or null if there were no
|
|
474
|
+
* unsettled receipts (no-op).
|
|
475
|
+
*/
|
|
476
|
+
async createSettlement() {
|
|
477
|
+
return this.post("/settlements", {});
|
|
478
|
+
}
|
|
479
|
+
async listSettlements(page = 1, perPage = 20) {
|
|
480
|
+
const data = await this.get(`/settlements?page=${page}&per_page=${perPage}`);
|
|
481
|
+
return this.paginated(data);
|
|
482
|
+
}
|
|
483
|
+
async getSettlement(settlementId) {
|
|
484
|
+
return this.get(`/settlements/${settlementId}`);
|
|
485
|
+
}
|
|
486
|
+
/** Get the Merkle inclusion proof for one receipt in its settlement. */
|
|
487
|
+
async getSettlementInclusionProof(receiptId) {
|
|
488
|
+
return this.get(`/settlements/inclusion-proof/${receiptId}`);
|
|
489
|
+
}
|
|
336
490
|
// ==================== Session ====================
|
|
337
491
|
/** Create a scoped session with pre-filled defaults. */
|
|
338
492
|
session(agentId, defaults) {
|
package/dist/extras/index.d.ts
CHANGED
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Aira SDK extras — framework integrations.
|
|
3
3
|
*
|
|
4
|
-
* Each integration is in its own file to avoid importing unnecessary
|
|
5
|
-
* Import directly from the subpath:
|
|
4
|
+
* Each integration is in its own file to avoid importing unnecessary
|
|
5
|
+
* dependencies. Import directly from the subpath:
|
|
6
6
|
*
|
|
7
7
|
* import { AiraCallbackHandler } from "aira-sdk/extras/langchain";
|
|
8
8
|
* import { AiraVercelMiddleware } from "aira-sdk/extras/vercel-ai";
|
|
9
9
|
* import { AiraGuardrail } from "aira-sdk/extras/openai-agents";
|
|
10
10
|
* import { createServer } from "aira-sdk/extras/mcp";
|
|
11
11
|
* import { verifySignature, parseEvent } from "aira-sdk/extras/webhooks";
|
|
12
|
+
*
|
|
13
|
+
* Every integration is honestly labeled as one of three kinds:
|
|
14
|
+
*
|
|
15
|
+
* "gate" — intercepts before execution and can deny. authorize()
|
|
16
|
+
* runs first; if the policy engine denies, the wrapped
|
|
17
|
+
* call never runs.
|
|
18
|
+
* "audit" — runs after execution because the host framework does not
|
|
19
|
+
* expose a pre-execution hook that can abort. Aira still
|
|
20
|
+
* records a signed receipt; it just cannot prevent the
|
|
21
|
+
* action.
|
|
22
|
+
* "adapter" — exposes Aira's own API as a tool the host framework can
|
|
23
|
+
* call. Neither a gate nor an audit hook over other tools.
|
|
24
|
+
*
|
|
25
|
+
* The INTEGRATIONS registry below is the single source of truth — the
|
|
26
|
+
* README integration matrix is generated from it so the docs cannot
|
|
27
|
+
* drift from the code.
|
|
12
28
|
*/
|
|
13
29
|
export { AiraCallbackHandler } from "./langchain";
|
|
14
30
|
export { AiraVercelMiddleware } from "./vercel-ai";
|
|
@@ -19,3 +35,34 @@ export { verifySignature, parseEvent, WebhookEventType } from "./webhooks";
|
|
|
19
35
|
export type { WebhookEvent, WebhookEventTypeName } from "./webhooks";
|
|
20
36
|
export { checkTrust } from "./trust";
|
|
21
37
|
export type { TrustPolicy, TrustContext } from "./trust";
|
|
38
|
+
export type IntegrationKind = "gate" | "audit" | "adapter";
|
|
39
|
+
export interface IntegrationSpec {
|
|
40
|
+
/** Human display name. */
|
|
41
|
+
name: string;
|
|
42
|
+
/** SDK subpath: aira-sdk/extras/{module}. */
|
|
43
|
+
module: string;
|
|
44
|
+
/** Primary exported symbol. */
|
|
45
|
+
symbol: string;
|
|
46
|
+
/** Honest classification. */
|
|
47
|
+
kind: IntegrationKind;
|
|
48
|
+
/**
|
|
49
|
+
* True if Aira can intercept and deny BEFORE the underlying call runs.
|
|
50
|
+
* Must be true for kind=="gate" and false otherwise.
|
|
51
|
+
*/
|
|
52
|
+
preExecutionGate: boolean;
|
|
53
|
+
/** What the integration wraps. */
|
|
54
|
+
surface: string;
|
|
55
|
+
/** Why this is gate / audit / adapter — surface in docs and tests. */
|
|
56
|
+
notes: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The single source of truth for the integration matrix. Tests pin this
|
|
60
|
+
* registry; the README is generated from it. To add a new integration:
|
|
61
|
+
*
|
|
62
|
+
* 1. Implement the file under src/extras/
|
|
63
|
+
* 2. Add an IntegrationSpec entry here
|
|
64
|
+
* 3. Run `npm test` — failing tests will tell you what to update
|
|
65
|
+
*/
|
|
66
|
+
export declare const INTEGRATIONS: readonly IntegrationSpec[];
|
|
67
|
+
/** Render INTEGRATIONS as a Markdown table for the README. */
|
|
68
|
+
export declare function integrationMatrixMarkdown(): string;
|
package/dist/extras/index.js
CHANGED
|
@@ -2,17 +2,34 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Aira SDK extras — framework integrations.
|
|
4
4
|
*
|
|
5
|
-
* Each integration is in its own file to avoid importing unnecessary
|
|
6
|
-
* Import directly from the subpath:
|
|
5
|
+
* Each integration is in its own file to avoid importing unnecessary
|
|
6
|
+
* dependencies. Import directly from the subpath:
|
|
7
7
|
*
|
|
8
8
|
* import { AiraCallbackHandler } from "aira-sdk/extras/langchain";
|
|
9
9
|
* import { AiraVercelMiddleware } from "aira-sdk/extras/vercel-ai";
|
|
10
10
|
* import { AiraGuardrail } from "aira-sdk/extras/openai-agents";
|
|
11
11
|
* import { createServer } from "aira-sdk/extras/mcp";
|
|
12
12
|
* import { verifySignature, parseEvent } from "aira-sdk/extras/webhooks";
|
|
13
|
+
*
|
|
14
|
+
* Every integration is honestly labeled as one of three kinds:
|
|
15
|
+
*
|
|
16
|
+
* "gate" — intercepts before execution and can deny. authorize()
|
|
17
|
+
* runs first; if the policy engine denies, the wrapped
|
|
18
|
+
* call never runs.
|
|
19
|
+
* "audit" — runs after execution because the host framework does not
|
|
20
|
+
* expose a pre-execution hook that can abort. Aira still
|
|
21
|
+
* records a signed receipt; it just cannot prevent the
|
|
22
|
+
* action.
|
|
23
|
+
* "adapter" — exposes Aira's own API as a tool the host framework can
|
|
24
|
+
* call. Neither a gate nor an audit hook over other tools.
|
|
25
|
+
*
|
|
26
|
+
* The INTEGRATIONS registry below is the single source of truth — the
|
|
27
|
+
* README integration matrix is generated from it so the docs cannot
|
|
28
|
+
* drift from the code.
|
|
13
29
|
*/
|
|
14
30
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.checkTrust = exports.WebhookEventType = exports.parseEvent = exports.verifySignature = exports.handleToolCall = exports.getTools = exports.createServer = exports.AiraGuardrail = exports.AiraVercelMiddleware = exports.AiraCallbackHandler = void 0;
|
|
31
|
+
exports.INTEGRATIONS = exports.checkTrust = exports.WebhookEventType = exports.parseEvent = exports.verifySignature = exports.handleToolCall = exports.getTools = exports.createServer = exports.AiraGuardrail = exports.AiraVercelMiddleware = exports.AiraCallbackHandler = void 0;
|
|
32
|
+
exports.integrationMatrixMarkdown = integrationMatrixMarkdown;
|
|
16
33
|
var langchain_1 = require("./langchain");
|
|
17
34
|
Object.defineProperty(exports, "AiraCallbackHandler", { enumerable: true, get: function () { return langchain_1.AiraCallbackHandler; } });
|
|
18
35
|
var vercel_ai_1 = require("./vercel-ai");
|
|
@@ -29,3 +46,73 @@ Object.defineProperty(exports, "parseEvent", { enumerable: true, get: function (
|
|
|
29
46
|
Object.defineProperty(exports, "WebhookEventType", { enumerable: true, get: function () { return webhooks_1.WebhookEventType; } });
|
|
30
47
|
var trust_1 = require("./trust");
|
|
31
48
|
Object.defineProperty(exports, "checkTrust", { enumerable: true, get: function () { return trust_1.checkTrust; } });
|
|
49
|
+
/**
|
|
50
|
+
* The single source of truth for the integration matrix. Tests pin this
|
|
51
|
+
* registry; the README is generated from it. To add a new integration:
|
|
52
|
+
*
|
|
53
|
+
* 1. Implement the file under src/extras/
|
|
54
|
+
* 2. Add an IntegrationSpec entry here
|
|
55
|
+
* 3. Run `npm test` — failing tests will tell you what to update
|
|
56
|
+
*/
|
|
57
|
+
exports.INTEGRATIONS = [
|
|
58
|
+
{
|
|
59
|
+
name: "LangChain.js",
|
|
60
|
+
module: "langchain",
|
|
61
|
+
symbol: "AiraCallbackHandler",
|
|
62
|
+
kind: "gate",
|
|
63
|
+
preExecutionGate: true,
|
|
64
|
+
surface: "Tools (gate). Chains and LLM completions are audit-only.",
|
|
65
|
+
notes: "handleToolStart calls authorize() and throws on POLICY_DENIED so the " +
|
|
66
|
+
"tool never runs. Chain/LLM hooks are post-hoc because LangChain has " +
|
|
67
|
+
"no pre-execution chain hook that can abort.",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "Vercel AI SDK",
|
|
71
|
+
module: "vercel-ai",
|
|
72
|
+
symbol: "AiraVercelMiddleware",
|
|
73
|
+
kind: "gate",
|
|
74
|
+
preExecutionGate: true,
|
|
75
|
+
surface: "Tools via wrapTool() (gate). onFinish helpers are audit-only.",
|
|
76
|
+
notes: "wrapTool() wraps a tool's execute function so authorize() runs " +
|
|
77
|
+
"before the tool body. onStepFinish / onFinish callbacks fire after " +
|
|
78
|
+
"execution and are explicitly labeled audit-only — Vercel AI has no " +
|
|
79
|
+
"pre-step hook.",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "OpenAI Agents",
|
|
83
|
+
module: "openai-agents",
|
|
84
|
+
symbol: "AiraGuardrail",
|
|
85
|
+
kind: "gate",
|
|
86
|
+
preExecutionGate: true,
|
|
87
|
+
surface: "Tools via wrapTool()",
|
|
88
|
+
notes: "Wraps each tool function: authorize() runs before the tool body. " +
|
|
89
|
+
"Denied calls throw; failed calls notarize with outcome=failed.",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "MCP",
|
|
93
|
+
module: "mcp",
|
|
94
|
+
symbol: "createServer",
|
|
95
|
+
kind: "adapter",
|
|
96
|
+
preExecutionGate: false,
|
|
97
|
+
surface: "Server adapter (exposes Aira as MCP tools)",
|
|
98
|
+
notes: "MCP is bidirectional: the agent CHOOSES to call authorize_action / " +
|
|
99
|
+
"notarize_action. This is not a wrapper over other MCP tools — it is " +
|
|
100
|
+
"a protocol adapter that lets MCP-aware agents reach Aira.",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "Webhooks",
|
|
104
|
+
module: "webhooks",
|
|
105
|
+
symbol: "verifySignature",
|
|
106
|
+
kind: "adapter",
|
|
107
|
+
preExecutionGate: false,
|
|
108
|
+
surface: "HMAC-SHA256 webhook signature verifier",
|
|
109
|
+
notes: "Standalone HMAC verification helper. Not an agent integration.",
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
/** Render INTEGRATIONS as a Markdown table for the README. */
|
|
113
|
+
function integrationMatrixMarkdown() {
|
|
114
|
+
const header = "| Integration | Type | Pre-execution gate? | Surface | Notes |\n" +
|
|
115
|
+
"|---|---|---|---|---|";
|
|
116
|
+
const rows = exports.INTEGRATIONS.map((i) => `| **${i.name}** | ${i.kind} | ${i.preExecutionGate ? "Yes" : "No"} | ${i.surface} | ${i.notes} |`);
|
|
117
|
+
return [header, ...rows].join("\n");
|
|
118
|
+
}
|
|
@@ -1,42 +1,93 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* LangChain.js integration —
|
|
2
|
+
* LangChain.js integration — pre-execution gate + post-execution notarize.
|
|
3
3
|
*
|
|
4
4
|
* Requires: @langchain/core (peer dependency)
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* ---------------------------------------------------------------------------
|
|
7
|
+
* LIFECYCLE & DESIGN NOTES
|
|
8
|
+
* ---------------------------------------------------------------------------
|
|
9
|
+
*
|
|
10
|
+
* LangChain fires callbacks BEFORE and AFTER each tool/chain/LLM step. The
|
|
11
|
+
* `handleToolStart` / `handleChainStart` / `handleLLMStart` callbacks are
|
|
12
|
+
* genuine pre-execution hooks: if one throws, LangChain propagates the error
|
|
13
|
+
* and the tool is never executed. That means `handleToolStart` can serve as
|
|
14
|
+
* a real authorization gate — not merely an audit hook.
|
|
15
|
+
*
|
|
16
|
+
* This handler implements the two-step flow as follows:
|
|
17
|
+
*
|
|
18
|
+
* 1. handleToolStart → aira.authorize()
|
|
19
|
+
* - If the backend returns "authorized" we cache the action_id
|
|
20
|
+
* keyed by LangChain's `runId`, then return so the tool executes.
|
|
21
|
+
* - If the backend throws POLICY_DENIED we propagate the error,
|
|
22
|
+
* which prevents the tool from running at all (real gate).
|
|
23
|
+
* - If the backend returns "pending_approval" we throw an error
|
|
24
|
+
* so the tool does NOT execute until a human approves.
|
|
25
|
+
*
|
|
26
|
+
* 2. handleToolEnd / handleToolError → aira.notarize()
|
|
27
|
+
* - Notarize the outcome as "completed" or "failed". This closes
|
|
28
|
+
* the two-step flow and produces a cryptographic receipt.
|
|
29
|
+
*
|
|
30
|
+
* The same pattern applies to chains and LLM calls. For chains and LLMs we
|
|
31
|
+
* use "chain_run" / "llm_run" as action types so you can filter by them.
|
|
32
|
+
*
|
|
33
|
+
* If the integration cannot reach Aira at authorize time, it fails open with
|
|
34
|
+
* a console warning — your agent keeps running, but no receipt is produced.
|
|
35
|
+
* To make it fail closed, set `strict: true` in the options.
|
|
10
36
|
*/
|
|
11
37
|
import type { Aira } from "../client";
|
|
12
38
|
import type { TrustPolicy, TrustContext } from "./trust";
|
|
13
39
|
export type { TrustPolicy, TrustContext } from "./trust";
|
|
40
|
+
export interface AiraCallbackHandlerOptions {
|
|
41
|
+
modelId?: string;
|
|
42
|
+
actionTypes?: Record<string, string>;
|
|
43
|
+
trustPolicy?: TrustPolicy;
|
|
44
|
+
/** Fail closed if authorize() fails (network, 5xx). Default: false. */
|
|
45
|
+
strict?: boolean;
|
|
46
|
+
}
|
|
14
47
|
export declare class AiraCallbackHandler {
|
|
15
48
|
private client;
|
|
16
49
|
private agentId;
|
|
17
50
|
private modelId?;
|
|
18
51
|
private actionTypes;
|
|
19
52
|
private trustPolicy?;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
});
|
|
53
|
+
private strict;
|
|
54
|
+
/** runId → action_id cache so handleEnd can notarize the right action. */
|
|
55
|
+
private inFlight;
|
|
56
|
+
constructor(client: Aira, agentId: string, options?: AiraCallbackHandlerOptions);
|
|
25
57
|
/**
|
|
26
58
|
* Check trust for a counterparty agent before interacting.
|
|
27
59
|
* Advisory by default — only blocks on revoked VC or unregistered agent if configured.
|
|
28
60
|
*/
|
|
29
61
|
checkTrust(counterpartyId: string): Promise<TrustContext>;
|
|
30
|
-
private
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
62
|
+
private doAuthorize;
|
|
63
|
+
private doNotarize;
|
|
64
|
+
/** Called BEFORE a tool runs — authorization gate. */
|
|
65
|
+
handleToolStart(tool: {
|
|
66
|
+
name?: string;
|
|
67
|
+
} | string | unknown, input: string, runId: string): Promise<void>;
|
|
68
|
+
/** Called AFTER a tool completes successfully. */
|
|
69
|
+
handleToolEnd(output: string, runId: string, name?: string): Promise<void>;
|
|
70
|
+
/** Called if a tool throws. */
|
|
71
|
+
handleToolError(err: Error, runId: string, name?: string): Promise<void>;
|
|
72
|
+
/** Called BEFORE a chain runs. */
|
|
73
|
+
handleChainStart(chain: {
|
|
74
|
+
name?: string;
|
|
75
|
+
} | unknown, inputs: Record<string, unknown>, runId: string): Promise<void>;
|
|
76
|
+
/** Called AFTER a chain completes. */
|
|
77
|
+
handleChainEnd(outputs: Record<string, unknown>, runId: string): Promise<void>;
|
|
78
|
+
/** Called if a chain throws. */
|
|
79
|
+
handleChainError(err: Error, runId: string): Promise<void>;
|
|
80
|
+
/** Called BEFORE an LLM runs. */
|
|
81
|
+
handleLLMStart(llm: unknown, prompts: string[], runId: string): Promise<void>;
|
|
82
|
+
/** Called AFTER an LLM completes. */
|
|
83
|
+
handleLLMEnd(response: {
|
|
84
|
+
generations?: unknown[];
|
|
85
|
+
} | number, runId: string): Promise<void>;
|
|
86
|
+
/** Called if an LLM throws. */
|
|
87
|
+
handleLLMError(err: Error, runId: string): Promise<void>;
|
|
37
88
|
/**
|
|
38
89
|
* Returns a LangChain-compatible callbacks object.
|
|
39
90
|
* Use with: chain.invoke(input, { callbacks: [handler.asCallbacks()] })
|
|
40
91
|
*/
|
|
41
|
-
asCallbacks(): Record<string, (...args: unknown[]) => void>;
|
|
92
|
+
asCallbacks(): Record<string, (...args: unknown[]) => Promise<void> | void>;
|
|
42
93
|
}
|