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.
@@ -1,37 +1,69 @@
1
1
  /**
2
- * OpenAI Node SDK integration — guardrail that notarizes tool calls.
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
- * Usage:
7
- * import { AiraGuardrail } from "aira-sdk/extras/openai-agents";
8
- * const guardrail = new AiraGuardrail(aira, "my-agent");
9
- * guardrail.onToolCall("search", { query: "test" });
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
- constructor(client: Aira, agentId: string, options?: {
20
- modelId?: string;
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
- * Wraps a tool function to auto-notarize calls and results.
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 Node SDK integration — guardrail that notarizes tool calls.
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
- * Usage:
8
- * import { AiraGuardrail } from "aira-sdk/extras/openai-agents";
9
- * const guardrail = new AiraGuardrail(aira, "my-agent");
10
- * guardrail.onToolCall("search", { query: "test" });
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
- notarize(actionType, details) {
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 params = {
40
- actionType,
41
- details: details.slice(0, MAX_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
- console.warn("Aira notarize failed (non-blocking):", e);
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
- /** Call after a tool execution to notarize it. */
55
- onToolCall(toolName, args) {
56
- const argKeys = Object.keys(args ?? {});
57
- this.notarize("tool_call", `Tool '${toolName}' called. Arg keys: [${argKeys.join(", ")}]`);
58
- }
59
- /** Call after a tool returns to notarize the result. */
60
- onToolResult(toolName, result) {
61
- this.notarize("tool_completed", `Tool '${toolName}' completed. Result length: ${String(result).length} chars`);
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
- * Wraps a tool function to auto-notarize calls and results.
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.onToolCall(name, kwargs);
75
- const result = await toolFn.apply(this, args);
76
- self.onToolResult(name, result);
77
- return result;
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 — middleware that notarizes tool calls and completions.
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
- * Usage:
7
- * import { AiraVercelMiddleware } from "aira-sdk/extras/vercel-ai";
8
- * const middleware = new AiraVercelMiddleware(aira, "my-agent");
9
- * // Use as wrap around tool calls or stream callbacks
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
- constructor(client: Aira, agentId: string, options?: {
20
- modelId?: string;
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
- private notarize;
29
- /** Call after a tool execution to notarize it. */
30
- onToolCall(toolName: string, argKeys?: string[]): void;
31
- /** Call after a tool returns to notarize the result. */
32
- onToolResult(toolName: string, resultLength: number): void;
33
- /** Call when a text generation step completes. */
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
- /** Call when the full generation completes. */
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
- * Wraps a tool's execute function to auto-notarize calls and results.
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
  }
@@ -1,13 +1,33 @@
1
1
  "use strict";
2
2
  /**
3
- * Vercel AI SDK integration — middleware that notarizes tool calls and completions.
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
- * Usage:
8
- * import { AiraVercelMiddleware } from "aira-sdk/extras/vercel-ai";
9
- * const middleware = new AiraVercelMiddleware(aira, "my-agent");
10
- * // Use as wrap around tool calls or stream callbacks
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
- notarize(actionType, details) {
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 params = {
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 notarize failed (non-blocking):", e);
77
+ console.warn("Aira audit failed (non-blocking):", e);
52
78
  }
53
79
  }
54
- /** Call after a tool execution to notarize it. */
55
- onToolCall(toolName, argKeys = []) {
56
- this.notarize("tool_call", `Tool '${toolName}' called. Arg keys: [${argKeys.join(", ")}]`);
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.notarize("step_completed", `Step '${stepType}' completed.${tokenCount != null ? ` Tokens: ${tokenCount}` : ""}`);
85
+ void this.auditFinish("step_completed", `Step '${stepType}' completed.${tokenCount != null ? ` Tokens: ${tokenCount}` : ""}`);
65
86
  }
66
- /** Call when the full generation completes. */
87
+ /**
88
+ * AUDIT-ONLY: called after full generation finishes.
89
+ */
67
90
  onFinish(finishReason, totalTokens) {
68
- this.notarize("generation_completed", `Generation completed. Reason: ${finishReason}.${totalTokens != null ? ` Total tokens: ${totalTokens}` : ""}`);
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
- * Wraps a tool's execute function to auto-notarize calls and results.
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
- self.onToolCall(toolName, args.length > 0 && typeof args[0] === "object" && args[0] ? Object.keys(args[0]) : []);
95
- const result = await toolFn.apply(this, args);
96
- self.onToolResult(toolName, String(result).length);
97
- return result;
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
- notarize(params: {
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 notarize(params) {
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.notarize(merged);
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;