aira-sdk 1.0.0 → 2.4.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 +300 -104
- package/dist/client.d.ts +195 -4
- package/dist/client.js +347 -6
- 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 +70 -9
- 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/index.js +5 -1
- package/dist/session.d.ts +15 -4
- package/dist/session.js +11 -3
- package/dist/types.d.ts +197 -20
- package/dist/types.js +26 -5
- package/package.json +1 -1
|
@@ -1,37 +1,69 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OpenAI
|
|
2
|
+
* OpenAI Agents SDK integration — pre-execution gate via tool wrapping.
|
|
3
3
|
*
|
|
4
|
-
* Requires: openai (peer dependency)
|
|
4
|
+
* Requires: @openai/agents (peer dependency)
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* ---------------------------------------------------------------------------
|
|
7
|
+
* LIFECYCLE & DESIGN NOTES
|
|
8
|
+
* ---------------------------------------------------------------------------
|
|
9
|
+
*
|
|
10
|
+
* The OpenAI Agents SDK supports guardrails that run BEFORE the model produces
|
|
11
|
+
* output (`inputGuardrails`) and BEFORE a tool executes (via wrapping the
|
|
12
|
+
* tool's `execute` / function). Either path can throw to abort the run, so
|
|
13
|
+
* both qualify as a REAL authorization gate.
|
|
14
|
+
*
|
|
15
|
+
* `AiraGuardrail.wrapTool()` is the cleanest integration: it calls
|
|
16
|
+
* `aira.authorize()` before the tool runs. If the backend responds with:
|
|
17
|
+
*
|
|
18
|
+
* - "authorized" → the tool runs; `aira.notarize()` is called
|
|
19
|
+
* with outcome="completed" (or "failed" on throw).
|
|
20
|
+
* - "pending_approval" → we throw. The agent never sees the tool result;
|
|
21
|
+
* it handles the error like any other tool failure.
|
|
22
|
+
* - AiraError POLICY_DENIED → rethrown. Tool is blocked entirely.
|
|
23
|
+
*
|
|
24
|
+
* Behavior on authorize network/5xx errors is controlled by `strict`:
|
|
25
|
+
* - strict=false (default) → fail open with a warning. Tool runs, no receipt.
|
|
26
|
+
* - strict=true → fail closed. Tool throws.
|
|
10
27
|
*/
|
|
11
28
|
import type { Aira } from "../client";
|
|
12
29
|
import type { TrustPolicy, TrustContext } from "./trust";
|
|
13
30
|
export type { TrustPolicy, TrustContext } from "./trust";
|
|
31
|
+
export interface AiraGuardrailOptions {
|
|
32
|
+
modelId?: string;
|
|
33
|
+
trustPolicy?: TrustPolicy;
|
|
34
|
+
/** Fail closed if authorize() fails (network, 5xx). Default: false. */
|
|
35
|
+
strict?: boolean;
|
|
36
|
+
}
|
|
14
37
|
export declare class AiraGuardrail {
|
|
15
38
|
private client;
|
|
16
39
|
private agentId;
|
|
17
40
|
private modelId?;
|
|
18
41
|
private trustPolicy?;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
trustPolicy?: TrustPolicy;
|
|
22
|
-
});
|
|
42
|
+
private strict;
|
|
43
|
+
constructor(client: Aira, agentId: string, options?: AiraGuardrailOptions);
|
|
23
44
|
/**
|
|
24
45
|
* Check trust for a counterparty agent before interacting.
|
|
25
46
|
* Advisory by default — only blocks on revoked VC or unregistered agent if configured.
|
|
26
47
|
*/
|
|
27
48
|
checkTrust(counterpartyId: string): Promise<TrustContext>;
|
|
28
|
-
private notarize;
|
|
29
|
-
/** Call after a tool execution to notarize it. */
|
|
30
|
-
onToolCall(toolName: string, args?: Record<string, unknown>): void;
|
|
31
|
-
/** Call after a tool returns to notarize the result. */
|
|
32
|
-
onToolResult(toolName: string, result?: unknown): void;
|
|
33
49
|
/**
|
|
34
|
-
*
|
|
50
|
+
* REAL GATE: call `authorize()` for a tool invocation.
|
|
51
|
+
*
|
|
52
|
+
* Returns the action_id on success, throws on POLICY_DENIED or
|
|
53
|
+
* pending_approval. Arg keys are logged (not values) to avoid leaking
|
|
54
|
+
* sensitive user input into audit trails.
|
|
55
|
+
*/
|
|
56
|
+
authorizeToolCall(toolName: string, args?: Record<string, unknown>): Promise<string | null>;
|
|
57
|
+
/** Notarize the outcome of a previously authorized tool call. */
|
|
58
|
+
notarizeToolResult(actionId: string, toolName: string, outcome: "completed" | "failed", detail: string): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* REAL GATE: wraps a tool function to gate + notarize.
|
|
61
|
+
*
|
|
62
|
+
* Flow:
|
|
63
|
+
* 1. Call `aira.authorize()` — throws POLICY_DENIED or pending_approval.
|
|
64
|
+
* 2. Run the tool.
|
|
65
|
+
* 3. Call `aira.notarize()` with outcome="completed" or "failed".
|
|
66
|
+
*
|
|
35
67
|
* No raw user data is sent — only tool name, arg keys, and output length.
|
|
36
68
|
*/
|
|
37
69
|
wrapTool<T extends (...args: unknown[]) => unknown>(toolFn: T, toolName?: string): T;
|
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* OpenAI
|
|
3
|
+
* OpenAI Agents SDK integration — pre-execution gate via tool wrapping.
|
|
4
4
|
*
|
|
5
|
-
* Requires: openai (peer dependency)
|
|
5
|
+
* Requires: @openai/agents (peer dependency)
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* ---------------------------------------------------------------------------
|
|
8
|
+
* LIFECYCLE & DESIGN NOTES
|
|
9
|
+
* ---------------------------------------------------------------------------
|
|
10
|
+
*
|
|
11
|
+
* The OpenAI Agents SDK supports guardrails that run BEFORE the model produces
|
|
12
|
+
* output (`inputGuardrails`) and BEFORE a tool executes (via wrapping the
|
|
13
|
+
* tool's `execute` / function). Either path can throw to abort the run, so
|
|
14
|
+
* both qualify as a REAL authorization gate.
|
|
15
|
+
*
|
|
16
|
+
* `AiraGuardrail.wrapTool()` is the cleanest integration: it calls
|
|
17
|
+
* `aira.authorize()` before the tool runs. If the backend responds with:
|
|
18
|
+
*
|
|
19
|
+
* - "authorized" → the tool runs; `aira.notarize()` is called
|
|
20
|
+
* with outcome="completed" (or "failed" on throw).
|
|
21
|
+
* - "pending_approval" → we throw. The agent never sees the tool result;
|
|
22
|
+
* it handles the error like any other tool failure.
|
|
23
|
+
* - AiraError POLICY_DENIED → rethrown. Tool is blocked entirely.
|
|
24
|
+
*
|
|
25
|
+
* Behavior on authorize network/5xx errors is controlled by `strict`:
|
|
26
|
+
* - strict=false (default) → fail open with a warning. Tool runs, no receipt.
|
|
27
|
+
* - strict=true → fail closed. Tool throws.
|
|
11
28
|
*/
|
|
12
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
30
|
exports.AiraGuardrail = void 0;
|
|
@@ -18,11 +35,13 @@ class AiraGuardrail {
|
|
|
18
35
|
agentId;
|
|
19
36
|
modelId;
|
|
20
37
|
trustPolicy;
|
|
38
|
+
strict;
|
|
21
39
|
constructor(client, agentId, options) {
|
|
22
40
|
this.client = client;
|
|
23
41
|
this.agentId = agentId;
|
|
24
42
|
this.modelId = options?.modelId;
|
|
25
43
|
this.trustPolicy = options?.trustPolicy;
|
|
44
|
+
this.strict = options?.strict ?? false;
|
|
26
45
|
}
|
|
27
46
|
/**
|
|
28
47
|
* Check trust for a counterparty agent before interacting.
|
|
@@ -34,34 +53,61 @@ class AiraGuardrail {
|
|
|
34
53
|
}
|
|
35
54
|
return (0, trust_1.checkTrust)(this.client, this.trustPolicy, counterpartyId);
|
|
36
55
|
}
|
|
37
|
-
|
|
56
|
+
/**
|
|
57
|
+
* REAL GATE: call `authorize()` for a tool invocation.
|
|
58
|
+
*
|
|
59
|
+
* Returns the action_id on success, throws on POLICY_DENIED or
|
|
60
|
+
* pending_approval. Arg keys are logged (not values) to avoid leaking
|
|
61
|
+
* sensitive user input into audit trails.
|
|
62
|
+
*/
|
|
63
|
+
async authorizeToolCall(toolName, args) {
|
|
64
|
+
const argKeys = Object.keys(args ?? {});
|
|
38
65
|
try {
|
|
39
|
-
const
|
|
40
|
-
actionType,
|
|
41
|
-
details:
|
|
66
|
+
const auth = await this.client.authorize({
|
|
67
|
+
actionType: "tool_call",
|
|
68
|
+
details: `Tool '${toolName}' called. Arg keys: [${argKeys.join(", ")}]`.slice(0, MAX_DETAILS),
|
|
42
69
|
agentId: this.agentId,
|
|
43
|
-
|
|
44
|
-
if (this.modelId)
|
|
45
|
-
params.modelId = this.modelId;
|
|
46
|
-
this.client.notarize(params).catch((e) => {
|
|
47
|
-
console.warn("Aira notarize failed (non-blocking):", e);
|
|
70
|
+
modelId: this.modelId,
|
|
48
71
|
});
|
|
72
|
+
if (auth.status === "pending_approval") {
|
|
73
|
+
const err = new Error(`Aira: tool '${toolName}' is pending human approval (action_id=${auth.action_id}). Tool execution blocked.`);
|
|
74
|
+
err.code = "PENDING_APPROVAL";
|
|
75
|
+
throw err;
|
|
76
|
+
}
|
|
77
|
+
return auth.action_id;
|
|
49
78
|
}
|
|
50
79
|
catch (e) {
|
|
51
|
-
|
|
80
|
+
const err = e;
|
|
81
|
+
// Always propagate authorization-layer rejections.
|
|
82
|
+
if (err.code === "POLICY_DENIED" || err.code === "PENDING_APPROVAL")
|
|
83
|
+
throw e;
|
|
84
|
+
if (this.strict)
|
|
85
|
+
throw e;
|
|
86
|
+
console.warn("Aira authorize failed (fail-open):", err);
|
|
87
|
+
return null;
|
|
52
88
|
}
|
|
53
89
|
}
|
|
54
|
-
/**
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
90
|
+
/** Notarize the outcome of a previously authorized tool call. */
|
|
91
|
+
async notarizeToolResult(actionId, toolName, outcome, detail) {
|
|
92
|
+
try {
|
|
93
|
+
await this.client.notarize({
|
|
94
|
+
actionId,
|
|
95
|
+
outcome,
|
|
96
|
+
outcomeDetails: `Tool '${toolName}' ${outcome}: ${detail}`.slice(0, MAX_DETAILS),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
console.warn("Aira notarize failed (non-blocking):", e);
|
|
101
|
+
}
|
|
62
102
|
}
|
|
63
103
|
/**
|
|
64
|
-
*
|
|
104
|
+
* REAL GATE: wraps a tool function to gate + notarize.
|
|
105
|
+
*
|
|
106
|
+
* Flow:
|
|
107
|
+
* 1. Call `aira.authorize()` — throws POLICY_DENIED or pending_approval.
|
|
108
|
+
* 2. Run the tool.
|
|
109
|
+
* 3. Call `aira.notarize()` with outcome="completed" or "failed".
|
|
110
|
+
*
|
|
65
111
|
* No raw user data is sent — only tool name, arg keys, and output length.
|
|
66
112
|
*/
|
|
67
113
|
wrapTool(toolFn, toolName) {
|
|
@@ -71,10 +117,20 @@ class AiraGuardrail {
|
|
|
71
117
|
const kwargs = args.length > 0 && typeof args[0] === "object" && args[0]
|
|
72
118
|
? args[0]
|
|
73
119
|
: undefined;
|
|
74
|
-
self.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
120
|
+
const actionId = await self.authorizeToolCall(name, kwargs);
|
|
121
|
+
try {
|
|
122
|
+
const result = await toolFn.apply(this, args);
|
|
123
|
+
if (actionId) {
|
|
124
|
+
await self.notarizeToolResult(actionId, name, "completed", `result length ${String(result).length} chars`);
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
if (actionId) {
|
|
130
|
+
await self.notarizeToolResult(actionId, name, "failed", err?.message ?? String(err));
|
|
131
|
+
}
|
|
132
|
+
throw err;
|
|
133
|
+
}
|
|
78
134
|
};
|
|
79
135
|
return wrapped;
|
|
80
136
|
}
|
|
@@ -1,48 +1,85 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Vercel AI SDK integration —
|
|
2
|
+
* Vercel AI SDK integration — pre-execution gate via tool wrapping.
|
|
3
3
|
*
|
|
4
4
|
* Requires: ai (Vercel AI SDK, peer dependency)
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* ---------------------------------------------------------------------------
|
|
7
|
+
* LIFECYCLE & DESIGN NOTES
|
|
8
|
+
* ---------------------------------------------------------------------------
|
|
9
|
+
*
|
|
10
|
+
* Vercel AI SDK exposes two integration points:
|
|
11
|
+
*
|
|
12
|
+
* 1. `onStepFinish` / `onFinish` callbacks on `generateText` / `streamText`
|
|
13
|
+
* — these fire AFTER each step or after the whole generation completes.
|
|
14
|
+
* They are post-hoc only; they cannot block a tool from running.
|
|
15
|
+
*
|
|
16
|
+
* 2. The per-tool `execute` function (user code) — this runs BEFORE the
|
|
17
|
+
* model sees the tool result. Wrapping it is the only place where we
|
|
18
|
+
* can synchronously gate execution.
|
|
19
|
+
*
|
|
20
|
+
* Therefore the `wrapTool()` method is the real authorization gate: it calls
|
|
21
|
+
* `aira.authorize()` before invoking the underlying tool and `aira.notarize()`
|
|
22
|
+
* after. If authorize returns `pending_approval` we throw so the tool does
|
|
23
|
+
* NOT run; the model will see the tool result as an error and react
|
|
24
|
+
* accordingly (typically by stopping or asking the user).
|
|
25
|
+
*
|
|
26
|
+
* The `onStepFinish` / `onFinish` helpers below are AUDIT-ONLY: they cannot
|
|
27
|
+
* gate execution (Vercel AI has no pre-step hook), and they run after the
|
|
28
|
+
* tool has already executed. They produce receipts for the overall
|
|
29
|
+
* generation as an audit trail, not as an authorization boundary.
|
|
10
30
|
*/
|
|
11
31
|
import type { Aira } from "../client";
|
|
12
32
|
import type { TrustPolicy, TrustContext } from "./trust";
|
|
13
33
|
export type { TrustPolicy, TrustContext } from "./trust";
|
|
34
|
+
export interface AiraVercelMiddlewareOptions {
|
|
35
|
+
modelId?: string;
|
|
36
|
+
trustPolicy?: TrustPolicy;
|
|
37
|
+
/** Fail closed if authorize() fails (network, 5xx). Default: false. */
|
|
38
|
+
strict?: boolean;
|
|
39
|
+
}
|
|
14
40
|
export declare class AiraVercelMiddleware {
|
|
15
41
|
private client;
|
|
16
42
|
private agentId;
|
|
17
43
|
private modelId?;
|
|
18
44
|
private trustPolicy?;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
trustPolicy?: TrustPolicy;
|
|
22
|
-
});
|
|
45
|
+
private strict;
|
|
46
|
+
constructor(client: Aira, agentId: string, options?: AiraVercelMiddlewareOptions);
|
|
23
47
|
/**
|
|
24
48
|
* Check trust for a counterparty agent before interacting.
|
|
25
49
|
* Advisory by default — only blocks on revoked VC or unregistered agent if configured.
|
|
26
50
|
*/
|
|
27
51
|
checkTrust(counterpartyId: string): Promise<TrustContext>;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* AUDIT-ONLY: log a post-hoc receipt for a generation step.
|
|
54
|
+
* Vercel AI has no pre-step hook; this cannot gate execution.
|
|
55
|
+
*/
|
|
56
|
+
private auditFinish;
|
|
57
|
+
/**
|
|
58
|
+
* AUDIT-ONLY: called after a step finishes.
|
|
59
|
+
* Cannot block the step — it has already run.
|
|
60
|
+
*/
|
|
34
61
|
onStepFinish(stepType: string, tokenCount?: number): void;
|
|
35
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* AUDIT-ONLY: called after full generation finishes.
|
|
64
|
+
*/
|
|
36
65
|
onFinish(finishReason: string, totalTokens?: number): void;
|
|
37
66
|
/**
|
|
38
67
|
* Returns a Vercel AI SDK-compatible callbacks object for streamText/generateText.
|
|
68
|
+
* These callbacks are AUDIT-ONLY and cannot gate execution.
|
|
39
69
|
*
|
|
40
70
|
* Usage:
|
|
41
71
|
* const result = await streamText({ ...opts, ...middleware.asCallbacks() });
|
|
42
72
|
*/
|
|
43
73
|
asCallbacks(): Record<string, (...args: unknown[]) => void>;
|
|
44
74
|
/**
|
|
45
|
-
*
|
|
75
|
+
* REAL GATE: wraps a tool's execute function so that:
|
|
76
|
+
* 1. `aira.authorize()` runs BEFORE the tool — throws on POLICY_DENIED
|
|
77
|
+
* or pending_approval, which prevents the tool from executing.
|
|
78
|
+
* 2. If authorized, the tool runs.
|
|
79
|
+
* 3. `aira.notarize()` runs AFTER, with outcome "completed" or "failed".
|
|
80
|
+
*
|
|
81
|
+
* This is the recommended integration point for Vercel AI because it's
|
|
82
|
+
* the only place where pre-execution gating is possible.
|
|
46
83
|
*/
|
|
47
84
|
wrapTool<T extends (...args: unknown[]) => unknown>(toolFn: T, toolName: string): T;
|
|
48
85
|
}
|
package/dist/extras/vercel-ai.js
CHANGED
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Vercel AI SDK integration —
|
|
3
|
+
* Vercel AI SDK integration — pre-execution gate via tool wrapping.
|
|
4
4
|
*
|
|
5
5
|
* Requires: ai (Vercel AI SDK, peer dependency)
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* ---------------------------------------------------------------------------
|
|
8
|
+
* LIFECYCLE & DESIGN NOTES
|
|
9
|
+
* ---------------------------------------------------------------------------
|
|
10
|
+
*
|
|
11
|
+
* Vercel AI SDK exposes two integration points:
|
|
12
|
+
*
|
|
13
|
+
* 1. `onStepFinish` / `onFinish` callbacks on `generateText` / `streamText`
|
|
14
|
+
* — these fire AFTER each step or after the whole generation completes.
|
|
15
|
+
* They are post-hoc only; they cannot block a tool from running.
|
|
16
|
+
*
|
|
17
|
+
* 2. The per-tool `execute` function (user code) — this runs BEFORE the
|
|
18
|
+
* model sees the tool result. Wrapping it is the only place where we
|
|
19
|
+
* can synchronously gate execution.
|
|
20
|
+
*
|
|
21
|
+
* Therefore the `wrapTool()` method is the real authorization gate: it calls
|
|
22
|
+
* `aira.authorize()` before invoking the underlying tool and `aira.notarize()`
|
|
23
|
+
* after. If authorize returns `pending_approval` we throw so the tool does
|
|
24
|
+
* NOT run; the model will see the tool result as an error and react
|
|
25
|
+
* accordingly (typically by stopping or asking the user).
|
|
26
|
+
*
|
|
27
|
+
* The `onStepFinish` / `onFinish` helpers below are AUDIT-ONLY: they cannot
|
|
28
|
+
* gate execution (Vercel AI has no pre-step hook), and they run after the
|
|
29
|
+
* tool has already executed. They produce receipts for the overall
|
|
30
|
+
* generation as an audit trail, not as an authorization boundary.
|
|
11
31
|
*/
|
|
12
32
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
33
|
exports.AiraVercelMiddleware = void 0;
|
|
@@ -18,11 +38,13 @@ class AiraVercelMiddleware {
|
|
|
18
38
|
agentId;
|
|
19
39
|
modelId;
|
|
20
40
|
trustPolicy;
|
|
41
|
+
strict;
|
|
21
42
|
constructor(client, agentId, options) {
|
|
22
43
|
this.client = client;
|
|
23
44
|
this.agentId = agentId;
|
|
24
45
|
this.modelId = options?.modelId;
|
|
25
46
|
this.trustPolicy = options?.trustPolicy;
|
|
47
|
+
this.strict = options?.strict ?? false;
|
|
26
48
|
}
|
|
27
49
|
/**
|
|
28
50
|
* Check trust for a counterparty agent before interacting.
|
|
@@ -34,41 +56,43 @@ class AiraVercelMiddleware {
|
|
|
34
56
|
}
|
|
35
57
|
return (0, trust_1.checkTrust)(this.client, this.trustPolicy, counterpartyId);
|
|
36
58
|
}
|
|
37
|
-
|
|
59
|
+
/**
|
|
60
|
+
* AUDIT-ONLY: log a post-hoc receipt for a generation step.
|
|
61
|
+
* Vercel AI has no pre-step hook; this cannot gate execution.
|
|
62
|
+
*/
|
|
63
|
+
async auditFinish(actionType, details) {
|
|
38
64
|
try {
|
|
39
|
-
const
|
|
65
|
+
const auth = await this.client.authorize({
|
|
40
66
|
actionType,
|
|
41
67
|
details: details.slice(0, MAX_DETAILS),
|
|
42
68
|
agentId: this.agentId,
|
|
43
|
-
|
|
44
|
-
if (this.modelId)
|
|
45
|
-
params.modelId = this.modelId;
|
|
46
|
-
this.client.notarize(params).catch((e) => {
|
|
47
|
-
console.warn("Aira notarize failed (non-blocking):", e);
|
|
69
|
+
modelId: this.modelId,
|
|
48
70
|
});
|
|
71
|
+
if (auth.status === "authorized") {
|
|
72
|
+
await this.client.notarize({ actionId: auth.action_id, outcome: "completed" });
|
|
73
|
+
}
|
|
74
|
+
// If pending_approval — just leave it; nothing to execute for audit-only.
|
|
49
75
|
}
|
|
50
76
|
catch (e) {
|
|
51
|
-
console.warn("Aira
|
|
77
|
+
console.warn("Aira audit failed (non-blocking):", e);
|
|
52
78
|
}
|
|
53
79
|
}
|
|
54
|
-
/**
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
/** Call after a tool returns to notarize the result. */
|
|
59
|
-
onToolResult(toolName, resultLength) {
|
|
60
|
-
this.notarize("tool_completed", `Tool '${toolName}' completed. Result length: ${resultLength} chars`);
|
|
61
|
-
}
|
|
62
|
-
/** Call when a text generation step completes. */
|
|
80
|
+
/**
|
|
81
|
+
* AUDIT-ONLY: called after a step finishes.
|
|
82
|
+
* Cannot block the step — it has already run.
|
|
83
|
+
*/
|
|
63
84
|
onStepFinish(stepType, tokenCount) {
|
|
64
|
-
this.
|
|
85
|
+
void this.auditFinish("step_completed", `Step '${stepType}' completed.${tokenCount != null ? ` Tokens: ${tokenCount}` : ""}`);
|
|
65
86
|
}
|
|
66
|
-
/**
|
|
87
|
+
/**
|
|
88
|
+
* AUDIT-ONLY: called after full generation finishes.
|
|
89
|
+
*/
|
|
67
90
|
onFinish(finishReason, totalTokens) {
|
|
68
|
-
this.
|
|
91
|
+
void this.auditFinish("generation_completed", `Generation completed. Reason: ${finishReason}.${totalTokens != null ? ` Total tokens: ${totalTokens}` : ""}`);
|
|
69
92
|
}
|
|
70
93
|
/**
|
|
71
94
|
* Returns a Vercel AI SDK-compatible callbacks object for streamText/generateText.
|
|
95
|
+
* These callbacks are AUDIT-ONLY and cannot gate execution.
|
|
72
96
|
*
|
|
73
97
|
* Usage:
|
|
74
98
|
* const result = await streamText({ ...opts, ...middleware.asCallbacks() });
|
|
@@ -86,15 +110,65 @@ class AiraVercelMiddleware {
|
|
|
86
110
|
};
|
|
87
111
|
}
|
|
88
112
|
/**
|
|
89
|
-
*
|
|
113
|
+
* REAL GATE: wraps a tool's execute function so that:
|
|
114
|
+
* 1. `aira.authorize()` runs BEFORE the tool — throws on POLICY_DENIED
|
|
115
|
+
* or pending_approval, which prevents the tool from executing.
|
|
116
|
+
* 2. If authorized, the tool runs.
|
|
117
|
+
* 3. `aira.notarize()` runs AFTER, with outcome "completed" or "failed".
|
|
118
|
+
*
|
|
119
|
+
* This is the recommended integration point for Vercel AI because it's
|
|
120
|
+
* the only place where pre-execution gating is possible.
|
|
90
121
|
*/
|
|
91
122
|
wrapTool(toolFn, toolName) {
|
|
92
123
|
const self = this;
|
|
93
124
|
const wrapped = async function (...args) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
125
|
+
const argKeys = args.length > 0 && typeof args[0] === "object" && args[0]
|
|
126
|
+
? Object.keys(args[0])
|
|
127
|
+
: [];
|
|
128
|
+
let actionId = null;
|
|
129
|
+
try {
|
|
130
|
+
const auth = await self.client.authorize({
|
|
131
|
+
actionType: "tool_call",
|
|
132
|
+
details: `Tool '${toolName}' called. Arg keys: [${argKeys.join(", ")}]`.slice(0, MAX_DETAILS),
|
|
133
|
+
agentId: self.agentId,
|
|
134
|
+
modelId: self.modelId,
|
|
135
|
+
});
|
|
136
|
+
if (auth.status === "pending_approval") {
|
|
137
|
+
const err = new Error(`Aira: tool '${toolName}' is pending human approval (action_id=${auth.action_id}). Tool execution blocked.`);
|
|
138
|
+
err.code = "PENDING_APPROVAL";
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
actionId = auth.action_id;
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
const err = e;
|
|
145
|
+
if (err.code === "POLICY_DENIED" || err.code === "PENDING_APPROVAL")
|
|
146
|
+
throw e;
|
|
147
|
+
if (self.strict)
|
|
148
|
+
throw e;
|
|
149
|
+
console.warn("Aira authorize failed (fail-open):", err);
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
const result = await toolFn.apply(this, args);
|
|
153
|
+
if (actionId) {
|
|
154
|
+
await self.client.notarize({
|
|
155
|
+
actionId,
|
|
156
|
+
outcome: "completed",
|
|
157
|
+
outcomeDetails: `Tool '${toolName}' completed. Result length: ${String(result).length} chars`.slice(0, MAX_DETAILS),
|
|
158
|
+
}).catch((e) => console.warn("Aira notarize failed (non-blocking):", e));
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
if (actionId) {
|
|
164
|
+
await self.client.notarize({
|
|
165
|
+
actionId,
|
|
166
|
+
outcome: "failed",
|
|
167
|
+
outcomeDetails: `Tool '${toolName}' failed: ${err?.message ?? String(err)}`.slice(0, MAX_DETAILS),
|
|
168
|
+
}).catch((e) => console.warn("Aira notarize failed (non-blocking):", e));
|
|
169
|
+
}
|
|
170
|
+
throw err;
|
|
171
|
+
}
|
|
98
172
|
};
|
|
99
173
|
return wrapped;
|
|
100
174
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,4 +3,4 @@ export type { AiraOptions } from "./client";
|
|
|
3
3
|
export { AiraSession } from "./session";
|
|
4
4
|
export { OfflineQueue } from "./offline";
|
|
5
5
|
export type { QueuedRequest } from "./offline";
|
|
6
|
-
export { AiraError, type ActionReceipt, type ActionDetail, type AgentDetail, type AgentVersion, type EvidencePackage, type ComplianceSnapshot, type EscrowAccount, type EscrowTransaction, type VerifyResult, type PaginatedList, } from "./types";
|
|
6
|
+
export { AiraError, FRAMEWORK_ANNEX_IV, FRAMEWORK_ART12, FRAMEWORK_ART9, FRAMEWORK_ART6, type Authorization, type ActionReceipt, type ActionDetail, type AgentDetail, type AgentVersion, type CosignResult, type EvidencePackage, type ComplianceSnapshot, type EscrowAccount, type EscrowTransaction, type VerifyResult, type PaginatedList, type ComplianceReport, type ComplianceReportListResponse, type ComplianceReportVerification, type ActionExplanation, type ExplanationEnvelope, type ExplanationVerification, type OutputPolicy, type OutputPolicyUpdate, type OutputScanFlags, type OutputScanHit, } from "./types";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AiraError = exports.OfflineQueue = exports.AiraSession = exports.Aira = void 0;
|
|
3
|
+
exports.FRAMEWORK_ART6 = exports.FRAMEWORK_ART9 = exports.FRAMEWORK_ART12 = exports.FRAMEWORK_ANNEX_IV = exports.AiraError = exports.OfflineQueue = exports.AiraSession = exports.Aira = void 0;
|
|
4
4
|
var client_1 = require("./client");
|
|
5
5
|
Object.defineProperty(exports, "Aira", { enumerable: true, get: function () { return client_1.Aira; } });
|
|
6
6
|
var session_1 = require("./session");
|
|
@@ -9,3 +9,7 @@ var offline_1 = require("./offline");
|
|
|
9
9
|
Object.defineProperty(exports, "OfflineQueue", { enumerable: true, get: function () { return offline_1.OfflineQueue; } });
|
|
10
10
|
var types_1 = require("./types");
|
|
11
11
|
Object.defineProperty(exports, "AiraError", { enumerable: true, get: function () { return types_1.AiraError; } });
|
|
12
|
+
Object.defineProperty(exports, "FRAMEWORK_ANNEX_IV", { enumerable: true, get: function () { return types_1.FRAMEWORK_ANNEX_IV; } });
|
|
13
|
+
Object.defineProperty(exports, "FRAMEWORK_ART12", { enumerable: true, get: function () { return types_1.FRAMEWORK_ART12; } });
|
|
14
|
+
Object.defineProperty(exports, "FRAMEWORK_ART9", { enumerable: true, get: function () { return types_1.FRAMEWORK_ART9; } });
|
|
15
|
+
Object.defineProperty(exports, "FRAMEWORK_ART6", { enumerable: true, get: function () { return types_1.FRAMEWORK_ART6; } });
|
package/dist/session.d.ts
CHANGED
|
@@ -1,22 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AiraSession — scoped session with pre-filled defaults
|
|
2
|
+
* AiraSession — scoped session with pre-filled defaults for `authorize()`.
|
|
3
|
+
*
|
|
4
|
+
* Under the two-step flow, only `authorize()` takes agent/model metadata;
|
|
5
|
+
* `notarize()` operates on an existing action_id. This session therefore
|
|
6
|
+
* merges defaults on `authorize()` only and provides a thin passthrough
|
|
7
|
+
* for `notarize()` so callers can use a single object end-to-end.
|
|
3
8
|
*/
|
|
4
9
|
import type { Aira } from "./client";
|
|
5
|
-
import type { ActionReceipt } from "./types";
|
|
10
|
+
import type { Authorization, ActionReceipt } from "./types";
|
|
6
11
|
export declare class AiraSession {
|
|
7
12
|
private client;
|
|
8
13
|
private defaults;
|
|
9
14
|
constructor(client: Aira, agentId: string, defaults?: Record<string, unknown>);
|
|
10
|
-
|
|
15
|
+
authorize(params: {
|
|
11
16
|
actionType: string;
|
|
12
17
|
details: string;
|
|
18
|
+
instructionHash?: string;
|
|
13
19
|
modelId?: string;
|
|
14
20
|
modelVersion?: string;
|
|
15
|
-
instructionHash?: string;
|
|
16
21
|
parentActionId?: string;
|
|
22
|
+
endpointUrl?: string;
|
|
17
23
|
storeDetails?: boolean;
|
|
18
24
|
idempotencyKey?: string;
|
|
19
25
|
requireApproval?: boolean;
|
|
20
26
|
approvers?: string[];
|
|
27
|
+
}): Promise<Authorization>;
|
|
28
|
+
notarize(params: {
|
|
29
|
+
actionId: string;
|
|
30
|
+
outcome?: "completed" | "failed";
|
|
31
|
+
outcomeDetails?: string;
|
|
21
32
|
}): Promise<ActionReceipt>;
|
|
22
33
|
}
|
package/dist/session.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* AiraSession — scoped session with pre-filled defaults
|
|
3
|
+
* AiraSession — scoped session with pre-filled defaults for `authorize()`.
|
|
4
|
+
*
|
|
5
|
+
* Under the two-step flow, only `authorize()` takes agent/model metadata;
|
|
6
|
+
* `notarize()` operates on an existing action_id. This session therefore
|
|
7
|
+
* merges defaults on `authorize()` only and provides a thin passthrough
|
|
8
|
+
* for `notarize()` so callers can use a single object end-to-end.
|
|
4
9
|
*/
|
|
5
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
11
|
exports.AiraSession = void 0;
|
|
@@ -11,14 +16,17 @@ class AiraSession {
|
|
|
11
16
|
this.client = client;
|
|
12
17
|
this.defaults = { agentId, ...(defaults ?? {}) };
|
|
13
18
|
}
|
|
14
|
-
async
|
|
19
|
+
async authorize(params) {
|
|
15
20
|
const merged = {
|
|
16
21
|
agentId: this.defaults.agentId,
|
|
17
22
|
...(this.defaults.modelId ? { modelId: this.defaults.modelId } : {}),
|
|
18
23
|
...(this.defaults.agentVersion ? { agentVersion: this.defaults.agentVersion } : {}),
|
|
19
24
|
...params,
|
|
20
25
|
};
|
|
21
|
-
return this.client.
|
|
26
|
+
return this.client.authorize(merged);
|
|
27
|
+
}
|
|
28
|
+
async notarize(params) {
|
|
29
|
+
return this.client.notarize(params);
|
|
22
30
|
}
|
|
23
31
|
}
|
|
24
32
|
exports.AiraSession = AiraSession;
|