@agentfield/hax-sdk
v0.2.4
Published
TypeScript SDK for the HAX (Human Approval eXchange) API
Maintainers
Readme
HAX TypeScript SDK
Official TypeScript SDK for the HAX (Human Approval eXchange) API.
Installation
npm install @agentfield/hax-sdk
# or
yarn add @agentfield/hax-sdk
# or
pnpm add @agentfield/hax-sdkQuick Start
import { HaxClient, isCompleted } from '@agentfield/hax-sdk';
const client = new HaxClient({
apiKey: process.env.HAX_API_KEY,
});
// Create an approval request
const request = await client.createRequest({
type: 'text-approval-v1',
payload: { text: 'Deploy v2.0.0 to production?' },
webhookUrl: 'https://myapp.com/webhook',
});
console.log('Approval URL:', request.url);
// Wait for the response
const result = await client.waitForResponse(request.id, {
timeout: 300, // 5 minutes
});
if (isCompleted(result)) {
console.log('Completed!', result.response);
}Features
- Type-safe: Full TypeScript support with comprehensive types
- Zero dependencies: Uses native
fetchand Web Crypto API - Automatic retries: Configurable retry logic with exponential backoff
- Webhook verification: Secure signature verification for webhooks
- End-to-end encryption: RSA-OAEP + AES-GCM hybrid encryption for sensitive response data
- FormBuilder: Fluent API for building typed forms
- Modern: ESM-first, Node.js 18+ compatible
API Reference
HaxClient
The main client for interacting with the HAX API.
import { HaxClient } from '@agentfield/hax-sdk';
const client = new HaxClient({
apiKey: 'hax_live_...',
baseUrl: 'https://hax-sdk-production.up.railway.app/api/v1', // optional
webhookUrl: 'https://myapp.com/webhook', // optional default webhook
timeout: 30000, // optional, in milliseconds
maxRetries: 3, // optional
encryptionKey: 'my-passphrase', // optional, for client-side encryption
});Request Methods
// Create a request
const request = await client.createRequest({
type: 'text-approval-v1',
payload: { text: 'Approve this action?' },
title: 'Optional title',
description: 'Optional description',
webhookUrl: 'https://myapp.com/webhook',
expiresInSeconds: 3600,
metadata: { key: 'value' },
});
// Create and send via email
const request = await client.requestViaEmail({
type: 'confirm-action-v1',
payload: { title: 'Confirm?', confirmPhrase: 'YES' },
toEmail: '[email protected]',
subject: 'Approval Required',
});
// Create and send via SMS
const request = await client.requestViaSms({
type: 'text-approval-v1',
payload: { text: 'Approve?' },
toPhone: '+15551234567',
});
// Get a request by ID
const request = await client.getRequest('req_123');
// List recent requests
const requests = await client.listRequests();
// Cancel a pending request
const cancelled = await client.cancelRequest('req_123');
// Submit a response (for testing)
const completed = await client.submitResponse('req_123', { decision: 'approve' });
// Wait for completion
const result = await client.waitForResponse('req_123', {
pollInterval: 2, // seconds
timeout: 60, // seconds
});
// List available template types
const types = await client.listTypes();Status Helpers
import { isPending, isOpened, isCompleted, isExpired, isCancelled } from '@agentfield/hax-sdk';
if (isPending(request)) {
// Request is waiting for response
}
if (isCompleted(request)) {
console.log('Response:', request.response);
}Template Types
Available built-in templates:
| Template | Description |
|----------|-------------|
| text-approval-v1 | Show text and collect an approve/deny decision |
| confirm-action-v1 | Require typing a specific phrase to confirm a destructive action |
| collect-email-v1 | Prompt the user for an email address |
| form-builder | Advanced forms with field types, layouts, validation, and conditional logic |
| multi-choice-selection-v1 | Single or multiple selection from customizable option cards |
| code-changes-v1 | GitHub-style diff view with inline line comments |
| rich-text-editor-v1 | Markdown-formatted text editing for documents and reports |
| file-upload-v1 | Collect files (documents, images, CSVs) from users |
| signature-capture-v1 | Capture e-signatures with optional signer name and legal text |
| data-table-review-v1 | Review, select, or edit tabular data |
| scheduling-picker-v1 | Date/time slot selection with optional recurring schedules |
| multi-step-wizard-v1 | Sequential steps with navigation and progress indicator |
| side-by-side-comparison-v1 | Compare two versions with diff highlighting |
| terminal-output-v1 | Display command output/logs with approve-to-continue |
Example payloads:
// text-approval-v1
await client.createRequest({
type: 'text-approval-v1',
payload: {
text: 'The message to display',
approveLabel: 'Approve', // optional
denyLabel: 'Deny', // optional
},
});
// confirm-action-v1
await client.createRequest({
type: 'confirm-action-v1',
payload: {
title: 'Dangerous Action',
message: 'This cannot be undone.',
confirmPhrase: 'DELETE ALL',
confirmLabel: 'Confirm', // optional
cancelLabel: 'Cancel', // optional
warningLevel: 'danger', // 'info' | 'warning' | 'danger'
},
});Webhooks
Verify and parse webhook events:
import { verifyWebhook, WebhookVerificationError } from '@agentfield/hax-sdk';
// In your webhook handler
app.post('/webhooks/hax', async (req, res) => {
try {
const event = await verifyWebhook({
signature: req.headers['x-hax-signature'],
payload: req.body, // raw string body
secret: process.env.HAX_WEBHOOK_SECRET,
});
switch (event.type) {
case 'completed':
// Handle completion
break;
case 'expired':
// Handle expiration
break;
}
res.status(200).send('OK');
} catch (error) {
if (error instanceof WebhookVerificationError) {
res.status(400).send('Invalid signature');
}
throw error;
}
});Event Types
sent- Request notification was sentopened- Request was viewedcompleted- Request was completedexpired- Request expired
FormBuilder
Build typed forms with a fluent API:
import { HaxClient, FormBuilder } from '@agentfield/hax-sdk';
const client = new HaxClient({ apiKey: 'hax_live_...' });
// Create a typed form
const form = FormBuilder.create()
.title('Event Registration')
.input('name', { label: 'Full Name', required: true })
.input('email', { label: 'Email', variant: 'email', required: true })
.number('age', { label: 'Age', min: 0, max: 120 })
.checkbox('newsletter', { checkboxLabel: 'Subscribe to newsletter' });
// Create the form and get a typed handle
const handle = await client.createFormRequest(form);
console.log('Form URL:', handle.url);
// Wait for the typed response
const response = await handle.waitForResponse({ timeout: 300 });
console.log(response.values.name); // string
console.log(response.values.email); // string
console.log(response.values.age); // number
console.log(response.values.newsletter); // booleanRehydrating Form Handles
If you lose the handle (e.g., across requests), rehydrate it with the request ID:
// Rehydrate from a stored request ID
const handle = client.getFormHandle('req_abc123', form);
const response = await handle.waitForResponse();Encryption
For sensitive response data, use end-to-end encryption:
import { HaxClient } from '@agentfield/hax-sdk';
// Use a passphrase (RSA keypair is derived deterministically)
const client = new HaxClient({
apiKey: 'hax_live_...',
encryptionKey: 'my-secret-passphrase',
});
// Public key is sent with the request
// Server encrypts user responses with it
const request = await client.createRequest({
type: 'text-approval-v1',
payload: { text: 'Approve this sensitive action?' },
});
// Response is automatically decrypted
const completed = await client.getRequest(request.id);
console.log(completed.response); // DecryptedError Handling
import {
HaxError,
ApiError,
AuthenticationError,
ValidationError,
NotFoundError,
RateLimitError,
NetworkError,
} from '@agentfield/hax-sdk';
try {
await client.createRequest({ /* ... */ });
} catch (error) {
if (error instanceof AuthenticationError) {
// Invalid API key (401)
} else if (error instanceof ValidationError) {
// Invalid request data (400/422)
console.log(error.errors);
} else if (error instanceof NotFoundError) {
// Resource not found (404)
} else if (error instanceof RateLimitError) {
// Too many requests (429)
console.log(error.retryAfter);
} else if (error instanceof NetworkError) {
// Network/connection error
} else if (error instanceof ApiError) {
// Other API error
console.log(error.statusCode);
}
}Examples
See the examples directory for complete working examples:
- Basic Approval - Simple text approval request
- Confirm Action - Dangerous action confirmation
- Webhook Handler - Processing webhooks
Requirements
- Node.js 18+ (for native
fetchand Web Crypto API) - TypeScript 5.0+ (optional, for TypeScript users)
License
MIT
