Client SDK for integrating with Ziggy's v1 API
npm install @ziggy-ai/client-sdkbash
npm install @ziggy-ai/client-sdk
`
Quick Start
`typescript
import { ZiggyClient } from '@ziggy-ai/client-sdk';
const ziggy = new ZiggyClient({
baseUrl: 'https://ziggy.example.com',
appId: 'my-app',
apiKey: 'pk_live_abc123',
});
// Connect (creates session, starts context batching + analytics)
const session = await ziggy.connect({
userId: 'user-1',
context: {
page: { path: '/dashboard', type: 'dashboard', title: 'Dashboard' },
user: { id: 'user-1', tier: 'pro', sessionCount: 42 },
},
onTrigger: (response) => {
if (response.trigger) showProactiveBubble(response.trigger);
},
});
// Streaming chat
for await (const event of ziggy.chat.stream({ message: 'Why did my transform fail?' })) {
switch (event.type) {
case 'content.delta': appendToUI(event.content); break;
case 'tool.start': showToolIndicator(event.toolName); break;
case 'metadata': renderActions(event.quickActions); break;
}
}
// Disconnect (flushes analytics, stops batching, ends session)
await ziggy.disconnect();
`
API
$3
The main entry point. Composes specialized sub-clients for each API area.
`typescript
const ziggy = new ZiggyClient({
baseUrl: string; // Ziggy API base URL
appId: string; // Your app identifier
apiKey: string; // API key (from App node in Ziggy)
timeout?: number; // Request timeout in ms (default: 30000)
});
`
#### Lifecycle
`typescript
// Connect — creates session, wires userId, starts batching
const session = await ziggy.connect({
userId: string;
context: Record;
capabilities?: string[]; // default: ['streaming', 'actions']
onTrigger?: (response: ContextPushResponse) => void;
});
// Disconnect — flush analytics, stop batching, destroy session
await ziggy.disconnect();
`
---
$3
`typescript
// Non-streaming
const response = await ziggy.chat.send({ message: 'Help me debug this' });
console.log(response.response.content);
console.log(response.response.agentId); // 'knuckles', 'ziggy', etc.
// Streaming (ndjson, MCP 2025-03-26 standard)
for await (const event of ziggy.chat.stream({ message: 'Help me debug this' })) {
// event.type: 'stream.start' | 'agent.start' | 'content.delta' |
// 'content.done' | 'tool.start' | 'tool.result' |
// 'metadata' | 'stream.end' | 'error'
}
// Resume interrupted stream
for await (const event of ziggy.chat.resume(streamId, lastSeqNumber)) {
// Pick up where you left off
}
`
Chat request options:
`typescript
{
message: string;
attachments?: Array<{ type: 'screenshot' | 'file_reference'; id: string }>;
context?: Record; // Bundled context update
requestId?: string; // Client-generated dedup ID
}
`
---
$3
Context updates are auto-batched every 10 seconds. Important changes (page navigation, errors, process status) flush immediately.
`typescript
// Queue a context update (merges with pending batch)
ziggy.context.update({
page: { path: '/settings', type: 'settings', title: 'Settings' },
});
// Queue an event
ziggy.context.addEvent({
type: 'button_click',
timestamp: new Date().toISOString(),
data: { buttonId: 'retry' },
});
// Force flush
await ziggy.context.flush();
`
---
$3
Register triggers, quick actions, brand identity, and credentials.
`typescript
// Brand
await ziggy.app.setBrand({
identity: { name: 'Coach', tagline: 'Your training partner' },
voice: { tone: ['motivational', 'knowledgeable'], formality: 'casual-professional' },
theme: { primaryColor: '#2E7D32', chatPosition: 'bottom-right' },
});
const brand = await ziggy.app.getBrand();
// Triggers (full sync — replaces all existing)
await ziggy.app.registerTriggers({
triggers: [{
id: 'transform-error',
name: 'Transform Error',
condition: { type: 'process_status', value: 'error' },
priority: 'critical',
message: "That didn't work — want me to figure out why?",
}],
});
const { triggers } = await ziggy.app.listTriggers();
await ziggy.app.deleteTrigger('transform-error');
// Quick Actions (full sync)
await ziggy.app.registerQuickActions({
sets: [{
id: 'dashboard-empty',
contextState: 'dashboard-empty',
conditions: { pageTypes: ['dashboard'] },
actions: [{ id: 'start', label: 'Get started', action: 'onboarding.start', priority: 1 }],
}],
});
const { sets } = await ziggy.app.listQuickActions();
await ziggy.app.deleteQuickActionSet('dashboard-empty');
// Credentials (encrypted at rest, AES-256-GCM)
await ziggy.app.setCredentials({ github_token: 'ghp_...', supabase_key: 'eyJ...' });
const { credentials, updatedAt } = await ziggy.app.getCredentials(); // keys only, never values
await ziggy.app.deleteCredentials();
`
---
$3
Handle tool execution requests from Ziggy. When Ziggy needs your app to do something (navigate, retry a process, etc.), it POSTs to your registered callback URL.
`typescript
import { CallbackHandler } from '@ziggy-ai/client-sdk';
const handler = new CallbackHandler();
handler
.on('support.navigate', async (params) => {
router.push(params.path as string);
return { navigated: true };
})
.on('support.retry_process', async (params) => {
await processes.retry(params.processId as string);
return { retried: true };
});
// Mount in your API framework
app.post('/api/ziggy/callback', async (req, res) => {
const result = await handler.handle(req.body);
res.json(result);
});
`
---
$3
Invoke tools directly (e.g., from quick action buttons).
`typescript
const result = await ziggy.tools.invoke('support.navigate', { path: '/settings' });
// Streaming tool invocation
for await (const event of ziggy.tools.invokeStream('support.respond', { message: 'Hi' })) {
// event.type: 'tool.start' | 'tool.result'
}
`
---
$3
`typescript
// Quick reaction
await ziggy.feedback.react('msg-1', 'helpful');
// reaction: 'helpful' | 'not_helpful' | 'wrong' | 'perfect'
// Detailed feedback
await ziggy.feedback.submit({
conversationId: 'conv-1',
type: 'accuracy', // 'accuracy' | 'helpfulness' | 'tone' | 'action' | 'correction'
rating: 4,
comment: 'Good answer but missed one detail',
userId: 'user-1',
});
// Record coordination outcome (for learning loop)
await ziggy.feedback.recordOutcome({
coordinationId: 'coord-1',
success: true,
agentIds: ['knuckles'],
});
`
---
$3
Track multi-step workflows.
`typescript
const { quest } = await ziggy.quests.create({
title: 'Investigate transform failure',
sessionId: session.sessionId,
initiatorId: 'knuckles',
steps: [
{ label: 'Check logs', agentId: 'ziggy' },
{ label: 'Analyze error' },
],
});
await ziggy.quests.updateStep(quest.id, quest.steps[0].id, {
status: 'complete',
update: { message: 'Found 3 errors in logs' },
});
// Stream quest updates
for await (const event of ziggy.quests.stream(quest.id)) {
// event.type: 'quest.status' | 'quest.step_update' | 'quest.complete'
}
`
---
$3
Events are auto-batched every 30 seconds and flushed on disconnect.
`typescript
ziggy.analytics.track('feature_used', { feature: 'dark-mode' });
ziggy.analytics.track('page_viewed', { path: '/settings' });
`
---
Stream Events
All streaming endpoints use ndjson (newline-delimited JSON) with sequence numbers for resumption.
| Event Type | Key Fields | Description |
|------------|------------|-------------|
| stream.start | streamId, messageId | Always first |
| agent.start | agentId, agentRole | Routed agent begins |
| agent.switch | fromAgentId, toAgentId | Agent handoff |
| content.delta | content | Partial text |
| content.done | fullContent | Complete text |
| tool.start | toolId, toolName | Tool invoked |
| tool.progress | toolId, progress | Tool progress (0-1) |
| tool.result | toolId, success | Tool completed |
| metadata | confidence, quickActions | Response metadata |
| stream.end | messageId, durationMs | Always last |
| error | code, message, retryable | Error during stream |
$3
`typescript
import { SequenceTracker } from '@ziggy-ai/client-sdk';
const tracker = new SequenceTracker();
for await (const event of ziggy.chat.stream({ message: 'Hello' })) {
tracker.update(event);
// ... process event
}
// If disconnected, resume:
if (tracker.streamId) {
for await (const event of ziggy.chat.resume(tracker.streamId, tracker.lastSeq)) {
// Picks up from where you left off
}
}
`
---
Error Handling
`typescript
import { ZiggyError, ZiggyConnectionError } from '@ziggy-ai/client-sdk';
try {
await ziggy.chat.send({ message: 'Hello' });
} catch (error) {
if (error instanceof ZiggyConnectionError) {
// Network/timeout — error.retryable is true
console.log('Connection failed, will retry');
} else if (error instanceof ZiggyError) {
// API error — check error.status
console.log(API error ${error.status}: ${error.message});
}
}
`
$3
`typescript
import { withRetry } from '@ziggy-ai/client-sdk';
const result = await withRetry(
() => ziggy.chat.send({ message: 'Hello' }),
{ maxRetries: 3, baseDelayMs: 1000, maxDelayMs: 30000 },
);
// Retries with exponential backoff + jitter. Skips retry for 4xx errors.
`
---
Architecture
The SDK is a thin client that maps to Ziggy's v1 REST API:
`
ZiggyClient
├── sessions → POST/DELETE /v1/sessions
├── chat → POST /v1/chat, POST /v1/chat/stream
├── context → POST /v1/sessions/:id/context (auto-batched)
├── tools → POST /v1/tools/invoke
├── feedback → POST /v1/feedback/*
├── quests → /v1/quests/*
├── analytics → POST /v1/feedback/analytics/events (auto-batched)
├── app → /v1/apps/* (triggers, quick actions, brand, credentials)
└── callbacks → CallbackHandler (host-side, receives Ziggy tool requests)
`
Key design decisions:
- Auto-batching — Context (10s) and analytics (30s) are batched to reduce API calls. Important context changes flush immediately.
- Streaming — Uses Streamable HTTP with ndjson (MCP 2025-03-26 standard). Sequence numbers enable reconnection.
- Composition — Each API area is a separate module composed by ZiggyClient.
- Zero dependencies — Uses only fetch (built into Node 18+ and all modern browsers).
---
Development
`bash
npm run build # Build with tsup (ESM + .d.ts)
npm run dev # Watch mode
npm run typecheck # Type check without emit
npm test # Run tests (vitest)
``