@atbash/sdk 0.3.3 → 0.3.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @atbash/sdk
2
2
 
3
- TypeScript SDK for the Atbash judge and risk-engine APIs. Evaluate agent actions against operator-defined policy before execution — programmatically, from any Node.js backend.
3
+ TypeScript SDK for Atbash — the safety layer that evaluates AI agent actions against operator-defined policies before execution.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,16 +8,16 @@ TypeScript SDK for the Atbash judge and risk-engine APIs. Evaluate agent actions
8
8
  npm install @atbash/sdk
9
9
  ```
10
10
 
11
- Requires Node.js 18 or higher. Server-side only — private keys are used for local signing and must never be exposed to browsers.
11
+ Requires Node.js 18+. Server-side only — private keys are used for local signing and must never be exposed to browsers.
12
12
 
13
13
  ## Quickstart
14
14
 
15
15
  ```ts
16
16
  import { loadAgent, judgeAction } from "@atbash/sdk";
17
17
 
18
- // 1. Load your agent identity paste the private key from the Atbash
19
- // dashboard (https://atbash.ai/risk-engine/agents). loadAgent()
20
- // validates the key and derives the matching public key for you.
18
+ // 1. Load your agent identity using the private key you saved during
19
+ // agent creation. loadAgent() validates the key and derives the
20
+ // matching public key for you.
21
21
  const agent = loadAgent(process.env.ATBASH_AGENT_PRIVKEY!);
22
22
 
23
23
  // 2. Submit an action for judgment, before executing it.
@@ -57,7 +57,11 @@ If you need finer control, you can call `logToolCall()` and the judge API separa
57
57
 
58
58
  ### Don't have an agent yet?
59
59
 
