hydra-aidirector 1.6.0 → 1.6.2

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,399 +1,310 @@
1
- # hydra-aidirector
2
-
3
- The official Node.js/TypeScript SDK for [Hydra](https://hydrai.dev) a high-performance AI API gateway.
4
-
5
- [![npm version](https://badge.fury.io/js/hydra-aidirector.svg)](https://www.npmjs.com/package/hydra-aidirector)
6
- [![TypeScript](https://badges.frapsoft.com/typescript/code/typescript.svg?v=101)](https://www.typescriptlang.org/)
7
-
8
- ## Why Hydra?
9
-
10
- | Problem | Hydra Solution |
11
- |---------|----------------|
12
- | LLM outages break your app | 🔄 **Automatic Failover** – Seamless fallback between providers |
13
- | High latency & costs | ⚡ **God-Tier Caching** – Hybrid Redis + DB cache with AI-directed scoping |
14
- | Malformed JSON responses | 🛡️ **Self-Healing AI** – Auto-repair JSON, strip markdown, extract from prose |
15
- | Schema compliance issues | ✅ **Strict JSON Mode** – Force models to conform to your schema or fail |
16
- | No visibility into usage | 📊 **Detailed Analytics** – Track tokens, latency, and costs per model |
17
-
18
- ---
19
-
20
- ## Installation
21
-
22
- ```bash
23
- npm install hydra-aidirector
24
- # or
25
- pnpm add hydra-aidirector
26
- # or
27
- yarn add hydra-aidirector
28
- ```
29
-
30
- ---
31
-
32
- ## Quick Start
33
-
34
- ```typescript
35
- import { Hydra } from 'hydra-aidirector';
36
-
37
- const client = new Hydra({
38
- secretKey: process.env.HYDRA_SECRET_KEY!, // hyd_sk_...
39
- baseUrl: 'https://your-instance.vercel.app',
40
- });
41
-
42
- // Basic generation
43
- const result = await client.generate({
44
- chainId: 'my-chain',
45
- prompt: 'Generate 5 user profiles as JSON',
46
- });
47
-
48
- if (result.success) {
49
- console.log(result.data.valid); // Parsed, schema-validated objects
50
- }
51
- ```
52
-
53
- ---
54
-
55
- ## Core Features
56
-
57
- ### 🔄 Automatic Failover
58
- Define fallback chains in your dashboard. If Gemini fails, Hydra automatically tries OpenRouter, Claude, etc.
59
-
60
- ### 🛡️ Self-Healing JSON
61
- LLMs sometimes return broken JSON. Hydra extracts and repairs it automatically:
62
-
63
- ```typescript
64
- const result = await client.generate({ chainId: 'x', prompt: 'Get user' });
65
-
66
- if (result.meta.recovered) {
67
- console.log('JSON was malformed but healed!');
68
- console.log(result.data.healingReport);
69
- // [{ original: "{name: 'foo'", healed: { name: "foo" }, fixes: ["Added closing brace"] }]
70
- }
71
- ```
72
-
73
- ### ✅ Strict JSON Mode vs Self-Healing
74
-
75
- Hydra's self-healing JSON repair **works in both modes**. The difference is how the model behaves:
76
-
77
- | Mode | Model Behavior | Healing Role |
78
- |------|----------------|--------------|
79
- | **Strict** (`strictJson: true`) | Model is **forced** to output pure JSON via native API constraints. Output is already clean. | Safety net — rarely needed since output is constrained. |
80
- | **Non-Strict** (`strictJson: false`) | Model outputs best-effort JSON (may include markdown, prose, or broken syntax). | Primary mechanism — extracts and repairs JSON from messy output. |
81
-
82
- ```typescript
83
- // Strict mode - model constrained to pure JSON
84
- await client.generate({
85
- chainId: 'gemini-chain',
86
- prompt: 'Extract invoice data',
87
- schema: invoiceSchema,
88
- strictJson: true, // No markdown, no explanations
89
- });
90
-
91
- // Non-strict mode - flexible output, Hydra heals as needed
92
- await client.generate({
93
- chainId: 'any-chain',
94
- prompt: 'Generate a creative story with metadata',
95
- schema: storySchema,
96
- strictJson: false, // Allow model to be creative, Hydra extracts JSON
97
- });
98
- ```
99
-
100
- ---
101
-
102
- ## Streaming (Recommended for Large Responses)
103
-
104
- Process JSON objects as they arrive — perfect for UIs that need instant feedback:
105
-
106
- ```typescript
107
- await client.generateStream(
108
- {
109
- chainId: 'my-chain',
110
- prompt: 'Generate 100 product descriptions',
111
- },
112
- {
113
- onObject: (obj, index) => {
114
- console.log(`Object ${index}:`, obj);
115
- renderToUI(obj); // Render immediately!
116
- },
117
- onComplete: (result) => {
118
- console.log(`Done! ${result.objectCount} objects`);
119
- },
120
- onError: (error) => {
121
- console.error('Stream failed:', error);
122
- },
123
- }
124
- );
125
- ```
126
-
127
- ---
128
-
129
- ## Batch Generation
130
-
131
- Process multiple prompts in parallel with automatic error handling:
132
-
133
- ```typescript
134
- const result = await client.generateBatch('my-chain', [
135
- { id: 'item1', prompt: 'Describe product A' },
136
- { id: 'item2', prompt: 'Describe product B' },
137
- { id: 'item3', prompt: 'Describe product C' },
138
- ]);
139
-
140
- console.log(`Processed ${result.summary.succeeded}/${result.summary.total}`);
141
- ```
142
-
143
- ---
144
-
145
- ## Caching
146
-
147
- ### Cache Scope
148
- Control how responses are cached:
149
-
150
- ```typescript
151
- // Global cache (shared across users - default)
152
- await client.generate({ chainId: 'x', prompt: 'Facts', cacheScope: 'global' });
153
-
154
- // User-scoped cache (private to authenticated user)
155
- await client.generate({ chainId: 'x', prompt: 'My profile', cacheScope: 'user' });
156
-
157
- // Skip cache entirely
158
- await client.generate({ chainId: 'x', prompt: 'Random', cacheScope: 'skip' });
159
- ```
160
-
161
- ### Cache Quality
162
- Control the trade-off between cache hit rate and precision:
163
-
164
- | Level | Behavior |
165
- |-------|----------|
166
- | `STANDARD` | Balanced fuzzy matching. Good for most cases. |
167
- | `HIGH` | Stricter matching. Higher quality hits, lower hit rate. |
168
- | `MAX_EFFICIENCY` | Aggressive matching. Maximum cost savings, less precision. |
169
-
170
- ```typescript
171
- await client.generate({
172
- chainId: 'my-chain',
173
- prompt: 'Generate report',
174
- cacheQuality: 'MAX_EFFICIENCY', // Maximize cache hits
175
- });
176
- ```
177
-
178
- ### AI-Directed Caching
179
- The AI can override cache scope by including a `_cache` directive in its output:
180
-
181
- ```json
182
- {
183
- "data": { "...": "..." },
184
- "_cache": { "scope": "user" }
185
- }
186
- ```
187
-
188
- The directive is automatically stripped from your final response.
189
-
190
- ---
191
-
192
- ## File Attachments
193
-
194
- Upload documents for analysis. Hydra handles type detection and model compatibility:
195
-
196
- ```typescript
197
- import fs from 'fs';
198
-
199
- const fileBuffer = fs.readFileSync('report.pdf');
200
-
201
- const result = await client.generate({
202
- chainId: 'document-analysis',
203
- prompt: 'Summarize this document',
204
- files: [{
205
- data: fileBuffer.toString('base64'),
206
- filename: 'report.pdf',
207
- mimeType: 'application/pdf',
208
- }],
209
- });
210
- ```
211
-
212
- ---
213
-
214
- ## Request Cancellation
215
-
216
- Cancel long-running requests with `AbortSignal`:
217
-
218
- ```typescript
219
- const controller = new AbortController();
220
- setTimeout(() => controller.abort(), 5000); // Cancel after 5s
221
-
222
- try {
223
- const result = await client.generate({
224
- chainId: 'my-chain',
225
- prompt: 'Long task',
226
- signal: controller.signal,
227
- });
228
- } catch (error) {
229
- if (error instanceof TimeoutError) {
230
- console.log('Request was cancelled');
231
- }
232
- }
233
- ```
234
-
235
- ---
236
-
237
- ## Thinking Mode
238
-
239
- Enable reasoning models to show their thought process:
240
-
241
- ```typescript
242
- const result = await client.generate({
243
- chainId: 'reasoning-chain',
244
- prompt: 'Solve this complex problem step by step',
245
- options: {
246
- thinkingMode: true,
247
- },
248
- });
249
- ```
250
-
251
- ---
252
-
253
- ## Webhooks
254
-
255
- Register callbacks for async notifications:
256
-
257
- ```typescript
258
- // Register
259
- await client.registerWebhook({
260
- requestId: 'req_123',
261
- url: 'https://your-domain.com/webhooks/hydra',
262
- secret: 'your-webhook-secret',
263
- retryCount: 3,
264
- });
265
-
266
- // Manage
267
- const webhooks = await client.listWebhooks();
268
- await client.updateWebhook('webhook_id', { retryCount: 5 });
269
- await client.unregisterWebhook('webhook_id');
270
- ```
271
-
272
- ---
273
-
274
- ## Configuration Reference
275
-
276
- ### Client Options
277
-
278
- | Option | Type | Default | Description |
279
- |--------|------|---------|-------------|
280
- | `secretKey` | `string` | **required** | Your API key (`hyd_sk_...`) |
281
- | `baseUrl` | `string` | `http://localhost:3000` | API base URL |
282
- | `timeout` | `number` | `600000` | Request timeout in ms (10 min) |
283
- | `maxRetries` | `number` | `3` | Max retry attempts |
284
- | `debug` | `boolean` | `false` | Enable debug logging |
285
-
286
- ### Generate Options
287
-
288
- | Option | Type | Default | Description |
289
- |--------|------|---------|-------------|
290
- | `chainId` | `string` | **required** | Fallback chain ID |
291
- | `prompt` | `string` | **required** | The prompt to send |
292
- | `schema` | `object` | - | JSON schema for validation |
293
- | `cacheScope` | `'global' \| 'user' \| 'skip'` | `'global'` | Cache sharing behavior |
294
- | `cacheQuality` | `'STANDARD' \| 'HIGH' \| 'MAX_EFFICIENCY'` | `'STANDARD'` | Cache match precision |
295
- | `strictJson` | `boolean` | `true` | Force strict JSON mode |
296
- | `signal` | `AbortSignal` | - | Cancellation signal |
297
- | `maxRetries` | `number` | Client default | Override retries |
298
- | `requestId` | `string` | Auto-generated | Custom request ID |
299
- | `files` | `FileAttachment[]` | - | File attachments |
300
- | `useOptimized` | `boolean` | `true` | Use 3-step cost-saving flow |
301
- | `noCache` | `boolean` | `false` | Skip cache entirely |
302
-
303
- ---
304
-
305
- ## API Methods
306
-
307
- | Method | Description |
308
- |--------|-------------|
309
- | `generate(options)` | Generate content with fallback chain |
310
- | `generateStream(options, callbacks)` | Stream JSON objects in real-time |
311
- | `generateBatch(chainId, items)` | Process multiple prompts |
312
- | `listModels()` | List available AI models |
313
- | `listChains()` | List your fallback chains |
314
- | `getUsage(options)` | Get usage statistics |
315
- | `health()` | Check API health |
316
- | `healthDetailed()` | Get detailed component health |
317
- | `registerWebhook(config)` | Register async webhook |
318
- | `unregisterWebhook(id)` | Remove a webhook |
319
- | `listWebhooks()` | List all webhooks |
320
- | `updateWebhook(id, updates)` | Modify webhook config |
321
-
322
- ---
323
-
324
- ## Error Handling
325
-
326
- ```typescript
327
- import {
328
- RateLimitError,
329
- TimeoutError,
330
- AuthenticationError,
331
- QuotaExceededError,
332
- WorkerError,
333
- FileProcessingError,
334
- isRetryableError,
335
- } from 'hydra-aidirector';
336
-
337
- try {
338
- const result = await client.generate({ chainId: 'x', prompt: 'y' });
339
- } catch (error) {
340
- if (error instanceof RateLimitError) {
341
- console.log(`Retry after ${error.retryAfterMs}ms`);
342
- } else if (error instanceof QuotaExceededError) {
343
- console.log(`Quota exceeded: ${error.used}/${error.limit} (${error.tier})`);
344
- } else if (error instanceof TimeoutError) {
345
- console.log(`Timed out after ${error.timeoutMs}ms`);
346
- } else if (error instanceof AuthenticationError) {
347
- console.log('Invalid API key');
348
- } else if (error instanceof WorkerError) {
349
- console.log('Worker failed - will retry');
350
- } else if (error instanceof FileProcessingError) {
351
- console.log(`File error: ${error.reason} - ${error.filename}`);
352
- } else if (isRetryableError(error)) {
353
- console.log('Transient error - safe to retry');
354
- }
355
- }
356
- ```
357
-
358
- ---
359
-
360
- ## Pricing
361
-
362
- **BYOK (Bring Your Own Key)** — You pay AI providers directly. Hydra charges only for API access:
363
-
364
- | Tier | Price/mo | Requests | Overage |
365
- |------|----------|----------|---------|
366
- | Free | $0 | 1,000 | Blocked |
367
- | Starter | $9 | 25,000 | $0.50/1K |
368
- | Pro | $29 | 100,000 | $0.40/1K |
369
- | Scale | $79 | 500,000 | $0.30/1K |
370
-
371
- ---
372
-
373
- ## TypeScript Support
374
-
375
- Full type safety with comprehensive types:
376
-
377
- ```typescript
378
- import type {
379
- GenerateOptions,
380
- GenerateResult,
381
- StreamCallbacks,
382
- FileAttachment,
383
- ChainInfo,
384
- ModelInfo,
385
- } from 'hydra-aidirector';
386
- ```
387
-
388
- ---
389
-
390
- ## Requirements
391
-
392
- - Node.js 18+
393
- - TypeScript 5+ (optional but recommended)
394
-
395
- ---
396
-
397
- ## License
398
-
399
- MIT © [Hydra](https://hydrai.dev)
1
+ # hydra-aidirector
2
+
3
+ One client. One secret. One AI gateway.
4
+
5
+ `hydra-aidirector` is the official TypeScript SDK for [Hydra](https://hydrai.dev/docs). It gives your app one clean path for generation, failover chains, structured output, scoped caching, streaming, usage, health, and webhooks without making you glue providers together by hand.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ bun add hydra-aidirector
11
+ ```
12
+
13
+ ```bash
14
+ npm install hydra-aidirector
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```ts
20
+ import { Hydra } from 'hydra-aidirector';
21
+
22
+ const client = new Hydra({
23
+ secretKey: process.env.HYDRA_SECRET_KEY!, // hyd_sk_...
24
+ baseUrl: process.env.HYDRA_BASE_URL!, // https://your-hydra-instance.com
25
+ });
26
+
27
+ const result = await client.generate({
28
+ chainId: 'support-replies',
29
+ prompt: 'Return 3 short onboarding emails as JSON.',
30
+ });
31
+
32
+ if (result.success) {
33
+ console.log(result.data.valid);
34
+ } else {
35
+ console.error(result.error);
36
+ }
37
+ ```
38
+
39
+ ## Why Teams Use It
40
+
41
+ - One typed client for generation, streaming, usage, health, and webhooks.
42
+ - Automatic failover across the models in your chain.
43
+ - Structured output recovery when providers return noisy JSON.
44
+ - Cache controls that let you keep shared prompts fast and sensitive prompts private.
45
+ - One request trail to inspect when latency, cost, or provider behavior starts drifting.
46
+
47
+ ## How Generation Works
48
+
49
+ Hydra uses the optimized 3-step flow by default for simple requests. That path keeps the fast lane lean by moving the long model call out of the request path that fronts your app.
50
+
51
+ When a request needs the full server pipeline, the SDK automatically falls back to the legacy endpoint. That includes:
52
+
53
+ - `schema`
54
+ - `noCache`
55
+ - `cacheScope`
56
+ - `cacheQuality`
57
+ - `strictJson`
58
+ - `maxRetries`
59
+
60
+ You can also force the legacy path yourself:
61
+
62
+ ```ts
63
+ await client.generate({
64
+ chainId: 'support-replies',
65
+ prompt: 'Return a fresh answer',
66
+ useOptimized: false,
67
+ });
68
+ ```
69
+
70
+ ## Structured Output
71
+
72
+ Use `schema` when the response shape matters:
73
+
74
+ ```ts
75
+ const result = await client.generate({
76
+ chainId: 'invoice-parser',
77
+ prompt: 'Extract the invoice fields.',
78
+ schema: {
79
+ invoiceNumber: 'string',
80
+ amount: 'number',
81
+ currency: 'string',
82
+ },
83
+ strictJson: true,
84
+ });
85
+ ```
86
+
87
+ If the provider comes back with malformed JSON, Hydra will still try to recover the payload before the request fails.
88
+
89
+ ## Cache Control
90
+
91
+ Use request-level cache controls when freshness or privacy matters:
92
+
93
+ ```ts
94
+ await client.generate({
95
+ chainId: 'account-summary',
96
+ prompt: 'Summarize this customer account.',
97
+ cacheScope: 'user',
98
+ });
99
+
100
+ await client.generate({
101
+ chainId: 'pricing-faq',
102
+ prompt: 'Return the current pricing summary.',
103
+ cacheScope: 'global',
104
+ });
105
+
106
+ await client.generate({
107
+ chainId: 'one-off',
108
+ prompt: 'Generate a fresh variant.',
109
+ noCache: true,
110
+ });
111
+ ```
112
+
113
+ Tune cache matching when you want better reuse:
114
+
115
+ ```ts
116
+ await client.generate({
117
+ chainId: 'reporting',
118
+ prompt: 'Generate the weekly report.',
119
+ cacheQuality: 'HIGH',
120
+ });
121
+ ```
122
+
123
+ ## Streaming
124
+
125
+ Stream parsed objects as they arrive:
126
+
127
+ ```ts
128
+ await client.generateStream(
129
+ {
130
+ chainId: 'catalog-generator',
131
+ prompt: 'Generate 50 product blurbs as JSON.',
132
+ },
133
+ {
134
+ onObject: (object, index) => {
135
+ console.log(index, object);
136
+ },
137
+ onComplete: (result) => {
138
+ console.log(result.objectCount);
139
+ },
140
+ onError: (error) => {
141
+ console.error(error);
142
+ },
143
+ }
144
+ );
145
+ ```
146
+
147
+ ## Batch Generation
148
+
149
+ Run multiple prompts in one call:
150
+
151
+ ```ts
152
+ const batch = await client.generateBatch('catalog-generator', [
153
+ { id: 'a', prompt: 'Describe product A' },
154
+ { id: 'b', prompt: 'Describe product B' },
155
+ { id: 'c', prompt: 'Describe product C' },
156
+ ]);
157
+
158
+ console.log(batch.summary);
159
+ ```
160
+
161
+ ## File Attachments
162
+
163
+ Send files as base64 and let Hydra handle the processing path:
164
+
165
+ ```ts
166
+ import fs from 'node:fs';
167
+
168
+ const file = fs.readFileSync('report.pdf');
169
+
170
+ const result = await client.generate({
171
+ chainId: 'document-analysis',
172
+ prompt: 'Summarize this report.',
173
+ files: [
174
+ {
175
+ data: file.toString('base64'),
176
+ filename: 'report.pdf',
177
+ mimeType: 'application/pdf',
178
+ },
179
+ ],
180
+ });
181
+ ```
182
+
183
+ ## Usage And Health
184
+
185
+ Get normalized usage metrics:
186
+
187
+ ```ts
188
+ const usage = await client.getUsage({
189
+ startDate: new Date('2026-03-01T00:00:00.000Z'),
190
+ endDate: new Date('2026-04-01T00:00:00.000Z'),
191
+ });
192
+
193
+ console.log(usage.totalRequests);
194
+ console.log(usage.cachedRequests);
195
+ console.log(usage.byModelList);
196
+ ```
197
+
198
+ Basic health:
199
+
200
+ ```ts
201
+ const health = await client.health();
202
+ console.log(health.ok, health.latencyMs, health.version);
203
+ ```
204
+
205
+ Detailed health requires an admin key:
206
+
207
+ ```ts
208
+ const detailed = await client.healthDetailed();
209
+ console.log(detailed.status);
210
+ ```
211
+
212
+ ## Webhooks
213
+
214
+ Register, list, update, and remove webhooks from the SDK:
215
+
216
+ ```ts
217
+ await client.registerWebhook({
218
+ requestId: 'req_123',
219
+ url: 'https://example.com/webhooks/hydra',
220
+ secret: 'super-secret-webhook-key',
221
+ retryCount: 3,
222
+ });
223
+
224
+ const webhooks = await client.listWebhooks();
225
+
226
+ await client.updateWebhook(webhooks[0].id, {
227
+ retryCount: 5,
228
+ });
229
+
230
+ await client.unregisterWebhook(webhooks[0].id);
231
+ ```
232
+
233
+ ## Cancellation
234
+
235
+ Cancel long-running requests with `AbortController`:
236
+
237
+ ```ts
238
+ const controller = new AbortController();
239
+
240
+ setTimeout(() => controller.abort(), 5000);
241
+
242
+ await client.generate({
243
+ chainId: 'long-job',
244
+ prompt: 'Generate a long report.',
245
+ signal: controller.signal,
246
+ });
247
+ ```
248
+
249
+ ## Error Handling
250
+
251
+ ```ts
252
+ import {
253
+ AuthenticationError,
254
+ QuotaExceededError,
255
+ RateLimitError,
256
+ TimeoutError,
257
+ isRetryableError,
258
+ } from 'hydra-aidirector';
259
+
260
+ try {
261
+ await client.generate({
262
+ chainId: 'support-replies',
263
+ prompt: 'Write a response.',
264
+ });
265
+ } catch (error) {
266
+ if (error instanceof AuthenticationError) {
267
+ console.error('Your Hydra secret key is invalid.');
268
+ } else if (error instanceof QuotaExceededError) {
269
+ console.error(error.tier, error.used, error.limit);
270
+ } else if (error instanceof RateLimitError) {
271
+ console.error(error.retryAfterMs);
272
+ } else if (error instanceof TimeoutError) {
273
+ console.error(error.timeoutMs);
274
+ } else if (isRetryableError(error)) {
275
+ console.error('Safe to retry.');
276
+ }
277
+ }
278
+ ```
279
+
280
+ ## API Surface
281
+
282
+ - `generate(options)`
283
+ - `generateStream(options, callbacks)`
284
+ - `generateBatch(chainId, items, options?)`
285
+ - `listModels()`
286
+ - `listChains()`
287
+ - `getUsage(options?)`
288
+ - `health()`
289
+ - `healthDetailed()`
290
+ - `registerWebhook(config)`
291
+ - `listWebhooks()`
292
+ - `updateWebhook(id, updates)`
293
+ - `unregisterWebhook(id)`
294
+ - `withConfig(overrides)`
295
+
296
+ ## Requirements
297
+
298
+ - Node.js 18+
299
+ - Bun 1.0+ or another runtime with `fetch`
300
+ - TypeScript 5+ for typed projects
301
+
302
+ ## Get Started
303
+
304
+ - Docs: [hydrai.dev/docs](https://hydrai.dev/docs)
305
+ - Dashboard: [hydrai.dev/signin](https://hydrai.dev/signin)
306
+ - Contact: [hydrai.dev/contact](https://hydrai.dev/contact)
307
+
308
+ ## License
309
+
310
+ MIT