@interven/sdk-js
v0.5.4
Published
Interven JS/TS SDK — Approvals + Audit for AI Agents. Scan tool calls through policy + risk scoring. Block, redact PII, or route to human approval via Slack.
Maintainers
Readme
@interven/sdk-js
Official JavaScript/TypeScript SDK for Interven -- the Agent Identity Firewall (AIF). Scan AI agent tool calls through policy + risk scoring. Block, redact PII, or route to human approval.
Installation
npm install @interven/sdk-jsNo peer dependencies required. Works with Node.js 16+ and modern browsers (uses fetch).
Quick Start
import { IntervenClient } from '@interven/sdk-js';
const client = new IntervenClient({ apiKey: 'iv_live_...' });
const result = await client.scan({
method: 'POST',
url: 'https://slack.com/api/chat.postMessage',
body: { channel: '#general', text: 'Hello from my agent' },
});
switch (result.decision) {
case 'ALLOW':
// Proceed with the original request
await sendToSlack(result.raw);
break;
case 'SANITIZE':
// Use the redacted body (PII removed)
await sendToSlack(result.sanitizedBody);
break;
case 'DENY':
console.log('Blocked:', result.reasonCodes);
break;
case 'REQUIRE_APPROVAL':
// Wait for human approval (see gate() below)
break;
}Configuration
const client = new IntervenClient({
apiKey: 'iv_live_...', // or set INTERVEN_API_KEY env var
gatewayUrl: 'http://localhost:4000', // or set INTERVEN_GATEWAY_URL env var
timeout: 30_000, // request timeout in ms (default: 30s)
agentId: 'uuid-of-your-agent', // optional default agent UUID
runtimeType: 'langchain', // telemetry tag (default: "node")
});gate() -- Handle All Decisions Automatically
The gate() function scans a tool call and handles all four decisions (ALLOW, DENY, SANITIZE, REQUIRE_APPROVAL) in a single call. For REQUIRE_APPROVAL, it polls until the analyst approves or denies.
import { IntervenClient, gate } from '@interven/sdk-js';
const client = new IntervenClient({ apiKey: 'iv_live_...' });
const result = await gate(client, {
method: 'POST',
url: 'https://api.github.com/repos/acme/app/issues',
body: { title: 'Bug report', body: 'Details...' },
maxWait: 600, // wait up to 10 min for approval (default)
pollInterval: 5, // poll every 5s (default)
onPending: (scan) => {
console.log(`Waiting for approval: ${scan.approvalId}`);
},
});
if (result.proceed) {
// ALLOW, approved REQUIRE_APPROVAL, or SANITIZE
await createGitHubIssue(result.body); // use result.body (may be sanitized)
} else {
console.log('Blocked:', result.blockedReason);
}GateResult fields
| Field | Type | Description |
|-------|------|-------------|
| proceed | boolean | Whether the tool should execute |
| decision | string | Raw decision: ALLOW, DENY, REQUIRE_APPROVAL, SANITIZE |
| scan | ScanResponse? | Full scan response for inspection |
| body | unknown? | Body to use (sanitized for SANITIZE, original otherwise) |
| blockedReason | string | Human-readable reason when blocked |
| approvalWaited | boolean | Whether the call waited for human approval |
guard() -- Wrap Any Function
The guard() higher-order function wraps an async function with automatic scanning. If the scan says DENY, the function is never called.
import { IntervenClient, guard, isBlocked } from '@interven/sdk-js';
const client = new IntervenClient({ apiKey: 'iv_live_...' });
// Wrap your tool function
const safeSendSlack = guard(
client,
{ url: 'https://slack.com/api/chat.postMessage', method: 'POST' },
async (message: { channel: string; text: string }) => {
const resp = await fetch('https://slack.com/api/chat.postMessage', {
method: 'POST',
headers: { 'Authorization': `Bearer ${SLACK_TOKEN}` },
body: JSON.stringify(message),
});
return resp.json();
},
);
// Call it normally -- Interven scans before executing
const result = await safeSendSlack({ channel: '#general', text: 'Hello!' });
if (isBlocked(result)) {
console.log('Blocked:', result.reason);
} else {
console.log('Sent:', result);
}Health Check
const isHealthy = await client.health();
console.log('Gateway reachable:', isHealthy);Approval Polling (Manual)
If you need more control than gate() provides, you can poll approvals manually:
const scan = await client.scan({ method: 'DELETE', url: '...', body: {} });
if (scan.decision === 'REQUIRE_APPROVAL' && scan.approvalId) {
// Option 1: Poll once
const status = await client.pollApproval(scan.approvalId);
console.log(status.status); // "pending", "approved", "denied", "expired"
// Option 2: Block until resolved
const finalStatus = await client.waitForApproval(scan.approvalId, {
pollInterval: 5, // seconds between polls (min 2.5)
maxWait: 3600, // max seconds to wait (default: 1 hour)
raiseOnTerminal: true, // throw on denied/expired (default: true)
});
}Response Scanning
After a scan returns ALLOW, you can classify the upstream response for audit:
const scan = await client.scan({ method: 'GET', url: '...', body: {} });
if (scan.decision === 'ALLOW') {
const upstreamResponse = await fetch('...');
const responseBody = await upstreamResponse.json();
const classification = await client.scanResponse({
traceId: scan.traceId,
responseBody,
responseStatus: upstreamResponse.status,
});
console.log('Data classes found:', classification.dataClasses);
}Error Handling
The SDK throws typed errors for different failure modes:
import {
IntervenClient,
AuthenticationError,
PayloadTooLargeError,
GatewayError,
ApprovalTimeoutError,
ApprovalDeniedError,
ApprovalExpiredError,
} from '@interven/sdk-js';
try {
const result = await client.scan({ method: 'POST', url: '...', body: largePayload });
} catch (err) {
if (err instanceof AuthenticationError) {
// API key invalid or revoked
} else if (err instanceof PayloadTooLargeError) {
// Body exceeds 256KB limit
} else if (err instanceof GatewayError) {
// Other gateway errors (network, 5xx, etc.)
}
}Environment Variables
| Variable | Description |
|----------|-------------|
| INTERVEN_API_KEY | Default API key (used if apiKey not passed to constructor) |
| INTERVEN_GATEWAY_URL | Default gateway URL (used if gatewayUrl not passed) |
License
MIT