60
- Generate one programmatically for local development:
60
+ There are two ways to create an agent:
61
+
62
+ 1. **Dashboard (recommended)** — create an agent at [atbash.ai/risk-engine/agents](https://atbash.ai/risk-engine/agents). The dashboard generates the keypair, assigns the agent to your org, and lets you attach a policy pack — all in one step.
63
+
64
+ 2. **Programmatic** — generate a new keypair locally or bring an existing secp256k1 private key from another platform, then onboard it via the dashboard:
61
65
 
62
66
  ```ts
63
67
  import { generateKeyPair, loadAgent } from "@atbash/sdk";
@@ -67,7 +71,7 @@ console.log("Save this private key somewhere safe:", privKey);
67
71
  const agent = loadAgent(privKey);
68
72
  ```
69
73
 
70
- For production, always create agents in the dashboard so operators can attach policy packs and manage tier / jail state.
74
+ > **Note:** After generating a key programmatically, you must still onboard the agent at [atbash.ai/risk-engine/agents](https://atbash.ai/risk-engine/agents) assign it to an org and attach a policy pack before `judgeAction` will work.
71
75
 
72
76
  ### Secret storage
73
77
 
@@ -88,6 +92,8 @@ Every `judgeAction` call returns one of three verdicts:
88
92
  | `HOLD` | Requires operator review | Pause — poll `getJudgmentStatus` until resolved |
89
93
  | `BLOCK` | Violates a red line | Abort — agent is jailed in Enforcement tier |
90
94
 
95
+ > **NB:** If your org is on the **Audit** tier, the judge returns `"No verdict"` — actions are logged on-chain for the audit trail but not evaluated by an AI provider. Upgrade to **Audit+** or **Enforcement** at [atbash.ai/risk-engine/settings](https://atbash.ai/risk-engine/settings) for active verdicts.
96
+
91
97
  ## API
92
98
 
93
99
  ### Judge
@@ -122,8 +128,8 @@ interface JudgeOptions {
122
128
  }
123
129
 
124
130
  interface ChainOpts {
125
- nodeUrls?: string[]; // Chromia node URLs (defaults to testnet)
126
- blockchainRid?: string; // Blockchain RID (defaults to testnet)
131
+ nodeUrls?: string[]; // Chromia node URLs (uses the default nodeurls)
132
+ blockchainRid?: string; // Blockchain RID (uses the default chromia rid)
127
133
  }
128
134
 
129
135
  interface JudgeResult {
@@ -147,10 +153,10 @@ logToolCall(
147
153
  auth: AgentAuth,
148
154
  chainOpts?: ChainOpts,
149
155
  extra?: { toolName?: string; toolArgsJson?: string },
150
- ): Promise<string>
156
+ ): Promise<LogToolCallResult>
151
157
  ```
152
158
 
153
- Sign and broadcast `log_tool_call` to the Chromia chain. Returns the `tool_call_id`. Use this if you need to separate the on-chain logging step from the verdict request.
159
+ Sign `log_tool_call` on the Chromia chain. Returns `{ success, toolCallId, error? }`. Use this if you need to separate the on-chain logging step from the verdict request.
154
160
 
155
161
  ### Poll judgment status
156
162
 
@@ -184,39 +190,32 @@ toPubkeyHex(val: unknown): string
184
190
 
185
191
  `loadAgent(privkey)` is the canonical loader — pass in the private key from the dashboard, get back `{ pubkey, privkey }` ready for `judgeAction`. It accepts `0x`-prefixed, padded, or mixed-case input and throws on malformed keys. Use `generateKeyPair()` only for local development; for production, create agents in the dashboard so operators can attach policies.
186
192
 
187
- ### Query APIs
188
-
189
- Read from the Atbash audit trail and operator queues:
190
-
191
- ```ts
192
- // Tool call history
193
- getToolCalls(maxCount: number, opts?: ClientOpts): Promise<ToolCallRecord[]>
194
- getOrgToolCalls(orgName: string, maxCount: number, opts?: ClientOpts): Promise<ToolCallRecord[]>
195
- getAgentToolCalls(agentPubkey: string, maxCount: number, opts?: ClientOpts): Promise<ToolCallRecord[]>
196
- getToolCallCount(opts?: ClientOpts): Promise<number>
197
- getToolCallFull(toolCallId: string, opts?: ClientOpts): Promise<ToolCallFull | null>
198
-
199
- // Org and agent info
200
- getOrgTierInfo(orgName: string, opts?: ClientOpts): Promise<TierInfo | null>
201
- getAgentDetail(agentPubkey: string, opts?: ClientOpts): Promise<Record<string, unknown>>
202
- getAgentPolicy(agentPubkey: string, opts?: ClientOpts): Promise<AgentPolicy>
203
-
204
- // Operator review queue
205
- getPendingHeldActions(orgName: string, maxCount: number, opts?: ClientOpts): Promise<HeldAction[]>
206
- getHeldActionReviews(orgName: string, maxCount: number, opts?: ClientOpts): Promise<HeldActionReview[]>
207
-
208
- // Chain-wide stats
209
- getSafetyStats(opts?: ClientOpts): Promise<Record<string, unknown>>
210
- ```
211
-
212
- All query functions accept an optional `ClientOpts` to override the endpoint:
213
-
214
- ```ts
215
- interface ClientOpts {
216
- endpoint?: string; // Default: https://atbash.ai
217
- timeout?: number;
218
- }
219
- ```
193
+ ### Operations
194
+
195
+ Functions that sign transactions and write to the Chromia blockchain.
196
+
197
+ | Function | Use case |
198
+ |----------|----------|
199
+ | `judgeAction(action, context, auth, opts?)` | Sign `log_tool_call` on-chain + request a verdict from the judge API |
200
+ | `logToolCall(action, context, auth, ...)` | Sign and broadcast `log_tool_call` to chain without requesting a verdict |
201
+
202
+ ### Queries
203
+
204
+ | Function | Use case |
205
+ |----------|----------|
206
+ | `checkAgentExists(pubkey, opts?)` | Check if an agent is onboarded before signing |
207
+ | `getJudgmentStatus(judgmentId, opts?)` | Poll whether a held action has been approved or rejected |
208
+ | `getToolCalls(maxCount)` | List recent tool calls across all agents |
209
+ | `getOrgToolCalls(orgName, maxCount)` | List tool calls for a specific org |
210
+ | `getAgentToolCalls(pubkey, maxCount)` | List tool calls for a specific agent |
211
+ | `getToolCallCount()` | Get total number of tool calls on-chain |
212
+ | `getToolCallFull(toolCallId)` | Get full details of a single tool call (verdict, context, timing) |
213
+ | `getOrgTierInfo(orgName)` | Check an org's tier and whether verdicts are enabled |
214
+ | `getAgentDetail(pubkey)` | Get agent metadata (org, status, creation date) |
215
+ | `getAgentPolicy(pubkey)` | Check agent's policy pack and jail status |
216
+ | `getPendingHeldActions(orgName, maxCount)` | List actions waiting for operator approval |
217
+ | `getHeldActionReviews(orgName, maxCount)` | List completed operator reviews |
218
+ | `getSafetyStats()` | Get chain-wide safety statistics (total judgments, verdicts, etc.) |
220
219
 
221
220
  ## Configuration
222
221
 
@@ -235,14 +234,10 @@ const result = await judgeAction(action, context, auth, {
235
234
  model: "gpt-4o",
236
235
  });
237
236
 
238
- // Custom Chromia chain (e.g. production RID)
239
- const result = await judgeAction(action, context, auth, {
240
- chainOpts: {
241
- blockchainRid: "YOUR_PRODUCTION_BLOCKCHAIN_RID",
242
- },
243
- });
244
237
  ```
245
238
 
239
+ > **Advanced:** The SDK connects to the default Atbash Chromia chain. To use a different chain, pass `chainOpts` with custom `nodeUrls` and `blockchainRid` in `JudgeOptions`.
240
+
246
241
  ## Integration patterns
247
242
 
248
243
  ### Pre-execution gate
package/dist/index.cjs CHANGED
@@ -110,14 +110,29 @@ function generateToolCallId() {
110
110
  const rand = (0, import_crypto.randomBytes)(4).toString("hex");
111
111
  return `tc-${ts}-${rand}`;
112
112
  }
113
+ async function buildSignedTx(opName, args, auth, chainOpts) {
114
+ const nodeUrls = chainOpts?.nodeUrls ?? DEFAULT_CHROMIA_NODE_URLS;
115
+ const blockchainRid = chainOpts?.blockchainRid ?? DEFAULT_BLOCKCHAIN_RID;
116
+ const client = await createClient({ nodeUrlPool: nodeUrls, blockchainRid });
117
+ const privKeyBuf = Buffer.from(auth.privkey, "hex");
118
+ const keyPair = encryption.makeKeyPair(privKeyBuf);
119
+ const sigProvider = newSignatureProvider({
120
+ privKey: keyPair.privKey,
121
+ pubKey: keyPair.pubKey
122
+ });
123
+ const signed = await client.signTransaction(
124
+ {
125
+ operations: [{ name: opName, args }],
126
+ signers: [keyPair.pubKey]
127
+ },
128
+ sigProvider
129
+ );
130
+ return Buffer.from(signed).toString("hex");
131
+ }
113
132
  async function checkAgentExists(pubkey, opts) {
114
- try {
115
- const url = `${baseUrl(opts)}/api/ai/exists?pubkey=${encodeURIComponent(pubkey)}`;
116
- const data = await getJson(url, opts);
117
- return Boolean(data.registered);
118
- } catch {
119
- return false;
120
- }
133
+ const url = `${baseUrl(opts)}/api/ai/exists?pubkey=${encodeURIComponent(pubkey)}`;
134
+ const data = await getJson(url, opts);
135
+ return Boolean(data.registered);
121
136
  }
122
137
  async function logToolCall(action, context, auth, chainOpts, extra, clientOpts) {
123
138
  const exists = await checkAgentExists(auth.pubkey, clientOpts);
@@ -129,38 +144,25 @@ async function logToolCall(action, context, auth, chainOpts, extra, clientOpts)
129
144
  };
130
145
  }
131
146
  try {
132
- const nodeUrls = chainOpts?.nodeUrls ?? DEFAULT_CHROMIA_NODE_URLS;
133
- const blockchainRid = chainOpts?.blockchainRid ?? DEFAULT_BLOCKCHAIN_RID;
134
- const client = await createClient({
135
- nodeUrlPool: nodeUrls,
136
- blockchainRid
137
- });
138
- const privKeyBuf = Buffer.from(auth.privkey, "hex");
139
- const keyPair = encryption.makeKeyPair(privKeyBuf);
140
- const sigProvider = newSignatureProvider({
141
- privKey: keyPair.privKey,
142
- pubKey: keyPair.pubKey
143
- });
144
147
  const toolCallId = generateToolCallId();
145
- await client.signAndSendUniqueTransaction(
146
- {
147
- name: "log_tool_call",
148
- args: [
149
- toolCallId,
150
- action,
151
- context || "",
152
- extra?.toolName || "",
153
- extra?.toolArgsJson || ""
154
- ]
155
- },
156
- sigProvider
148
+ const signedHex = await buildSignedTx(
149
+ "log_tool_call",
150
+ [
151
+ toolCallId,
152
+ action,
153
+ context || "",
154
+ extra?.toolName || "",
155
+ extra?.toolArgsJson || ""
156
+ ],
157
+ auth,
158
+ chainOpts
157
159
  );
158
- return { success: true, toolCallId };
160
+ return { success: true, toolCallId, signedHex };
159
161
  } catch (err) {
160
162
  return {
161
163
  success: false,
162
164
  toolCallId: null,
163
- error: err instanceof Error ? err.message : "Failed to log tool call on-chain"
165
+ error: err instanceof Error ? err.message : "Failed to sign log_tool_call"
164
166
  };
165
167
  }
166
168
  }
@@ -215,6 +217,7 @@ async function postJson(url, body, opts) {
215
217
  async function getJson(url, opts) {
216
218
  const resp = await fetch(url, {
217
219
  method: "GET",
220
+ headers: { Accept: "application/json" },
218
221
  signal: opts?.timeout ? AbortSignal.timeout(opts.timeout) : void 0
219
222
  });
220
223
  if (!resp.ok) {
@@ -223,7 +226,10 @@ async function getJson(url, opts) {
223
226
  }
224
227
  return resp.json();
225
228
  }
226
- async function judgeAction(action, context, auth, opts) {
229
+ async function judgeAction(action, context = "", auth, opts) {
230
+ if (!action || !action.trim()) {
231
+ throw new Error("action is required and cannot be empty.");
232
+ }
227
233
  const logResult = await logToolCall(
228
234
  action,
229
235
  context,
@@ -232,25 +238,32 @@ async function judgeAction(action, context, auth, opts) {
232
238
  { toolName: opts?.toolName, toolArgsJson: opts?.toolArgsJson },
233
239
  opts
234
240
  );
235
- if (!logResult.success || !logResult.toolCallId) {
236
- throw new Error(logResult.error || "Failed to log tool call on-chain");
241
+ if (!logResult.success || !logResult.toolCallId || !logResult.signedHex) {
242
+ throw new Error(logResult.error || "Failed to sign log_tool_call");
243
+ }
244
+ let signedJudgeActionHex;
245
+ if (!opts?.provider) {
246
+ const judgmentId = generateToolCallId();
247
+ signedJudgeActionHex = await buildSignedTx(
248
+ "judge_action",
249
+ [judgmentId, action, context || "", ""],
250
+ auth,
251
+ opts?.chainOpts
252
+ );
237
253
  }
238
254
  const url = `${baseUrl(opts)}/api/v1/judge`;
239
- const data = await postJson(
240
- url,
241
- {
242
- tool_call_id: logResult.toolCallId,
243
- agent_pubkey: auth.pubkey,
244
- action,
245
- ...context && { context },
246
- ...opts?.provider && { provider: opts.provider },
247
- ...opts?.toolName && { tool_name: opts.toolName },
248
- ...opts?.apiKey && { api_key: opts.apiKey },
249
- ...opts?.providerEndpoint && { endpoint_url: opts.providerEndpoint },
250
- ...opts?.model && { model: opts.model }
251
- },
252
- opts
253
- );
255
+ const body = {
256
+ tool_call_id: logResult.toolCallId,
257
+ agent_pubkey: auth.pubkey,
258
+ action,
259
+ signed_log_tool_call: logResult.signedHex,
260
+ ...signedJudgeActionHex && { signed_judge_action: signedJudgeActionHex },
261
+ ...context && { context },
262
+ ...opts?.provider && { provider: opts.provider },
263
+ ...opts?.toolName && { tool_name: opts.toolName },
264
+ ...opts?.model && { model: opts.model }
265
+ };
266
+ const data = await postJson(url, body, opts);
254
267
  return {
255
268
  verdict: normalizeVerdict(data.verdict),
256
269
  action_type: String(data.action_type || ""),
package/dist/index.d.cts CHANGED
@@ -1,6 +1,3 @@
1
- declare const DEFAULT_ENDPOINT = "https://atbash.ai";
2
- declare const DEFAULT_CHROMIA_NODE_URLS: string[];
3
- declare const DEFAULT_BLOCKCHAIN_RID = "25B41DF620C489349C54944496FF5C6E58CFCEFED0C51658780B67299D40E8ED";
4
1
  type Verdict = "ALLOW" | "HOLD" | "BLOCK" | "No verdict";
5
2
  type Provider = "atbash" | "openai" | "google" | "microsoft" | "custom" | (string & {});
6
3
  type Tier = "audit" | "audit_plus" | "enforcement" | (string & {});
@@ -9,18 +6,10 @@ type PubkeyValue = string | Buffer | {
9
6
  data: number[];
10
7
  };
11
8
  type JudgmentStatusState = "pending" | "answered" | "error";
12
- declare function isValidPrivateKey(hex: string): boolean;
13
- declare function derivePublicKey(privKeyHex: string): string;
14
- declare function generateKeyPair(): {
15
- privKey: string;
16
- pubKey: string;
17
- };
18
9
  interface AgentAuth {
19
10
  pubkey: string;
20
11
  privkey: string;
21
12
  }
22
- declare function loadAgent(privkey: string): AgentAuth;
23
- declare function toPubkeyHex(val: unknown): string;
24
13
  interface ClientOpts {
25
14
  endpoint?: string;
26
15
  timeout?: number;
@@ -32,23 +21,9 @@ interface ChainOpts {
32
21
  interface LogToolCallResult {
33
22
  success: boolean;
34
23
  toolCallId: string | null;
24
+ signedHex?: string;
35
25
  error?: string;
36
26
  }
37
- /**
38
- * Check if an agent is onboarded before signing anything.
39
- * Calls GET /api/ai/exists?pubkey=<66-hex>
40
- */
41
- declare function checkAgentExists(pubkey: string, opts?: ClientOpts): Promise<boolean>;
42
- /**
43
- * Sign and broadcast `log_tool_call` to the Chromia chain.
44
- *
45
- * Checks that the agent is onboarded before signing. The agent's private
46
- * key is used to sign the transaction locally — never sent over the network.
47
- */
48
- declare function logToolCall(action: string, context: string, auth: AgentAuth, chainOpts?: ChainOpts, extra?: {
49
- toolName?: string;
50
- toolArgsJson?: string;
51
- }, clientOpts?: ClientOpts): Promise<LogToolCallResult>;
52
27
  interface JudgeResult {
53
28
  verdict: Verdict;
54
29
  action_type: ActionType;
@@ -59,6 +34,22 @@ interface JudgeResult {
59
34
  tool_call_id: string;
60
35
  on_chain: boolean;
61
36
  }
37
+ interface JudgeOptions extends ClientOpts {
38
+ provider?: Provider;
39
+ model?: string;
40
+ toolName?: string;
41
+ toolArgsJson?: string;
42
+ chainOpts?: ChainOpts;
43
+ }
44
+ interface JudgmentStatus {
45
+ status: JudgmentStatusState;
46
+ verdict: Verdict;
47
+ reason: string;
48
+ judgmentId: string;
49
+ onChain?: boolean;
50
+ cached?: boolean;
51
+ responseTimeMs?: number;
52
+ }
62
53
  interface TierInfo {
63
54
  org_name: string;
64
55
  tier: Tier;
@@ -109,31 +100,41 @@ interface HeldActionReview {
109
100
  reviewed_at: number;
110
101
  created_at: number;
111
102
  }
112
- interface JudgmentStatus {
113
- status: JudgmentStatusState;
114
- verdict: Verdict;
115
- reason: string;
116
- judgmentId: string;
117
- onChain?: boolean;
118
- cached?: boolean;
119
- responseTimeMs?: number;
120
- }
121
- interface JudgeOptions extends ClientOpts {
122
- provider?: Provider;
123
- apiKey?: string;
124
- providerEndpoint?: string;
125
- model?: string;
126
- toolName?: string;
127
- toolArgsJson?: string;
128
- chainOpts?: ChainOpts;
129
- }
130
103
  interface AgentPolicy {
131
104
  policy: string;
132
105
  is_jailed: boolean;
133
106
  is_custom: boolean;
134
107
  default_policy: string;
135
108
  }
136
- declare function judgeAction(action: string, context: string, auth: AgentAuth, opts?: JudgeOptions): Promise<JudgeResult>;
109
+
110
+ declare const DEFAULT_ENDPOINT = "https://atbash.ai";
111
+ declare const DEFAULT_CHROMIA_NODE_URLS: string[];
112
+ declare const DEFAULT_BLOCKCHAIN_RID = "25B41DF620C489349C54944496FF5C6E58CFCEFED0C51658780B67299D40E8ED";
113
+ declare function isValidPrivateKey(hex: string): boolean;
114
+ declare function derivePublicKey(privKeyHex: string): string;
115
+ declare function generateKeyPair(): {
116
+ privKey: string;
117
+ pubKey: string;
118
+ };
119
+ declare function loadAgent(privkey: string): AgentAuth;
120
+ declare function toPubkeyHex(val: unknown): string;
121
+ /**
122
+ * Check if an agent is onboarded before signing anything.
123
+ * Calls GET /api/ai/exists?pubkey=<66-hex>
124
+ */
125
+ declare function checkAgentExists(pubkey: string, opts?: ClientOpts): Promise<boolean>;
126
+ /**
127
+ * Sign `log_tool_call` locally and return the signed transaction hex.
128
+ *
129
+ * Checks that the agent is onboarded before signing. The private key
130
+ * is used locally — never sent over the network. The server will
131
+ * broadcast the signed transaction to the chain.
132
+ */
133
+ declare function logToolCall(action: string, context: string, auth: AgentAuth, chainOpts?: ChainOpts, extra?: {
134
+ toolName?: string;
135
+ toolArgsJson?: string;
136
+ }, clientOpts?: ClientOpts): Promise<LogToolCallResult>;
137
+ declare function judgeAction(action: string, context: string | undefined, auth: AgentAuth, opts?: JudgeOptions): Promise<JudgeResult>;
137
138
  declare function getJudgmentStatus(judgmentId: string, opts?: ClientOpts): Promise<JudgmentStatus>;
138
139
  declare function getToolCalls(maxCount: number, opts?: ClientOpts): Promise<ToolCallRecord[]>;
139
140
  declare function getOrgToolCalls(orgName: string, maxCount: number, opts?: ClientOpts): Promise<ToolCallRecord[]>;
@@ -147,4 +148,4 @@ declare function getAgentDetail(agentPubkey: string, opts?: ClientOpts): Promise
147
148
  declare function getAgentPolicy(agentPubkey: string, opts?: ClientOpts): Promise<AgentPolicy>;
148
149
  declare function getSafetyStats(opts?: ClientOpts): Promise<Record<string, unknown>>;
149
150
 
150
- export { type AgentAuth, type ChainOpts, type ClientOpts, DEFAULT_BLOCKCHAIN_RID, DEFAULT_CHROMIA_NODE_URLS, DEFAULT_ENDPOINT, type HeldAction, type HeldActionReview, type JudgeOptions, type JudgeResult, type JudgmentStatus, type LogToolCallResult, type TierInfo, type ToolCallFull, type ToolCallRecord, type Verdict, checkAgentExists, derivePublicKey, generateKeyPair, getAgentDetail, getAgentPolicy, getAgentToolCalls, getHeldActionReviews, getJudgmentStatus, getOrgTierInfo, getOrgToolCalls, getPendingHeldActions, getSafetyStats, getToolCallCount, getToolCallFull, getToolCalls, isValidPrivateKey, judgeAction, loadAgent, logToolCall, toPubkeyHex };
151
+ export { type ActionType, type AgentAuth, type AgentPolicy, type ChainOpts, type ClientOpts, DEFAULT_BLOCKCHAIN_RID, DEFAULT_CHROMIA_NODE_URLS, DEFAULT_ENDPOINT, type HeldAction, type HeldActionReview, type JudgeOptions, type JudgeResult, type JudgmentStatus, type JudgmentStatusState, type LogToolCallResult, type Provider, type PubkeyValue, type Tier, type TierInfo, type ToolCallFull, type ToolCallRecord, type Verdict, checkAgentExists, derivePublicKey, generateKeyPair, getAgentDetail, getAgentPolicy, getAgentToolCalls, getHeldActionReviews, getJudgmentStatus, getOrgTierInfo, getOrgToolCalls, getPendingHeldActions, getSafetyStats, getToolCallCount, getToolCallFull, getToolCalls, isValidPrivateKey, judgeAction, loadAgent, logToolCall, toPubkeyHex };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,3 @@
1
- declare const DEFAULT_ENDPOINT = "https://atbash.ai";
2
- declare const DEFAULT_CHROMIA_NODE_URLS: string[];
3
- declare const DEFAULT_BLOCKCHAIN_RID = "25B41DF620C489349C54944496FF5C6E58CFCEFED0C51658780B67299D40E8ED";
4
1
  type Verdict = "ALLOW" | "HOLD" | "BLOCK" | "No verdict";
5
2
  type Provider = "atbash" | "openai" | "google" | "microsoft" | "custom" | (string & {});
6
3
  type Tier = "audit" | "audit_plus" | "enforcement" | (string & {});
@@ -9,18 +6,10 @@ type PubkeyValue = string | Buffer | {
9
6
  data: number[];
10
7
  };
11
8
  type JudgmentStatusState = "pending" | "answered" | "error";
12
- declare function isValidPrivateKey(hex: string): boolean;
13
- declare function derivePublicKey(privKeyHex: string): string;
14
- declare function generateKeyPair(): {
15
- privKey: string;
16
- pubKey: string;
17
- };
18
9
  interface AgentAuth {
19
10
  pubkey: string;
20
11
  privkey: string;
21
12
  }
22
- declare function loadAgent(privkey: string): AgentAuth;
23
- declare function toPubkeyHex(val: unknown): string;
24
13
  interface ClientOpts {
25
14
  endpoint?: string;
26
15
  timeout?: number;
@@ -32,23 +21,9 @@ interface ChainOpts {
32
21
  interface LogToolCallResult {
33
22
  success: boolean;
34
23
  toolCallId: string | null;
24
+ signedHex?: string;
35
25
  error?: string;
36
26
  }
37
- /**
38
- * Check if an agent is onboarded before signing anything.
39
- * Calls GET /api/ai/exists?pubkey=<66-hex>
40
- */
41
- declare function checkAgentExists(pubkey: string, opts?: ClientOpts): Promise<boolean>;
42
- /**
43
- * Sign and broadcast `log_tool_call` to the Chromia chain.
44
- *
45
- * Checks that the agent is onboarded before signing. The agent's private
46
- * key is used to sign the transaction locally — never sent over the network.
47
- */
48
- declare function logToolCall(action: string, context: string, auth: AgentAuth, chainOpts?: ChainOpts, extra?: {
49
- toolName?: string;
50
- toolArgsJson?: string;
51
- }, clientOpts?: ClientOpts): Promise<LogToolCallResult>;
52
27
  interface JudgeResult {
53
28
  verdict: Verdict;
54
29
  action_type: ActionType;
@@ -59,6 +34,22 @@ interface JudgeResult {
59
34
  tool_call_id: string;
60
35
  on_chain: boolean;
61
36
  }
37
+ interface JudgeOptions extends ClientOpts {
38
+ provider?: Provider;
39
+ model?: string;
40
+ toolName?: string;
41
+ toolArgsJson?: string;
42
+ chainOpts?: ChainOpts;
43
+ }
44
+ interface JudgmentStatus {
45
+ status: JudgmentStatusState;
46
+ verdict: Verdict;
47
+ reason: string;
48
+ judgmentId: string;
49
+ onChain?: boolean;
50
+ cached?: boolean;
51
+ responseTimeMs?: number;
52
+ }
62
53
  interface TierInfo {
63
54
  org_name: string;
64
55
  tier: Tier;
@@ -109,31 +100,41 @@ interface HeldActionReview {
109
100
  reviewed_at: number;
110
101
  created_at: number;
111
102
  }
112
- interface JudgmentStatus {
113
- status: JudgmentStatusState;
114
- verdict: Verdict;
115
- reason: string;
116
- judgmentId: string;
117
- onChain?: boolean;
118
- cached?: boolean;
119
- responseTimeMs?: number;
120
- }
121
- interface JudgeOptions extends ClientOpts {
122
- provider?: Provider;
123
- apiKey?: string;
124
- providerEndpoint?: string;
125
- model?: string;
126
- toolName?: string;
127
- toolArgsJson?: string;
128
- chainOpts?: ChainOpts;
129
- }
130
103
  interface AgentPolicy {
131
104
  policy: string;
132
105
  is_jailed: boolean;
133
106
  is_custom: boolean;
134
107
  default_policy: string;
135
108
  }
136
- declare function judgeAction(action: string, context: string, auth: AgentAuth, opts?: JudgeOptions): Promise<JudgeResult>;
109
+
110
+ declare const DEFAULT_ENDPOINT = "https://atbash.ai";
111
+ declare const DEFAULT_CHROMIA_NODE_URLS: string[];
112
+ declare const DEFAULT_BLOCKCHAIN_RID = "25B41DF620C489349C54944496FF5C6E58CFCEFED0C51658780B67299D40E8ED";
113
+ declare function isValidPrivateKey(hex: string): boolean;
114
+ declare function derivePublicKey(privKeyHex: string): string;
115
+ declare function generateKeyPair(): {
116
+ privKey: string;
117
+ pubKey: string;
118
+ };
119
+ declare function loadAgent(privkey: string): AgentAuth;
120
+ declare function toPubkeyHex(val: unknown): string;
121
+ /**
122
+ * Check if an agent is onboarded before signing anything.
123
+ * Calls GET /api/ai/exists?pubkey=<66-hex>
124
+ */
125
+ declare function checkAgentExists(pubkey: string, opts?: ClientOpts): Promise<boolean>;
126
+ /**
127
+ * Sign `log_tool_call` locally and return the signed transaction hex.
128
+ *
129
+ * Checks that the agent is onboarded before signing. The private key
130
+ * is used locally — never sent over the network. The server will
131
+ * broadcast the signed transaction to the chain.
132
+ */
133
+ declare function logToolCall(action: string, context: string, auth: AgentAuth, chainOpts?: ChainOpts, extra?: {
134
+ toolName?: string;
135
+ toolArgsJson?: string;
136
+ }, clientOpts?: ClientOpts): Promise<LogToolCallResult>;
137
+ declare function judgeAction(action: string, context: string | undefined, auth: AgentAuth, opts?: JudgeOptions): Promise<JudgeResult>;
137
138
  declare function getJudgmentStatus(judgmentId: string, opts?: ClientOpts): Promise<JudgmentStatus>;
138
139
  declare function getToolCalls(maxCount: number, opts?: ClientOpts): Promise<ToolCallRecord[]>;
139
140
  declare function getOrgToolCalls(orgName: string, maxCount: number, opts?: ClientOpts): Promise<ToolCallRecord[]>;
@@ -147,4 +148,4 @@ declare function getAgentDetail(agentPubkey: string, opts?: ClientOpts): Promise
147
148
  declare function getAgentPolicy(agentPubkey: string, opts?: ClientOpts): Promise<AgentPolicy>;
148
149
  declare function getSafetyStats(opts?: ClientOpts): Promise<Record<string, unknown>>;
149
150
 
150
- export { type AgentAuth, type ChainOpts, type ClientOpts, DEFAULT_BLOCKCHAIN_RID, DEFAULT_CHROMIA_NODE_URLS, DEFAULT_ENDPOINT, type HeldAction, type HeldActionReview, type JudgeOptions, type JudgeResult, type JudgmentStatus, type LogToolCallResult, type TierInfo, type ToolCallFull, type ToolCallRecord, type Verdict, checkAgentExists, derivePublicKey, generateKeyPair, getAgentDetail, getAgentPolicy, getAgentToolCalls, getHeldActionReviews, getJudgmentStatus, getOrgTierInfo, getOrgToolCalls, getPendingHeldActions, getSafetyStats, getToolCallCount, getToolCallFull, getToolCalls, isValidPrivateKey, judgeAction, loadAgent, logToolCall, toPubkeyHex };
151
+ export { type ActionType, type AgentAuth, type AgentPolicy, type ChainOpts, type ClientOpts, DEFAULT_BLOCKCHAIN_RID, DEFAULT_CHROMIA_NODE_URLS, DEFAULT_ENDPOINT, type HeldAction, type HeldActionReview, type JudgeOptions, type JudgeResult, type JudgmentStatus, type JudgmentStatusState, type LogToolCallResult, type Provider, type PubkeyValue, type Tier, type TierInfo, type ToolCallFull, type ToolCallRecord, type Verdict, checkAgentExists, derivePublicKey, generateKeyPair, getAgentDetail, getAgentPolicy, getAgentToolCalls, getHeldActionReviews, getJudgmentStatus, getOrgTierInfo, getOrgToolCalls, getPendingHeldActions, getSafetyStats, getToolCallCount, getToolCallFull, getToolCalls, isValidPrivateKey, judgeAction, loadAgent, logToolCall, toPubkeyHex };
package/dist/index.js CHANGED
@@ -52,14 +52,29 @@ function generateToolCallId() {
52
52
  const rand = randomBytes(4).toString("hex");
53
53
  return `tc-${ts}-${rand}`;
54
54
  }
55
+ async function buildSignedTx(opName, args, auth, chainOpts) {
56
+ const nodeUrls = chainOpts?.nodeUrls ?? DEFAULT_CHROMIA_NODE_URLS;
57
+ const blockchainRid = chainOpts?.blockchainRid ?? DEFAULT_BLOCKCHAIN_RID;
58
+ const client = await createClient({ nodeUrlPool: nodeUrls, blockchainRid });
59
+ const privKeyBuf = Buffer.from(auth.privkey, "hex");
60
+ const keyPair = encryption.makeKeyPair(privKeyBuf);
61
+ const sigProvider = newSignatureProvider({
62
+ privKey: keyPair.privKey,
63
+ pubKey: keyPair.pubKey
64
+ });
65
+ const signed = await client.signTransaction(
66
+ {
67
+ operations: [{ name: opName, args }],
68
+ signers: [keyPair.pubKey]
69
+ },
70
+ sigProvider
71
+ );
72
+ return Buffer.from(signed).toString("hex");
73
+ }
55
74
  async function checkAgentExists(pubkey, opts) {
56
- try {
57
- const url = `${baseUrl(opts)}/api/ai/exists?pubkey=${encodeURIComponent(pubkey)}`;
58
- const data = await getJson(url, opts);
59
- return Boolean(data.registered);
60
- } catch {
61
- return false;
62
- }
75
+ const url = `${baseUrl(opts)}/api/ai/exists?pubkey=${encodeURIComponent(pubkey)}`;
76
+ const data = await getJson(url, opts);
77
+ return Boolean(data.registered);
63
78
  }
64
79
  async function logToolCall(action, context, auth, chainOpts, extra, clientOpts) {
65
80
  const exists = await checkAgentExists(auth.pubkey, clientOpts);
@@ -71,38 +86,25 @@ async function logToolCall(action, context, auth, chainOpts, extra, clientOpts)
71
86
  };
72
87
  }
73
88
  try {
74
- const nodeUrls = chainOpts?.nodeUrls ?? DEFAULT_CHROMIA_NODE_URLS;
75
- const blockchainRid = chainOpts?.blockchainRid ?? DEFAULT_BLOCKCHAIN_RID;
76
- const client = await createClient({
77
- nodeUrlPool: nodeUrls,
78
- blockchainRid
79
- });
80
- const privKeyBuf = Buffer.from(auth.privkey, "hex");
81
- const keyPair = encryption.makeKeyPair(privKeyBuf);
82
- const sigProvider = newSignatureProvider({
83
- privKey: keyPair.privKey,
84
- pubKey: keyPair.pubKey
85
- });
86
89
  const toolCallId = generateToolCallId();
87
- await client.signAndSendUniqueTransaction(
88
- {
89
- name: "log_tool_call",
90
- args: [
91
- toolCallId,
92
- action,
93
- context || "",
94
- extra?.toolName || "",
95
- extra?.toolArgsJson || ""
96
- ]
97
- },
98
- sigProvider
90
+ const signedHex = await buildSignedTx(
91
+ "log_tool_call",
92
+ [
93
+ toolCallId,
94
+ action,
95
+ context || "",
96
+ extra?.toolName || "",
97
+ extra?.toolArgsJson || ""
98
+ ],
99
+ auth,
100
+ chainOpts
99
101
  );
100
- return { success: true, toolCallId };
102
+ return { success: true, toolCallId, signedHex };
101
103
  } catch (err) {
102
104
  return {
103
105
  success: false,
104
106
  toolCallId: null,
105
- error: err instanceof Error ? err.message : "Failed to log tool call on-chain"
107
+ error: err instanceof Error ? err.message : "Failed to sign log_tool_call"
106
108
  };
107
109
  }
108
110
  }
@@ -157,6 +159,7 @@ async function postJson(url, body, opts) {
157
159
  async function getJson(url, opts) {
158
160
  const resp = await fetch(url, {
159
161
  method: "GET",
162
+ headers: { Accept: "application/json" },
160
163
  signal: opts?.timeout ? AbortSignal.timeout(opts.timeout) : void 0
161
164
  });
162
165
  if (!resp.ok) {
@@ -165,7 +168,10 @@ async function getJson(url, opts) {
165
168
  }
166
169
  return resp.json();
167
170
  }
168
- async function judgeAction(action, context, auth, opts) {
171
+ async function judgeAction(action, context = "", auth, opts) {
172
+ if (!action || !action.trim()) {
173
+ throw new Error("action is required and cannot be empty.");
174
+ }
169
175
  const logResult = await logToolCall(
170
176
  action,
171
177
  context,
@@ -174,25 +180,32 @@ async function judgeAction(action, context, auth, opts) {
174
180
  { toolName: opts?.toolName, toolArgsJson: opts?.toolArgsJson },
175
181
  opts
176
182
  );
177
- if (!logResult.success || !logResult.toolCallId) {
178
- throw new Error(logResult.error || "Failed to log tool call on-chain");
183
+ if (!logResult.success || !logResult.toolCallId || !logResult.signedHex) {
184
+ throw new Error(logResult.error || "Failed to sign log_tool_call");
185
+ }
186
+ let signedJudgeActionHex;
187
+ if (!opts?.provider) {
188
+ const judgmentId = generateToolCallId();
189
+ signedJudgeActionHex = await buildSignedTx(
190
+ "judge_action",
191
+ [judgmentId, action, context || "", ""],
192
+ auth,
193
+ opts?.chainOpts
194
+ );
179
195
  }
180
196
  const url = `${baseUrl(opts)}/api/v1/judge`;
181
- const data = await postJson(
182
- url,
183
- {
184
- tool_call_id: logResult.toolCallId,
185
- agent_pubkey: auth.pubkey,
186
- action,
187
- ...context && { context },
188
- ...opts?.provider && { provider: opts.provider },
189
- ...opts?.toolName && { tool_name: opts.toolName },
190
- ...opts?.apiKey && { api_key: opts.apiKey },
191
- ...opts?.providerEndpoint && { endpoint_url: opts.providerEndpoint },
192
- ...opts?.model && { model: opts.model }
193
- },
194
- opts
195
- );
197
+ const body = {
198
+ tool_call_id: logResult.toolCallId,
199
+ agent_pubkey: auth.pubkey,
200
+ action,
201
+ signed_log_tool_call: logResult.signedHex,
202
+ ...signedJudgeActionHex && { signed_judge_action: signedJudgeActionHex },
203
+ ...context && { context },
204
+ ...opts?.provider && { provider: opts.provider },
205
+ ...opts?.toolName && { tool_name: opts.toolName },
206
+ ...opts?.model && { model: opts.model }
207
+ };
208
+ const data = await postJson(url, body, opts);
196
209
  return {
197
210
  verdict: normalizeVerdict(data.verdict),
198
211
  action_type: String(data.action_type || ""),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atbash/sdk",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Atbash SDK — control boundary before the last irreversible step in an agent workflow",
5
5
  "homepage": "https://atbash.ai",
6
6
  "author": "Atbash",
@@ -26,6 +26,7 @@
26
26
  },
27
27
  "scripts": {
28
28
  "build": "tsup src/index.ts --format esm,cjs --dts --clean",
29
+ "typecheck": "tsc --noEmit",
29
30
  "release": "npm version patch --no-git-tag-version && npm run build && npx npm@10 publish --access public"
30
31
  },
31
32
  "devDependencies": {