hydra-aidirector 1.6.0 → 1.6.1
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 +304 -399
- package/dist/index.d.mts +67 -26
- package/dist/index.d.ts +67 -26
- package/dist/index.js +4 -4
- package/dist/index.mjs +4 -4
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,399 +1,304 @@
|
|
|
1
|
-
# hydra-aidirector
|
|
2
|
-
|
|
3
|
-
The official
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
await client.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
await client.generate({
|
|
172
|
-
chainId: '
|
|
173
|
-
prompt: '
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
+
The official TypeScript SDK for [Hydra](https://hydrai.dev).
|
|
4
|
+
|
|
5
|
+
Hydra gives your app one stable AI gateway instead of a pile of provider glue. You send one request. Hydra handles routing, failover, cache behavior, JSON recovery, and operational visibility behind the same API.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun add hydra-aidirector
|
|
11
|
+
# or
|
|
12
|
+
npm install hydra-aidirector
|
|
13
|
+
# or
|
|
14
|
+
pnpm add hydra-aidirector
|
|
15
|
+
# or
|
|
16
|
+
yarn add hydra-aidirector
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { Hydra } from 'hydra-aidirector';
|
|
23
|
+
|
|
24
|
+
const client = new Hydra({
|
|
25
|
+
secretKey: process.env.HYDRA_SECRET_KEY!, // hyd_sk_...
|
|
26
|
+
baseUrl: 'https://your-hydra-instance.com',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const result = await client.generate({
|
|
30
|
+
chainId: 'support-replies',
|
|
31
|
+
prompt: 'Write 3 concise onboarding emails as JSON',
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (result.success) {
|
|
35
|
+
console.log(result.data.valid);
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## What You Get
|
|
40
|
+
|
|
41
|
+
- Automatic failover across the models in your chain.
|
|
42
|
+
- JSON extraction and repair when providers return noisy output.
|
|
43
|
+
- User-safe caching with request-level cache controls.
|
|
44
|
+
- Streaming for long responses.
|
|
45
|
+
- Batch generation for parallel prompt execution.
|
|
46
|
+
- Usage, health, and webhook management from the same client.
|
|
47
|
+
|
|
48
|
+
## Generation Modes
|
|
49
|
+
|
|
50
|
+
Hydra uses the optimized 3-step flow by default for simple requests. That keeps the fast path cheap and responsive.
|
|
51
|
+
|
|
52
|
+
When you ask for features that require the full server pipeline, the SDK automatically falls back to the legacy endpoint. That includes:
|
|
53
|
+
|
|
54
|
+
- `schema`
|
|
55
|
+
- `noCache`
|
|
56
|
+
- `cacheScope`
|
|
57
|
+
- `cacheQuality`
|
|
58
|
+
- `strictJson`
|
|
59
|
+
- `maxRetries`
|
|
60
|
+
|
|
61
|
+
You can also force the legacy endpoint yourself:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
await client.generate({
|
|
65
|
+
chainId: 'support-replies',
|
|
66
|
+
prompt: 'Return a fresh answer',
|
|
67
|
+
useOptimized: false,
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Structured Output
|
|
72
|
+
|
|
73
|
+
Use `schema` when you want validated JSON back:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const result = await client.generate({
|
|
77
|
+
chainId: 'invoice-parser',
|
|
78
|
+
prompt: 'Extract invoice fields',
|
|
79
|
+
schema: {
|
|
80
|
+
invoiceNumber: 'string',
|
|
81
|
+
amount: 'number',
|
|
82
|
+
currency: 'string',
|
|
83
|
+
},
|
|
84
|
+
strictJson: true,
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
If the provider returns malformed JSON, Hydra will still try to recover it before the request fails.
|
|
89
|
+
|
|
90
|
+
## Cache Control
|
|
91
|
+
|
|
92
|
+
Use request-level cache controls when the prompt should behave differently from the default path:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
await client.generate({
|
|
96
|
+
chainId: 'account-summary',
|
|
97
|
+
prompt: 'Summarize this customer account',
|
|
98
|
+
cacheScope: 'user',
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
await client.generate({
|
|
102
|
+
chainId: 'pricing-faq',
|
|
103
|
+
prompt: 'Return the current pricing summary',
|
|
104
|
+
cacheScope: 'global',
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
await client.generate({
|
|
108
|
+
chainId: 'one-off',
|
|
109
|
+
prompt: 'Generate a fresh variant',
|
|
110
|
+
noCache: true,
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
You can also tune cache matching:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
await client.generate({
|
|
118
|
+
chainId: 'reporting',
|
|
119
|
+
prompt: 'Generate the weekly report',
|
|
120
|
+
cacheQuality: 'HIGH',
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Streaming
|
|
125
|
+
|
|
126
|
+
Stream parsed objects as they arrive:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
await client.generateStream(
|
|
130
|
+
{
|
|
131
|
+
chainId: 'catalog-generator',
|
|
132
|
+
prompt: 'Generate 50 product blurbs as JSON',
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
onObject: (object, index) => {
|
|
136
|
+
console.log(index, object);
|
|
137
|
+
},
|
|
138
|
+
onComplete: (result) => {
|
|
139
|
+
console.log(result.objectCount);
|
|
140
|
+
},
|
|
141
|
+
onError: (error) => {
|
|
142
|
+
console.error(error);
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Batch Generation
|
|
149
|
+
|
|
150
|
+
Run multiple prompts in one call:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
const batch = await client.generateBatch('catalog-generator', [
|
|
154
|
+
{ id: 'a', prompt: 'Describe product A' },
|
|
155
|
+
{ id: 'b', prompt: 'Describe product B' },
|
|
156
|
+
{ id: 'c', prompt: 'Describe product C' },
|
|
157
|
+
]);
|
|
158
|
+
|
|
159
|
+
console.log(batch.summary);
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Files
|
|
163
|
+
|
|
164
|
+
Send file attachments as base64:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import fs from 'node:fs';
|
|
168
|
+
|
|
169
|
+
const file = fs.readFileSync('report.pdf');
|
|
170
|
+
|
|
171
|
+
const result = await client.generate({
|
|
172
|
+
chainId: 'document-analysis',
|
|
173
|
+
prompt: 'Summarize this report',
|
|
174
|
+
files: [
|
|
175
|
+
{
|
|
176
|
+
data: file.toString('base64'),
|
|
177
|
+
filename: 'report.pdf',
|
|
178
|
+
mimeType: 'application/pdf',
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Usage and Health
|
|
185
|
+
|
|
186
|
+
Get normalized usage metrics:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
const usage = await client.getUsage({
|
|
190
|
+
startDate: new Date('2026-03-01T00:00:00.000Z'),
|
|
191
|
+
endDate: new Date('2026-04-01T00:00:00.000Z'),
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
console.log(usage.totalRequests);
|
|
195
|
+
console.log(usage.cachedRequests);
|
|
196
|
+
console.log(usage.byModelList);
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Basic health:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
const health = await client.health();
|
|
203
|
+
console.log(health.ok, health.latencyMs);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Detailed health requires an admin key:
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
const detailed = await client.healthDetailed();
|
|
210
|
+
console.log(detailed.status);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Webhooks
|
|
214
|
+
|
|
215
|
+
Register, list, update, and remove webhooks from the SDK:
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
await client.registerWebhook({
|
|
219
|
+
requestId: 'req_123',
|
|
220
|
+
url: 'https://example.com/webhooks/hydra',
|
|
221
|
+
secret: 'super-secret-webhook-key',
|
|
222
|
+
retryCount: 3,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const webhooks = await client.listWebhooks();
|
|
226
|
+
|
|
227
|
+
await client.updateWebhook(webhooks[0].id, {
|
|
228
|
+
retryCount: 5,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
await client.unregisterWebhook(webhooks[0].id);
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Cancellation
|
|
235
|
+
|
|
236
|
+
Cancel long-running requests with `AbortController`:
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
const controller = new AbortController();
|
|
240
|
+
|
|
241
|
+
setTimeout(() => controller.abort(), 5000);
|
|
242
|
+
|
|
243
|
+
await client.generate({
|
|
244
|
+
chainId: 'long-job',
|
|
245
|
+
prompt: 'Generate a long report',
|
|
246
|
+
signal: controller.signal,
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Error Handling
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
import {
|
|
254
|
+
AuthenticationError,
|
|
255
|
+
QuotaExceededError,
|
|
256
|
+
RateLimitError,
|
|
257
|
+
TimeoutError,
|
|
258
|
+
isRetryableError,
|
|
259
|
+
} from 'hydra-aidirector';
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
await client.generate({
|
|
263
|
+
chainId: 'support-replies',
|
|
264
|
+
prompt: 'Write a response',
|
|
265
|
+
});
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if (error instanceof AuthenticationError) {
|
|
268
|
+
console.error('The Hydra secret key is invalid.');
|
|
269
|
+
} else if (error instanceof QuotaExceededError) {
|
|
270
|
+
console.error(error.tier, error.used, error.limit);
|
|
271
|
+
} else if (error instanceof RateLimitError) {
|
|
272
|
+
console.error(error.retryAfterMs);
|
|
273
|
+
} else if (error instanceof TimeoutError) {
|
|
274
|
+
console.error(error.timeoutMs);
|
|
275
|
+
} else if (isRetryableError(error)) {
|
|
276
|
+
console.error('Safe to retry.');
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## API Surface
|
|
282
|
+
|
|
283
|
+
- `generate(options)`
|
|
284
|
+
- `generateStream(options, callbacks)`
|
|
285
|
+
- `generateBatch(chainId, items, options?)`
|
|
286
|
+
- `listModels()`
|
|
287
|
+
- `listChains()`
|
|
288
|
+
- `getUsage(options?)`
|
|
289
|
+
- `health()`
|
|
290
|
+
- `healthDetailed()`
|
|
291
|
+
- `registerWebhook(config)`
|
|
292
|
+
- `listWebhooks()`
|
|
293
|
+
- `updateWebhook(id, updates)`
|
|
294
|
+
- `unregisterWebhook(id)`
|
|
295
|
+
- `withConfig(overrides)`
|
|
296
|
+
|
|
297
|
+
## Requirements
|
|
298
|
+
|
|
299
|
+
- Node.js 18+
|
|
300
|
+
- TypeScript 5+ for typed projects
|
|
301
|
+
|
|
302
|
+
## License
|
|
303
|
+
|
|
304
|
+
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -69,7 +69,7 @@ interface GenerateOptions {
|
|
|
69
69
|
* The server automatically handles:
|
|
70
70
|
* - File type detection (magic bytes)
|
|
71
71
|
* - Model compatibility checking
|
|
72
|
-
* - Automatic conversion (
|
|
72
|
+
* - Automatic conversion (for example, DOCX to PDF)
|
|
73
73
|
*
|
|
74
74
|
* @example
|
|
75
75
|
* ```typescript
|
|
@@ -94,10 +94,9 @@ interface GenerateOptions {
|
|
|
94
94
|
useOptimized?: boolean;
|
|
95
95
|
/**
|
|
96
96
|
* Cache scope for this request.
|
|
97
|
-
* - 'global': Cache is shared across all users (default)
|
|
98
97
|
* - 'user': Cache is scoped to the authenticated user
|
|
98
|
+
* - 'global': Cache is shared across users
|
|
99
99
|
* - 'skip': Do not cache this response
|
|
100
|
-
* @default 'global'
|
|
101
100
|
*/
|
|
102
101
|
cacheScope?: 'global' | 'user' | 'skip';
|
|
103
102
|
/**
|
|
@@ -113,7 +112,7 @@ interface GenerateOptions {
|
|
|
113
112
|
signal?: AbortSignal;
|
|
114
113
|
/**
|
|
115
114
|
* Override max retries for this specific request.
|
|
116
|
-
* Set to 0 to disable retries for
|
|
115
|
+
* Set to 0 to disable retries for this call.
|
|
117
116
|
*/
|
|
118
117
|
maxRetries?: number;
|
|
119
118
|
/**
|
|
@@ -125,12 +124,13 @@ interface GenerateOptions {
|
|
|
125
124
|
cacheQuality?: 'STANDARD' | 'HIGH' | 'MAX_EFFICIENCY';
|
|
126
125
|
/**
|
|
127
126
|
* Force strict JSON usage for providers that support it.
|
|
128
|
-
*
|
|
127
|
+
* When enabled with a schema, the SDK uses the legacy endpoint so
|
|
128
|
+
* validation and repair behave consistently.
|
|
129
129
|
*/
|
|
130
130
|
strictJson?: boolean;
|
|
131
131
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
132
|
+
* Optional client-side correlation id.
|
|
133
|
+
* Reserved for compatibility with custom tracing layers.
|
|
134
134
|
*/
|
|
135
135
|
requestId?: string;
|
|
136
136
|
/**
|
|
@@ -184,9 +184,8 @@ interface GenerationParameters {
|
|
|
184
184
|
*/
|
|
185
185
|
systemPrompt?: string;
|
|
186
186
|
/**
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
* Only works with models that support thinking (e.g., Gemini 2.0 Flash Thinking).
|
|
187
|
+
* Reserved for reasoning-mode support on deployments that expose it.
|
|
188
|
+
* Deployments that do not support this flag may ignore it.
|
|
190
189
|
*/
|
|
191
190
|
thinkingMode?: boolean;
|
|
192
191
|
}
|
|
@@ -504,22 +503,68 @@ interface UsageStats {
|
|
|
504
503
|
/**
|
|
505
504
|
* Cached responses served
|
|
506
505
|
*/
|
|
506
|
+
cachedRequests: number;
|
|
507
|
+
/**
|
|
508
|
+
* Legacy alias for cachedRequests
|
|
509
|
+
*/
|
|
507
510
|
cachedResponses: number;
|
|
511
|
+
/**
|
|
512
|
+
* Total input tokens in the selected range
|
|
513
|
+
*/
|
|
514
|
+
totalTokensIn: number;
|
|
515
|
+
/**
|
|
516
|
+
* Total output tokens in the selected range
|
|
517
|
+
*/
|
|
518
|
+
totalTokensOut: number;
|
|
508
519
|
/**
|
|
509
520
|
* Total tokens used
|
|
510
521
|
*/
|
|
511
522
|
totalTokens: TokenUsage;
|
|
512
523
|
/**
|
|
513
|
-
*
|
|
524
|
+
* Cache hit rate as a percentage
|
|
525
|
+
*/
|
|
526
|
+
cacheHitRate: number;
|
|
527
|
+
/**
|
|
528
|
+
* Usage by model keyed by model id
|
|
514
529
|
*/
|
|
515
530
|
byModel: Record<string, {
|
|
516
531
|
requests: number;
|
|
517
532
|
tokens: TokenUsage;
|
|
533
|
+
tokensIn: number;
|
|
534
|
+
tokensOut: number;
|
|
535
|
+
}>;
|
|
536
|
+
/**
|
|
537
|
+
* Usage by model in API response order
|
|
538
|
+
*/
|
|
539
|
+
byModelList: Array<{
|
|
540
|
+
modelId: string;
|
|
541
|
+
requests: number;
|
|
542
|
+
tokensIn: number;
|
|
543
|
+
tokensOut: number;
|
|
544
|
+
}>;
|
|
545
|
+
/**
|
|
546
|
+
* Daily rollup for charts
|
|
547
|
+
*/
|
|
548
|
+
byDay: Array<{
|
|
549
|
+
date: string;
|
|
550
|
+
requests: number;
|
|
551
|
+
tokens: number;
|
|
518
552
|
}>;
|
|
519
553
|
/**
|
|
520
554
|
* Average latency in milliseconds
|
|
521
555
|
*/
|
|
556
|
+
averageLatencyMs: number;
|
|
557
|
+
/**
|
|
558
|
+
* Legacy alias for averageLatencyMs
|
|
559
|
+
*/
|
|
522
560
|
avgLatencyMs: number;
|
|
561
|
+
/**
|
|
562
|
+
* Date range for the returned stats
|
|
563
|
+
*/
|
|
564
|
+
dateRange: {
|
|
565
|
+
start: string;
|
|
566
|
+
end: string;
|
|
567
|
+
};
|
|
523
568
|
/**
|
|
524
569
|
* Period start date
|
|
525
570
|
*/
|
|
@@ -641,11 +686,11 @@ interface WebhookConfig {
|
|
|
641
686
|
* Use in Next.js API routes, Server Actions, or any Node.js/Edge environment.
|
|
642
687
|
*
|
|
643
688
|
* Features:
|
|
644
|
-
* -
|
|
645
|
-
* -
|
|
646
|
-
* -
|
|
647
|
-
* -
|
|
648
|
-
* -
|
|
689
|
+
* - HMAC authentication with browser support
|
|
690
|
+
* - Configurable timeout (default 10 minutes)
|
|
691
|
+
* - Automatic retries with exponential backoff
|
|
692
|
+
* - Structured error handling
|
|
693
|
+
* - Full TypeScript support
|
|
649
694
|
*
|
|
650
695
|
* @example
|
|
651
696
|
* ```typescript
|
|
@@ -674,6 +719,7 @@ declare class Hydra {
|
|
|
674
719
|
private readonly maxRetries;
|
|
675
720
|
private readonly keyPrefix;
|
|
676
721
|
private readonly debug;
|
|
722
|
+
private static readonly LEGACY_ONLY_OPTION_LABELS;
|
|
677
723
|
constructor(config: HydraConfig);
|
|
678
724
|
/**
|
|
679
725
|
* Generate content using your fallback chain
|
|
@@ -692,11 +738,13 @@ declare class Hydra {
|
|
|
692
738
|
* ```
|
|
693
739
|
*/
|
|
694
740
|
generate(options: GenerateOptions): Promise<GenerateResult>;
|
|
741
|
+
private shouldUseOptimizedFlow;
|
|
742
|
+
private getLegacyFallbackReason;
|
|
695
743
|
/**
|
|
696
744
|
* Optimized 3-step generation (minimizes Vercel compute costs)
|
|
697
745
|
*
|
|
698
746
|
* Step 1: Get token from Vercel (~50ms)
|
|
699
|
-
* Step 2: Call
|
|
747
|
+
* Step 2: Call the Cloudflare Worker directly
|
|
700
748
|
* Step 3: Cache result in Vercel (~50ms)
|
|
701
749
|
*
|
|
702
750
|
* Total Vercel time: ~100ms vs 30-120s in legacy mode
|
|
@@ -827,13 +875,7 @@ declare class Hydra {
|
|
|
827
875
|
*
|
|
828
876
|
* @returns Detailed health status
|
|
829
877
|
*/
|
|
830
|
-
healthDetailed(): Promise<
|
|
831
|
-
status: 'healthy' | 'degraded' | 'unhealthy';
|
|
832
|
-
timestamp: string;
|
|
833
|
-
uptime: number;
|
|
834
|
-
components: Record<string, unknown>;
|
|
835
|
-
metrics: Record<string, unknown>;
|
|
836
|
-
}>;
|
|
878
|
+
healthDetailed(): Promise<DetailedHealthResult>;
|
|
837
879
|
/**
|
|
838
880
|
* Generate content for multiple prompts in a single request
|
|
839
881
|
*
|
|
@@ -1024,8 +1066,7 @@ declare function isRetryableError(error: unknown): boolean;
|
|
|
1024
1066
|
*
|
|
1025
1067
|
* Automatically detects environment and uses appropriate implementation.
|
|
1026
1068
|
*
|
|
1027
|
-
*
|
|
1028
|
-
* This allows the server to verify using the stored hash (it never sees the full key).
|
|
1069
|
+
* Uses the raw secret key as the HMAC key.
|
|
1029
1070
|
*/
|
|
1030
1071
|
declare function generateSignature(secretKey: string, method: string, path: string, body: string, timestamp: number): Promise<string>;
|
|
1031
1072
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -69,7 +69,7 @@ interface GenerateOptions {
|
|
|
69
69
|
* The server automatically handles:
|
|
70
70
|
* - File type detection (magic bytes)
|
|
71
71
|
* - Model compatibility checking
|
|
72
|
-
* - Automatic conversion (
|
|
72
|
+
* - Automatic conversion (for example, DOCX to PDF)
|
|
73
73
|
*
|
|
74
74
|
* @example
|
|
75
75
|
* ```typescript
|
|
@@ -94,10 +94,9 @@ interface GenerateOptions {
|
|
|
94
94
|
useOptimized?: boolean;
|
|
95
95
|
/**
|
|
96
96
|
* Cache scope for this request.
|
|
97
|
-
* - 'global': Cache is shared across all users (default)
|
|
98
97
|
* - 'user': Cache is scoped to the authenticated user
|
|
98
|
+
* - 'global': Cache is shared across users
|
|
99
99
|
* - 'skip': Do not cache this response
|
|
100
|
-
* @default 'global'
|
|
101
100
|
*/
|
|
102
101
|
cacheScope?: 'global' | 'user' | 'skip';
|
|
103
102
|
/**
|
|
@@ -113,7 +112,7 @@ interface GenerateOptions {
|
|
|
113
112
|
signal?: AbortSignal;
|
|
114
113
|
/**
|
|
115
114
|
* Override max retries for this specific request.
|
|
116
|
-
* Set to 0 to disable retries for
|
|
115
|
+
* Set to 0 to disable retries for this call.
|
|
117
116
|
*/
|
|
118
117
|
maxRetries?: number;
|
|
119
118
|
/**
|
|
@@ -125,12 +124,13 @@ interface GenerateOptions {
|
|
|
125
124
|
cacheQuality?: 'STANDARD' | 'HIGH' | 'MAX_EFFICIENCY';
|
|
126
125
|
/**
|
|
127
126
|
* Force strict JSON usage for providers that support it.
|
|
128
|
-
*
|
|
127
|
+
* When enabled with a schema, the SDK uses the legacy endpoint so
|
|
128
|
+
* validation and repair behave consistently.
|
|
129
129
|
*/
|
|
130
130
|
strictJson?: boolean;
|
|
131
131
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
132
|
+
* Optional client-side correlation id.
|
|
133
|
+
* Reserved for compatibility with custom tracing layers.
|
|
134
134
|
*/
|
|
135
135
|
requestId?: string;
|
|
136
136
|
/**
|
|
@@ -184,9 +184,8 @@ interface GenerationParameters {
|
|
|
184
184
|
*/
|
|
185
185
|
systemPrompt?: string;
|
|
186
186
|
/**
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
* Only works with models that support thinking (e.g., Gemini 2.0 Flash Thinking).
|
|
187
|
+
* Reserved for reasoning-mode support on deployments that expose it.
|
|
188
|
+
* Deployments that do not support this flag may ignore it.
|
|
190
189
|
*/
|
|
191
190
|
thinkingMode?: boolean;
|
|
192
191
|
}
|
|
@@ -504,22 +503,68 @@ interface UsageStats {
|
|
|
504
503
|
/**
|
|
505
504
|
* Cached responses served
|
|
506
505
|
*/
|
|
506
|
+
cachedRequests: number;
|
|
507
|
+
/**
|
|
508
|
+
* Legacy alias for cachedRequests
|
|
509
|
+
*/
|
|
507
510
|
cachedResponses: number;
|
|
511
|
+
/**
|
|
512
|
+
* Total input tokens in the selected range
|
|
513
|
+
*/
|
|
514
|
+
totalTokensIn: number;
|
|
515
|
+
/**
|
|
516
|
+
* Total output tokens in the selected range
|
|
517
|
+
*/
|
|
518
|
+
totalTokensOut: number;
|
|
508
519
|
/**
|
|
509
520
|
* Total tokens used
|
|
510
521
|
*/
|
|
511
522
|
totalTokens: TokenUsage;
|
|
512
523
|
/**
|
|
513
|
-
*
|
|
524
|
+
* Cache hit rate as a percentage
|
|
525
|
+
*/
|
|
526
|
+
cacheHitRate: number;
|
|
527
|
+
/**
|
|
528
|
+
* Usage by model keyed by model id
|
|
514
529
|
*/
|
|
515
530
|
byModel: Record<string, {
|
|
516
531
|
requests: number;
|
|
517
532
|
tokens: TokenUsage;
|
|
533
|
+
tokensIn: number;
|
|
534
|
+
tokensOut: number;
|
|
535
|
+
}>;
|
|
536
|
+
/**
|
|
537
|
+
* Usage by model in API response order
|
|
538
|
+
*/
|
|
539
|
+
byModelList: Array<{
|
|
540
|
+
modelId: string;
|
|
541
|
+
requests: number;
|
|
542
|
+
tokensIn: number;
|
|
543
|
+
tokensOut: number;
|
|
544
|
+
}>;
|
|
545
|
+
/**
|
|
546
|
+
* Daily rollup for charts
|
|
547
|
+
*/
|
|
548
|
+
byDay: Array<{
|
|
549
|
+
date: string;
|
|
550
|
+
requests: number;
|
|
551
|
+
tokens: number;
|
|
518
552
|
}>;
|
|
519
553
|
/**
|
|
520
554
|
* Average latency in milliseconds
|
|
521
555
|
*/
|
|
556
|
+
averageLatencyMs: number;
|
|
557
|
+
/**
|
|
558
|
+
* Legacy alias for averageLatencyMs
|
|
559
|
+
*/
|
|
522
560
|
avgLatencyMs: number;
|
|
561
|
+
/**
|
|
562
|
+
* Date range for the returned stats
|
|
563
|
+
*/
|
|
564
|
+
dateRange: {
|
|
565
|
+
start: string;
|
|
566
|
+
end: string;
|
|
567
|
+
};
|
|
523
568
|
/**
|
|
524
569
|
* Period start date
|
|
525
570
|
*/
|
|
@@ -641,11 +686,11 @@ interface WebhookConfig {
|
|
|
641
686
|
* Use in Next.js API routes, Server Actions, or any Node.js/Edge environment.
|
|
642
687
|
*
|
|
643
688
|
* Features:
|
|
644
|
-
* -
|
|
645
|
-
* -
|
|
646
|
-
* -
|
|
647
|
-
* -
|
|
648
|
-
* -
|
|
689
|
+
* - HMAC authentication with browser support
|
|
690
|
+
* - Configurable timeout (default 10 minutes)
|
|
691
|
+
* - Automatic retries with exponential backoff
|
|
692
|
+
* - Structured error handling
|
|
693
|
+
* - Full TypeScript support
|
|
649
694
|
*
|
|
650
695
|
* @example
|
|
651
696
|
* ```typescript
|
|
@@ -674,6 +719,7 @@ declare class Hydra {
|
|
|
674
719
|
private readonly maxRetries;
|
|
675
720
|
private readonly keyPrefix;
|
|
676
721
|
private readonly debug;
|
|
722
|
+
private static readonly LEGACY_ONLY_OPTION_LABELS;
|
|
677
723
|
constructor(config: HydraConfig);
|
|
678
724
|
/**
|
|
679
725
|
* Generate content using your fallback chain
|
|
@@ -692,11 +738,13 @@ declare class Hydra {
|
|
|
692
738
|
* ```
|
|
693
739
|
*/
|
|
694
740
|
generate(options: GenerateOptions): Promise<GenerateResult>;
|
|
741
|
+
private shouldUseOptimizedFlow;
|
|
742
|
+
private getLegacyFallbackReason;
|
|
695
743
|
/**
|
|
696
744
|
* Optimized 3-step generation (minimizes Vercel compute costs)
|
|
697
745
|
*
|
|
698
746
|
* Step 1: Get token from Vercel (~50ms)
|
|
699
|
-
* Step 2: Call
|
|
747
|
+
* Step 2: Call the Cloudflare Worker directly
|
|
700
748
|
* Step 3: Cache result in Vercel (~50ms)
|
|
701
749
|
*
|
|
702
750
|
* Total Vercel time: ~100ms vs 30-120s in legacy mode
|
|
@@ -827,13 +875,7 @@ declare class Hydra {
|
|
|
827
875
|
*
|
|
828
876
|
* @returns Detailed health status
|
|
829
877
|
*/
|
|
830
|
-
healthDetailed(): Promise<
|
|
831
|
-
status: 'healthy' | 'degraded' | 'unhealthy';
|
|
832
|
-
timestamp: string;
|
|
833
|
-
uptime: number;
|
|
834
|
-
components: Record<string, unknown>;
|
|
835
|
-
metrics: Record<string, unknown>;
|
|
836
|
-
}>;
|
|
878
|
+
healthDetailed(): Promise<DetailedHealthResult>;
|
|
837
879
|
/**
|
|
838
880
|
* Generate content for multiple prompts in a single request
|
|
839
881
|
*
|
|
@@ -1024,8 +1066,7 @@ declare function isRetryableError(error: unknown): boolean;
|
|
|
1024
1066
|
*
|
|
1025
1067
|
* Automatically detects environment and uses appropriate implementation.
|
|
1026
1068
|
*
|
|
1027
|
-
*
|
|
1028
|
-
* This allows the server to verify using the stored hash (it never sees the full key).
|
|
1069
|
+
* Uses the raw secret key as the HMAC key.
|
|
1029
1070
|
*/
|
|
1030
1071
|
declare function generateSignature(secretKey: string, method: string, path: string, body: string, timestamp: number): Promise<string>;
|
|
1031
1072
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
'use strict';var
|
|
2
|
-
`)
|
|
3
|
-
`),d=await
|
|
4
|
-
`);
|
|
1
|
+
'use strict';var G=typeof process<"u"&&process.versions?.node;async function H(i,e,t,r,a){let{createHmac:s}=await import('crypto'),o=[e.toUpperCase(),t,a.toString(),r].join(`
|
|
2
|
+
`);return s("sha256",i).update(o).digest("hex")}async function j(i,e,t,r,a){let s=new TextEncoder,o=[e.toUpperCase(),t,a.toString(),r].join(`
|
|
3
|
+
`),d=await crypto.subtle.importKey("raw",s.encode(i),{name:"HMAC",hash:"SHA-256"},false,["sign"]),n=await crypto.subtle.sign("HMAC",d,s.encode(o));return Array.from(new Uint8Array(n)).map(c=>c.toString(16).padStart(2,"0")).join("")}async function A(i,e,t,r,a){return G?H(i,e,t,r,a):j(i,e,t,r,a)}function I(i){return i.slice(0,12)}function C(i){return typeof i=="string"&&i.startsWith("hyd_sk_")&&i.length>=20}var m=class extends Error{constructor(e,t,r){super(e),this.name="HydraError",this.code=t,this.retryable=r?.retryable??false,this.statusCode=r?.statusCode,this.originalError=r?.cause;}},w=class extends m{constructor(e){super(e,"CONFIGURATION_ERROR",{retryable:false}),this.name="ConfigurationError";}},S=class extends m{constructor(e,t){super(e,"AUTH_ERROR",{retryable:false,statusCode:t}),this.name="AuthenticationError";}},E=class extends m{constructor(e,t=6e4){super(e,"RATE_LIMITED",{retryable:true,statusCode:429}),this.name="RateLimitError",this.retryAfterMs=t;}},R=class extends m{constructor(e){super(`Request timed out after ${e}ms`,"TIMEOUT",{retryable:true}),this.name="TimeoutError",this.timeoutMs=e;}},k=class extends m{constructor(e,t){super(e,"NETWORK_ERROR",{retryable:true,cause:t}),this.name="NetworkError";}},b=class extends m{constructor(e,t=[]){super(e,"CHAIN_FAILED",{retryable:false}),this.name="ChainExecutionError",this.attemptedModels=t;}},U=class extends m{constructor(e,t=[]){super(e,"VALIDATION_ERROR",{retryable:false}),this.name="ValidationError",this.validationErrors=t;}},y=class extends m{constructor(e,t=500){super(e,"SERVER_ERROR",{retryable:true,statusCode:t}),this.name="ServerError";}},T=class extends m{constructor(e,t){super(e,"QUOTA_EXCEEDED",{retryable:false,statusCode:402}),this.name="QuotaExceededError",this.tier=t.tier,this.limit=t.limit,this.used=t.used;}},P=class extends m{constructor(e,t){super(e,"WORKER_ERROR",{retryable:true}),this.name="WorkerError",this.workerDurationMs=t;}},M=class extends m{constructor(e,t){super(e,"FILE_PROCESSING_ERROR",{retryable:false}),this.name="FileProcessingError",this.filename=t.filename,this.mimeType=t.mimeType,this.reason=t.reason;}},D=class extends m{constructor(e,t){super(e,"INVALID_SCHEMA",{retryable:false}),this.name="InvalidSchemaError",this.schemaPath=t;}};function N(i){return i instanceof m}function K(i){return N(i)?i.retryable:false}var F="http://localhost:3000",W=6e5,J=3,_=[1e3,2e3,5e3];function $(i){let e=i.trim(),t=e.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);t&&(e=t[1].trim());let r=e.indexOf("{"),a=e.indexOf("["),s;if(r===-1&&a===-1)return e;r===-1?s=a:a===-1?s=r:s=Math.min(r,a);let o=e[s],d=o==="{"?"}":"]",n=0,u=false,c=false,l=-1;for(let g=s;g<e.length;g++){let h=e[g];if(c){c=false;continue}if(h==="\\"&&u){c=true;continue}if(h==='"'&&!c){u=!u;continue}if(!u){if(h===o)n++;else if(h===d&&(n--,n===0)){l=g;break}}}if(l!==-1)return e.slice(s,l+1);let f=e.lastIndexOf(d);return f>s?e.slice(s,f+1):e}var O=class O{constructor(e){if(!e.secretKey)throw new w("secretKey is required");if(!C(e.secretKey))throw new w("Invalid secret key format. Expected format: hyd_sk_<key>");this.secretKey=e.secretKey,this.baseUrl=(e.baseUrl||F).replace(/\/$/,""),this.timeout=e.timeout??W,this.maxRetries=e.maxRetries??J,this.keyPrefix=I(e.secretKey),this.debug=e.debug??false,this.debug&&this.log("Initialized",{baseUrl:this.baseUrl,timeout:this.timeout});}async generate(e){if(this.shouldUseOptimizedFlow(e))try{return await this.generateOptimized(e)}catch(r){this.debug&&this.log("3-step generation failed, falling back to legacy",{error:r instanceof Error?r.message:String(r)});}else e.useOptimized!==false&&this.debug&&this.log("generate using legacy endpoint",{reason:this.getLegacyFallbackReason(e)});return this.generateLegacy(e)}shouldUseOptimizedFlow(e){return e.useOptimized===false?false:O.LEGACY_ONLY_OPTION_LABELS.every(([t])=>e[t]===void 0)}getLegacyFallbackReason(e){let t=O.LEGACY_ONLY_OPTION_LABELS.filter(([r])=>e[r]!==void 0).map(([,r])=>r);return t.length===0?"optimized flow disabled":t.join(", ")}async generateOptimized(e){let t=Date.now(),r="/api/v1/generate/token",a=JSON.stringify({chainId:e.chainId,prompt:e.prompt,systemPrompt:e.options?.systemPrompt,temperature:e.options?.temperature,maxTokens:e.options?.maxTokens,topP:e.options?.topP,topK:e.options?.topK,files:e.files}),s=await this.makeAuthenticatedRequest(r,"POST",a,void 0,e.signal);if(s.cached&&s.data)return this.debug&&this.log("generate cache hit (optimized)",{latencyMs:Date.now()-t}),{success:true,data:s.data,meta:{cached:true,modelUsed:s.meta?.modelUsed||"",tokensUsed:{input:0,output:0},latencyMs:Date.now()-t,attemptedModels:[]}};if(!s.token||!s.workerUrl)throw new b("Token generation failed - no token or worker URL returned",[]);let o=JSON.stringify({token:s.token,prompt:e.prompt,systemPrompt:e.options?.systemPrompt,timeout:e.timeout??this.timeout}),n=await(await this.fetchWithTimeout(s.workerUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:o},e.timeout??this.timeout,e.signal)).json();if(!n.success||!n.content)return {success:false,data:{valid:[],invalid:[]},meta:{cached:false,modelUsed:n.modelId||"",tokensUsed:n.tokensUsed||{input:0,output:0},latencyMs:Date.now()-t,attemptedModels:[n.modelId||""]},error:{code:n.errorCode||"GENERATION_FAILED",message:n.errorMessage||"Generation failed",retryable:false}};let u=[],c=[],l=$(n.content);try{let g=JSON.parse(l);Array.isArray(g)?u=g:u=[g];}catch{u=[l];}n.completionToken&&this.cacheCompletionAsync({completionToken:n.completionToken,content:n.content,tokensUsed:n.tokensUsed||{input:0,output:0},finishReason:n.finishReason||"stop",chainId:e.chainId,modelUsed:n.modelId,prompt:e.prompt,systemPrompt:e.options?.systemPrompt,temperature:e.options?.temperature,maxTokens:e.options?.maxTokens,topP:e.options?.topP,topK:e.options?.topK}).catch(g=>{this.debug&&this.log("cacheCompletion failed (non-blocking)",{error:g});});let f=Date.now()-t;return this.debug&&this.log("generate success (optimized 3-step)",{latencyMs:f,modelUsed:n.modelId,tokensUsed:n.tokensUsed}),{success:true,data:{valid:u,invalid:c},meta:{cached:false,modelUsed:n.modelId,tokensUsed:n.tokensUsed||{input:0,output:0},latencyMs:f,attemptedModels:[n.modelId]}}}async cacheCompletionAsync(e){let t="/api/v1/generate/complete",r=JSON.stringify(e);await this.makeAuthenticatedRequest(t,"POST",r);}async generateLegacy(e){let t=Date.now(),r="/api/v1/generate",a=JSON.stringify({chainId:e.chainId,prompt:e.prompt,schema:e.schema,noCache:e.noCache,cacheScope:e.cacheScope,cacheQuality:e.cacheQuality,strictJson:e.strictJson,files:e.files,options:{...e.options,timeout:e.timeout??this.timeout}}),s=null,o=e.maxRetries??this.maxRetries;for(let u=0;u<=o;u++)try{let c=await this.makeAuthenticatedRequest(r,"POST",a,e.timeout,e.signal),l=Date.now()-t;return this.debug&&this.log("generate success (legacy)",{latencyMs:l,attempt:u+1}),c.meta&&(c.meta.latencyMs=l),c}catch(c){if(s=c instanceof Error?c:new Error(String(c)),this.debug&&this.log(`generate attempt ${u+1} failed`,{error:s.message}),!this.isRetryable(c)||u>=o)break;let l=_[Math.min(u,_.length-1)];c instanceof E&&c.retryAfterMs>l?await this.sleep(c.retryAfterMs):await this.sleep(l);}let d=Date.now()-t,n=s instanceof R;return {success:false,data:{valid:[],invalid:[]},meta:{cached:false,modelUsed:"",tokensUsed:{input:0,output:0},latencyMs:d,attemptedModels:[]},error:{code:n?"TIMEOUT":"REQUEST_FAILED",message:s?.message||"Request failed after all retries",retryable:false}}}async generateStream(e,t){let r="/api/v1/generate/stream",a=JSON.stringify({chainId:e.chainId,prompt:e.prompt,schema:e.schema,options:e.options});try{let s=Date.now(),o=await A(this.secretKey,"POST",r,a,s),d=await this.fetchWithTimeout(`${this.baseUrl}${r}`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"text/event-stream","x-hydra-signature":o,"x-hydra-timestamp":s.toString(),"x-hydra-key":this.keyPrefix},body:a},e.timeout??this.timeout);if(!d.ok){let f=await d.json();throw this.parseError(d.status,f)}let n=d.body?.getReader();if(!n)throw new k("Response body is not readable");let u=new TextDecoder,c="",l=[];for(;;){let{done:f,value:g}=await n.read();if(f)break;c+=u.decode(g,{stream:!0});let h=c.split(`
|
|
4
|
+
`);c=h.pop()||"";let v="";for(let x of h){if(x.startsWith("event: ")){v=x.slice(7).trim();continue}if(x.startsWith("data: ")){let q=x.slice(6);if(q==="[DONE]")break;try{let p=JSON.parse(q);switch(v){case "start":this.debug&&this.log("Stream started",p);break;case "object":p.object!==void 0&&(l.push(p.object),t.onObject?.(p.object,p.index)),t.onChunk&&p.object&&t.onChunk(JSON.stringify(p.object));break;case "array_start":t.onArrayStart?.();break;case "complete":t.onComplete&&t.onComplete({objects:l,objectCount:p.objectCount,tokensUsed:p.tokensUsed,cached:p.cached});break;case "error":t.onError&&t.onError(new b(p.error||"Stream error",["STREAM_ERROR"]));break;default:p.chunk&&t.onChunk?.(p.chunk);}}catch{}v="";}}}}catch(s){if(t.onError)t.onError(s instanceof Error?s:new Error(String(s)));else throw s}}async listModels(){let t=await(await this.fetchWithTimeout(`${this.baseUrl}/api/v1/models`,{method:"GET",headers:{Accept:"application/json"}})).json();if(!t.success)throw new y(t.error?.message||"Failed to list models");return t.data}async listChains(){let t=await(await this.fetchWithTimeout(`${this.baseUrl}/api/v1/chains`,{method:"GET",credentials:"include",headers:{Accept:"application/json"}})).json();if(!t.success)throw new y(t.error?.message||"Failed to list chains");return t.data}async getUsage(e){let t=new URLSearchParams;e?.startDate&&t.set("start",e.startDate.toISOString()),e?.endDate&&t.set("end",e.endDate.toISOString());let r=`${this.baseUrl}/api/v1/usage${t.toString()?"?"+t:""}`,s=await(await this.fetchWithTimeout(r,{method:"GET",credentials:"include",headers:{Accept:"application/json"}})).json();if(!s.success)throw new y(s.error?.message||"Failed to get usage");let o=s.data;if(!o)throw new y("Usage payload was empty");let d=o.cachedRequests??o.cachedResponses??0,n=o.totalTokensIn??o.totalTokens?.input??0,u=o.totalTokensOut??o.totalTokens?.output??0,c=o.averageLatencyMs??o.avgLatencyMs??0,l=s.meta?.dateRange??{start:e?.startDate?.toISOString()??"",end:e?.endDate?.toISOString()??""},f=o.byModel??[],g=Object.fromEntries(f.map(h=>[h.modelId,{requests:h.requests,tokensIn:h.tokensIn,tokensOut:h.tokensOut,tokens:{input:h.tokensIn,output:h.tokensOut,total:h.tokensIn+h.tokensOut}}]));return {totalRequests:o.totalRequests,successfulRequests:o.successfulRequests,failedRequests:o.failedRequests,cachedRequests:d,cachedResponses:d,totalTokensIn:n,totalTokensOut:u,totalTokens:{input:n,output:u,total:n+u},cacheHitRate:o.cacheHitRate,averageLatencyMs:c,avgLatencyMs:c,byModel:g,byModelList:f,byDay:o.byDay??[],dateRange:l,periodStart:l.start,periodEnd:l.end}}async health(){let e=Date.now();try{let t=await this.fetchWithTimeout(`${this.baseUrl}/api/v1/health`,{method:"GET"},5e3),r=Date.now()-e,a=await t.json().catch(()=>({}));return {ok:t.ok,latencyMs:r,version:a.version}}catch{return {ok:false,latencyMs:Date.now()-e}}}withConfig(e){return new O({secretKey:this.secretKey,baseUrl:this.baseUrl,timeout:this.timeout,maxRetries:this.maxRetries,debug:this.debug,...e})}async registerWebhook(e){let t=await this.makeAuthenticatedRequest("/api/v1/webhooks","POST",JSON.stringify(e));if(!t.success||!t.data)throw new y(t.error?.message||"Failed to register webhook");return t.data}async unregisterWebhook(e){let t=await this.makeAuthenticatedRequest(`/api/v1/webhooks/${e}`,"DELETE","");if(!t.success||!t.data)throw new y(t.error?.message||"Failed to unregister webhook");return t.data}async listWebhooks(){let e=await this.makeAuthenticatedRequest("/api/v1/webhooks","GET","");if(!e.success)throw new y(e.error?.message||"Failed to list webhooks");return e.data||[]}async updateWebhook(e,t){let r=await this.makeAuthenticatedRequest(`/api/v1/webhooks/${e}`,"PATCH",JSON.stringify(t));if(!r.success||!r.data)throw new y(r.error?.message||"Failed to update webhook");return r.data}async healthDetailed(){return this.makeAuthenticatedRequest("/api/v1/health/detailed","GET","")}async generateBatch(e,t,r={}){let a=await this.makeAuthenticatedRequest("/api/v1/generate/batch","POST",JSON.stringify({chainId:e,items:t,continueOnError:r.continueOnError??true}));if(!a.success||!a.data)throw new b(a.error?.message||"Batch generation failed",[]);return a.data}async makeAuthenticatedRequest(e,t,r,a,s){let o=Date.now(),d=t.toUpperCase(),n=await A(this.secretKey,d,e,r,o),u={method:d,headers:{"Content-Type":"application/json",Accept:"application/json","x-hydra-signature":n,"x-hydra-timestamp":o.toString(),"x-hydra-key":this.keyPrefix}};r.length>0&&d!=="GET"&&d!=="HEAD"&&(u.body=r);let c=await this.fetchWithTimeout(`${this.baseUrl}${e}`,u,a??this.timeout,s),l=await c.json();if(!c.ok)throw this.parseError(c.status,l);return l}async fetchWithTimeout(e,t,r=this.timeout,a){let s=new AbortController,o=setTimeout(()=>s.abort(),r),d=()=>s.abort();a&&(a.aborted?s.abort():a.addEventListener("abort",d,{once:true}));try{return await fetch(e,{...t,signal:s.signal})}catch(n){throw n instanceof Error&&n.name==="AbortError"?new R(r):n instanceof Error?new k(n.message,n):new k(String(n))}finally{clearTimeout(o),a?.removeEventListener("abort",d);}}parseError(e,t){let r=typeof t?.error=="string"?t.error:t?.error?.message||`HTTP ${e}`,a=t?.error?.code||"UNKNOWN",s=t?.meta||t;switch(e){case 401:return new S(r,e);case 429:if(s?.tier&&typeof s?.used=="number"&&typeof s?.limit=="number")return new T(r,{tier:s.tier,used:s.used,limit:s.limit});let o=t?.retryAfterMs||6e4;return new E(r,o);case 402:return new T(r,{tier:s?.tier||"UNKNOWN",used:s?.used||0,limit:s?.limit||0});case 500:case 502:case 503:case 504:return new y(r,e);default:return a==="CHAIN_FAILED"?new b(r,t?.error?.attemptedModels||[]):new m(r,a,{statusCode:e,retryable:e>=500})}}isRetryable(e){if(e instanceof m)return e.retryable;if(e instanceof Error){let t=e.message.toLowerCase();return t.includes("network")||t.includes("timeout")||e.name==="AbortError"}return false}sleep(e){return new Promise(t=>setTimeout(t,e))}log(e,t){let r="[Hydra]";t?console.log(r,e,t):console.log(r,e);}};O.LEGACY_ONLY_OPTION_LABELS=[["schema","schema validation"],["noCache","cache bypass"],["cacheQuality","cache quality override"],["strictJson","strict JSON mode"],["cacheScope","cache scope override"],["maxRetries","per-request retries"]];var L=O;exports.AuthenticationError=S;exports.ChainExecutionError=b;exports.ConfigurationError=w;exports.FileProcessingError=M;exports.Hydra=L;exports.HydraError=m;exports.InvalidSchemaError=D;exports.NetworkError=k;exports.QuotaExceededError=T;exports.RateLimitError=E;exports.ServerError=y;exports.TimeoutError=R;exports.ValidationError=U;exports.WorkerError=P;exports.generateSignature=A;exports.getKeyPrefix=I;exports.isHydraError=N;exports.isRetryableError=K;exports.isValidSecretKey=C;
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
2
|
-
`)
|
|
3
|
-
`),d=await
|
|
4
|
-
`);
|
|
1
|
+
var G=typeof process<"u"&&process.versions?.node;async function H(i,e,t,r,a){let{createHmac:s}=await import('crypto'),o=[e.toUpperCase(),t,a.toString(),r].join(`
|
|
2
|
+
`);return s("sha256",i).update(o).digest("hex")}async function j(i,e,t,r,a){let s=new TextEncoder,o=[e.toUpperCase(),t,a.toString(),r].join(`
|
|
3
|
+
`),d=await crypto.subtle.importKey("raw",s.encode(i),{name:"HMAC",hash:"SHA-256"},false,["sign"]),n=await crypto.subtle.sign("HMAC",d,s.encode(o));return Array.from(new Uint8Array(n)).map(c=>c.toString(16).padStart(2,"0")).join("")}async function A(i,e,t,r,a){return G?H(i,e,t,r,a):j(i,e,t,r,a)}function I(i){return i.slice(0,12)}function C(i){return typeof i=="string"&&i.startsWith("hyd_sk_")&&i.length>=20}var m=class extends Error{constructor(e,t,r){super(e),this.name="HydraError",this.code=t,this.retryable=r?.retryable??false,this.statusCode=r?.statusCode,this.originalError=r?.cause;}},w=class extends m{constructor(e){super(e,"CONFIGURATION_ERROR",{retryable:false}),this.name="ConfigurationError";}},S=class extends m{constructor(e,t){super(e,"AUTH_ERROR",{retryable:false,statusCode:t}),this.name="AuthenticationError";}},E=class extends m{constructor(e,t=6e4){super(e,"RATE_LIMITED",{retryable:true,statusCode:429}),this.name="RateLimitError",this.retryAfterMs=t;}},R=class extends m{constructor(e){super(`Request timed out after ${e}ms`,"TIMEOUT",{retryable:true}),this.name="TimeoutError",this.timeoutMs=e;}},k=class extends m{constructor(e,t){super(e,"NETWORK_ERROR",{retryable:true,cause:t}),this.name="NetworkError";}},b=class extends m{constructor(e,t=[]){super(e,"CHAIN_FAILED",{retryable:false}),this.name="ChainExecutionError",this.attemptedModels=t;}},U=class extends m{constructor(e,t=[]){super(e,"VALIDATION_ERROR",{retryable:false}),this.name="ValidationError",this.validationErrors=t;}},y=class extends m{constructor(e,t=500){super(e,"SERVER_ERROR",{retryable:true,statusCode:t}),this.name="ServerError";}},T=class extends m{constructor(e,t){super(e,"QUOTA_EXCEEDED",{retryable:false,statusCode:402}),this.name="QuotaExceededError",this.tier=t.tier,this.limit=t.limit,this.used=t.used;}},P=class extends m{constructor(e,t){super(e,"WORKER_ERROR",{retryable:true}),this.name="WorkerError",this.workerDurationMs=t;}},M=class extends m{constructor(e,t){super(e,"FILE_PROCESSING_ERROR",{retryable:false}),this.name="FileProcessingError",this.filename=t.filename,this.mimeType=t.mimeType,this.reason=t.reason;}},D=class extends m{constructor(e,t){super(e,"INVALID_SCHEMA",{retryable:false}),this.name="InvalidSchemaError",this.schemaPath=t;}};function N(i){return i instanceof m}function K(i){return N(i)?i.retryable:false}var F="http://localhost:3000",W=6e5,J=3,_=[1e3,2e3,5e3];function $(i){let e=i.trim(),t=e.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);t&&(e=t[1].trim());let r=e.indexOf("{"),a=e.indexOf("["),s;if(r===-1&&a===-1)return e;r===-1?s=a:a===-1?s=r:s=Math.min(r,a);let o=e[s],d=o==="{"?"}":"]",n=0,u=false,c=false,l=-1;for(let g=s;g<e.length;g++){let h=e[g];if(c){c=false;continue}if(h==="\\"&&u){c=true;continue}if(h==='"'&&!c){u=!u;continue}if(!u){if(h===o)n++;else if(h===d&&(n--,n===0)){l=g;break}}}if(l!==-1)return e.slice(s,l+1);let f=e.lastIndexOf(d);return f>s?e.slice(s,f+1):e}var O=class O{constructor(e){if(!e.secretKey)throw new w("secretKey is required");if(!C(e.secretKey))throw new w("Invalid secret key format. Expected format: hyd_sk_<key>");this.secretKey=e.secretKey,this.baseUrl=(e.baseUrl||F).replace(/\/$/,""),this.timeout=e.timeout??W,this.maxRetries=e.maxRetries??J,this.keyPrefix=I(e.secretKey),this.debug=e.debug??false,this.debug&&this.log("Initialized",{baseUrl:this.baseUrl,timeout:this.timeout});}async generate(e){if(this.shouldUseOptimizedFlow(e))try{return await this.generateOptimized(e)}catch(r){this.debug&&this.log("3-step generation failed, falling back to legacy",{error:r instanceof Error?r.message:String(r)});}else e.useOptimized!==false&&this.debug&&this.log("generate using legacy endpoint",{reason:this.getLegacyFallbackReason(e)});return this.generateLegacy(e)}shouldUseOptimizedFlow(e){return e.useOptimized===false?false:O.LEGACY_ONLY_OPTION_LABELS.every(([t])=>e[t]===void 0)}getLegacyFallbackReason(e){let t=O.LEGACY_ONLY_OPTION_LABELS.filter(([r])=>e[r]!==void 0).map(([,r])=>r);return t.length===0?"optimized flow disabled":t.join(", ")}async generateOptimized(e){let t=Date.now(),r="/api/v1/generate/token",a=JSON.stringify({chainId:e.chainId,prompt:e.prompt,systemPrompt:e.options?.systemPrompt,temperature:e.options?.temperature,maxTokens:e.options?.maxTokens,topP:e.options?.topP,topK:e.options?.topK,files:e.files}),s=await this.makeAuthenticatedRequest(r,"POST",a,void 0,e.signal);if(s.cached&&s.data)return this.debug&&this.log("generate cache hit (optimized)",{latencyMs:Date.now()-t}),{success:true,data:s.data,meta:{cached:true,modelUsed:s.meta?.modelUsed||"",tokensUsed:{input:0,output:0},latencyMs:Date.now()-t,attemptedModels:[]}};if(!s.token||!s.workerUrl)throw new b("Token generation failed - no token or worker URL returned",[]);let o=JSON.stringify({token:s.token,prompt:e.prompt,systemPrompt:e.options?.systemPrompt,timeout:e.timeout??this.timeout}),n=await(await this.fetchWithTimeout(s.workerUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:o},e.timeout??this.timeout,e.signal)).json();if(!n.success||!n.content)return {success:false,data:{valid:[],invalid:[]},meta:{cached:false,modelUsed:n.modelId||"",tokensUsed:n.tokensUsed||{input:0,output:0},latencyMs:Date.now()-t,attemptedModels:[n.modelId||""]},error:{code:n.errorCode||"GENERATION_FAILED",message:n.errorMessage||"Generation failed",retryable:false}};let u=[],c=[],l=$(n.content);try{let g=JSON.parse(l);Array.isArray(g)?u=g:u=[g];}catch{u=[l];}n.completionToken&&this.cacheCompletionAsync({completionToken:n.completionToken,content:n.content,tokensUsed:n.tokensUsed||{input:0,output:0},finishReason:n.finishReason||"stop",chainId:e.chainId,modelUsed:n.modelId,prompt:e.prompt,systemPrompt:e.options?.systemPrompt,temperature:e.options?.temperature,maxTokens:e.options?.maxTokens,topP:e.options?.topP,topK:e.options?.topK}).catch(g=>{this.debug&&this.log("cacheCompletion failed (non-blocking)",{error:g});});let f=Date.now()-t;return this.debug&&this.log("generate success (optimized 3-step)",{latencyMs:f,modelUsed:n.modelId,tokensUsed:n.tokensUsed}),{success:true,data:{valid:u,invalid:c},meta:{cached:false,modelUsed:n.modelId,tokensUsed:n.tokensUsed||{input:0,output:0},latencyMs:f,attemptedModels:[n.modelId]}}}async cacheCompletionAsync(e){let t="/api/v1/generate/complete",r=JSON.stringify(e);await this.makeAuthenticatedRequest(t,"POST",r);}async generateLegacy(e){let t=Date.now(),r="/api/v1/generate",a=JSON.stringify({chainId:e.chainId,prompt:e.prompt,schema:e.schema,noCache:e.noCache,cacheScope:e.cacheScope,cacheQuality:e.cacheQuality,strictJson:e.strictJson,files:e.files,options:{...e.options,timeout:e.timeout??this.timeout}}),s=null,o=e.maxRetries??this.maxRetries;for(let u=0;u<=o;u++)try{let c=await this.makeAuthenticatedRequest(r,"POST",a,e.timeout,e.signal),l=Date.now()-t;return this.debug&&this.log("generate success (legacy)",{latencyMs:l,attempt:u+1}),c.meta&&(c.meta.latencyMs=l),c}catch(c){if(s=c instanceof Error?c:new Error(String(c)),this.debug&&this.log(`generate attempt ${u+1} failed`,{error:s.message}),!this.isRetryable(c)||u>=o)break;let l=_[Math.min(u,_.length-1)];c instanceof E&&c.retryAfterMs>l?await this.sleep(c.retryAfterMs):await this.sleep(l);}let d=Date.now()-t,n=s instanceof R;return {success:false,data:{valid:[],invalid:[]},meta:{cached:false,modelUsed:"",tokensUsed:{input:0,output:0},latencyMs:d,attemptedModels:[]},error:{code:n?"TIMEOUT":"REQUEST_FAILED",message:s?.message||"Request failed after all retries",retryable:false}}}async generateStream(e,t){let r="/api/v1/generate/stream",a=JSON.stringify({chainId:e.chainId,prompt:e.prompt,schema:e.schema,options:e.options});try{let s=Date.now(),o=await A(this.secretKey,"POST",r,a,s),d=await this.fetchWithTimeout(`${this.baseUrl}${r}`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"text/event-stream","x-hydra-signature":o,"x-hydra-timestamp":s.toString(),"x-hydra-key":this.keyPrefix},body:a},e.timeout??this.timeout);if(!d.ok){let f=await d.json();throw this.parseError(d.status,f)}let n=d.body?.getReader();if(!n)throw new k("Response body is not readable");let u=new TextDecoder,c="",l=[];for(;;){let{done:f,value:g}=await n.read();if(f)break;c+=u.decode(g,{stream:!0});let h=c.split(`
|
|
4
|
+
`);c=h.pop()||"";let v="";for(let x of h){if(x.startsWith("event: ")){v=x.slice(7).trim();continue}if(x.startsWith("data: ")){let q=x.slice(6);if(q==="[DONE]")break;try{let p=JSON.parse(q);switch(v){case "start":this.debug&&this.log("Stream started",p);break;case "object":p.object!==void 0&&(l.push(p.object),t.onObject?.(p.object,p.index)),t.onChunk&&p.object&&t.onChunk(JSON.stringify(p.object));break;case "array_start":t.onArrayStart?.();break;case "complete":t.onComplete&&t.onComplete({objects:l,objectCount:p.objectCount,tokensUsed:p.tokensUsed,cached:p.cached});break;case "error":t.onError&&t.onError(new b(p.error||"Stream error",["STREAM_ERROR"]));break;default:p.chunk&&t.onChunk?.(p.chunk);}}catch{}v="";}}}}catch(s){if(t.onError)t.onError(s instanceof Error?s:new Error(String(s)));else throw s}}async listModels(){let t=await(await this.fetchWithTimeout(`${this.baseUrl}/api/v1/models`,{method:"GET",headers:{Accept:"application/json"}})).json();if(!t.success)throw new y(t.error?.message||"Failed to list models");return t.data}async listChains(){let t=await(await this.fetchWithTimeout(`${this.baseUrl}/api/v1/chains`,{method:"GET",credentials:"include",headers:{Accept:"application/json"}})).json();if(!t.success)throw new y(t.error?.message||"Failed to list chains");return t.data}async getUsage(e){let t=new URLSearchParams;e?.startDate&&t.set("start",e.startDate.toISOString()),e?.endDate&&t.set("end",e.endDate.toISOString());let r=`${this.baseUrl}/api/v1/usage${t.toString()?"?"+t:""}`,s=await(await this.fetchWithTimeout(r,{method:"GET",credentials:"include",headers:{Accept:"application/json"}})).json();if(!s.success)throw new y(s.error?.message||"Failed to get usage");let o=s.data;if(!o)throw new y("Usage payload was empty");let d=o.cachedRequests??o.cachedResponses??0,n=o.totalTokensIn??o.totalTokens?.input??0,u=o.totalTokensOut??o.totalTokens?.output??0,c=o.averageLatencyMs??o.avgLatencyMs??0,l=s.meta?.dateRange??{start:e?.startDate?.toISOString()??"",end:e?.endDate?.toISOString()??""},f=o.byModel??[],g=Object.fromEntries(f.map(h=>[h.modelId,{requests:h.requests,tokensIn:h.tokensIn,tokensOut:h.tokensOut,tokens:{input:h.tokensIn,output:h.tokensOut,total:h.tokensIn+h.tokensOut}}]));return {totalRequests:o.totalRequests,successfulRequests:o.successfulRequests,failedRequests:o.failedRequests,cachedRequests:d,cachedResponses:d,totalTokensIn:n,totalTokensOut:u,totalTokens:{input:n,output:u,total:n+u},cacheHitRate:o.cacheHitRate,averageLatencyMs:c,avgLatencyMs:c,byModel:g,byModelList:f,byDay:o.byDay??[],dateRange:l,periodStart:l.start,periodEnd:l.end}}async health(){let e=Date.now();try{let t=await this.fetchWithTimeout(`${this.baseUrl}/api/v1/health`,{method:"GET"},5e3),r=Date.now()-e,a=await t.json().catch(()=>({}));return {ok:t.ok,latencyMs:r,version:a.version}}catch{return {ok:false,latencyMs:Date.now()-e}}}withConfig(e){return new O({secretKey:this.secretKey,baseUrl:this.baseUrl,timeout:this.timeout,maxRetries:this.maxRetries,debug:this.debug,...e})}async registerWebhook(e){let t=await this.makeAuthenticatedRequest("/api/v1/webhooks","POST",JSON.stringify(e));if(!t.success||!t.data)throw new y(t.error?.message||"Failed to register webhook");return t.data}async unregisterWebhook(e){let t=await this.makeAuthenticatedRequest(`/api/v1/webhooks/${e}`,"DELETE","");if(!t.success||!t.data)throw new y(t.error?.message||"Failed to unregister webhook");return t.data}async listWebhooks(){let e=await this.makeAuthenticatedRequest("/api/v1/webhooks","GET","");if(!e.success)throw new y(e.error?.message||"Failed to list webhooks");return e.data||[]}async updateWebhook(e,t){let r=await this.makeAuthenticatedRequest(`/api/v1/webhooks/${e}`,"PATCH",JSON.stringify(t));if(!r.success||!r.data)throw new y(r.error?.message||"Failed to update webhook");return r.data}async healthDetailed(){return this.makeAuthenticatedRequest("/api/v1/health/detailed","GET","")}async generateBatch(e,t,r={}){let a=await this.makeAuthenticatedRequest("/api/v1/generate/batch","POST",JSON.stringify({chainId:e,items:t,continueOnError:r.continueOnError??true}));if(!a.success||!a.data)throw new b(a.error?.message||"Batch generation failed",[]);return a.data}async makeAuthenticatedRequest(e,t,r,a,s){let o=Date.now(),d=t.toUpperCase(),n=await A(this.secretKey,d,e,r,o),u={method:d,headers:{"Content-Type":"application/json",Accept:"application/json","x-hydra-signature":n,"x-hydra-timestamp":o.toString(),"x-hydra-key":this.keyPrefix}};r.length>0&&d!=="GET"&&d!=="HEAD"&&(u.body=r);let c=await this.fetchWithTimeout(`${this.baseUrl}${e}`,u,a??this.timeout,s),l=await c.json();if(!c.ok)throw this.parseError(c.status,l);return l}async fetchWithTimeout(e,t,r=this.timeout,a){let s=new AbortController,o=setTimeout(()=>s.abort(),r),d=()=>s.abort();a&&(a.aborted?s.abort():a.addEventListener("abort",d,{once:true}));try{return await fetch(e,{...t,signal:s.signal})}catch(n){throw n instanceof Error&&n.name==="AbortError"?new R(r):n instanceof Error?new k(n.message,n):new k(String(n))}finally{clearTimeout(o),a?.removeEventListener("abort",d);}}parseError(e,t){let r=typeof t?.error=="string"?t.error:t?.error?.message||`HTTP ${e}`,a=t?.error?.code||"UNKNOWN",s=t?.meta||t;switch(e){case 401:return new S(r,e);case 429:if(s?.tier&&typeof s?.used=="number"&&typeof s?.limit=="number")return new T(r,{tier:s.tier,used:s.used,limit:s.limit});let o=t?.retryAfterMs||6e4;return new E(r,o);case 402:return new T(r,{tier:s?.tier||"UNKNOWN",used:s?.used||0,limit:s?.limit||0});case 500:case 502:case 503:case 504:return new y(r,e);default:return a==="CHAIN_FAILED"?new b(r,t?.error?.attemptedModels||[]):new m(r,a,{statusCode:e,retryable:e>=500})}}isRetryable(e){if(e instanceof m)return e.retryable;if(e instanceof Error){let t=e.message.toLowerCase();return t.includes("network")||t.includes("timeout")||e.name==="AbortError"}return false}sleep(e){return new Promise(t=>setTimeout(t,e))}log(e,t){let r="[Hydra]";t?console.log(r,e,t):console.log(r,e);}};O.LEGACY_ONLY_OPTION_LABELS=[["schema","schema validation"],["noCache","cache bypass"],["cacheQuality","cache quality override"],["strictJson","strict JSON mode"],["cacheScope","cache scope override"],["maxRetries","per-request retries"]];var L=O;export{S as AuthenticationError,b as ChainExecutionError,w as ConfigurationError,M as FileProcessingError,L as Hydra,m as HydraError,D as InvalidSchemaError,k as NetworkError,T as QuotaExceededError,E as RateLimitError,y as ServerError,R as TimeoutError,U as ValidationError,P as WorkerError,A as generateSignature,I as getKeyPrefix,N as isHydraError,K as isRetryableError,C as isValidSecretKey};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hydra-aidirector",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "Official TypeScript SDK for Hydra - Intelligent AI API Gateway with automatic failover, caching, and JSON extraction",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
"lint": "tsc --noEmit",
|
|
30
30
|
"test": "vitest run",
|
|
31
31
|
"test:watch": "vitest",
|
|
32
|
-
"prepublishOnly": "
|
|
32
|
+
"prepublishOnly": "bun run build",
|
|
33
33
|
"clean": "rimraf dist",
|
|
34
|
-
"publish:check": "
|
|
35
|
-
"release": "
|
|
34
|
+
"publish:check": "bun publish --dry-run",
|
|
35
|
+
"release": "bun run build && bun publish --access public",
|
|
36
36
|
"release:dual": "node scripts/publish-dual.js",
|
|
37
|
-
"release:beta": "
|
|
37
|
+
"release:beta": "bun run build && bun publish --access public --tag beta"
|
|
38
38
|
},
|
|
39
39
|
"keywords": [
|
|
40
40
|
"ai",
|