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 CHANGED
@@ -1,399 +1,304 @@
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
+ 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 (e.g., DOCX PDF)
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 idempotent-sensitive operations.
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
- * If true, ensures the model outputs valid JSON conforming to the schema.
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
- * Client-generated request ID for tracing and debugging.
133
- * If not provided, one will be generated automatically.
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
- * Enable thinking/reasoning mode for supported models.
188
- * When enabled, the model will show its reasoning process.
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
- * Usage by model
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
- * - 🔐 HMAC Authentication with browser support
645
- * - Configurable timeout (default 10 minutes)
646
- * - 🔄 Automatic retries with exponential backoff
647
- * - 📦 Structured error handling
648
- * - 🎯 Full TypeScript support
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 CF Worker directly (FREE)
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
- * IMPORTANT: We hash the secret key first, then use the hash as HMAC key.
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 (e.g., DOCX PDF)
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 idempotent-sensitive operations.
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
- * If true, ensures the model outputs valid JSON conforming to the schema.
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
- * Client-generated request ID for tracing and debugging.
133
- * If not provided, one will be generated automatically.
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
- * Enable thinking/reasoning mode for supported models.
188
- * When enabled, the model will show its reasoning process.
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
- * Usage by model
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
- * - 🔐 HMAC Authentication with browser support
645
- * - Configurable timeout (default 10 minutes)
646
- * - 🔄 Automatic retries with exponential backoff
647
- * - 📦 Structured error handling
648
- * - 🎯 Full TypeScript support
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 CF Worker directly (FREE)
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
- * IMPORTANT: We hash the secret key first, then use the hash as HMAC key.
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 K=typeof process<"u"&&process.versions?.node;async function H(n){let{createHash:e}=await import('crypto');return e("sha256").update(n).digest("hex")}async function G(n,e,t,r,o){let{createHmac:s}=await import('crypto'),c=[e.toUpperCase(),t,o.toString(),r].join(`
2
- `),d=await H(n);return s("sha256",d).update(c).digest("hex")}async function L(n){let t=new TextEncoder().encode(n),r=await crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(r)).map(s=>s.toString(16).padStart(2,"0")).join("")}async function W(n,e,t,r,o){let s=new TextEncoder,c=[e.toUpperCase(),t,o.toString(),r].join(`
3
- `),d=await L(n),a=s.encode(d),i=await crypto.subtle.importKey("raw",a,{name:"HMAC",hash:"SHA-256"},false,["sign"]),m=await crypto.subtle.sign("HMAC",i,s.encode(c));return Array.from(new Uint8Array(m)).map(p=>p.toString(16).padStart(2,"0")).join("")}async function T(n,e,t,r,o){return K?G(n,e,t,r,o):W(n,e,t,r,o)}function U(n){return n.slice(0,12)}function C(n){return typeof n=="string"&&n.startsWith("hyd_sk_")&&n.length>=20}var u=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;}},k=class extends u{constructor(e){super(e,"CONFIGURATION_ERROR",{retryable:false}),this.name="ConfigurationError";}},x=class extends u{constructor(e,t){super(e,"AUTH_ERROR",{retryable:false,statusCode:t}),this.name="AuthenticationError";}},E=class extends u{constructor(e,t=6e4){super(e,"RATE_LIMITED",{retryable:true,statusCode:429}),this.name="RateLimitError",this.retryAfterMs=t;}},R=class extends u{constructor(e){super(`Request timed out after ${e}ms`,"TIMEOUT",{retryable:true}),this.name="TimeoutError",this.timeoutMs=e;}},w=class extends u{constructor(e,t){super(e,"NETWORK_ERROR",{retryable:true,cause:t}),this.name="NetworkError";}},f=class extends u{constructor(e,t=[]){super(e,"CHAIN_FAILED",{retryable:false}),this.name="ChainExecutionError",this.attemptedModels=t;}},P=class extends u{constructor(e,t=[]){super(e,"VALIDATION_ERROR",{retryable:false}),this.name="ValidationError",this.validationErrors=t;}},g=class extends u{constructor(e,t=500){super(e,"SERVER_ERROR",{retryable:true,statusCode:t}),this.name="ServerError";}},v=class extends u{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;}},O=class extends u{constructor(e,t){super(e,"WORKER_ERROR",{retryable:true}),this.name="WorkerError",this.workerDurationMs=t;}},I=class extends u{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;}},M=class extends u{constructor(e,t){super(e,"INVALID_SCHEMA",{retryable:false}),this.name="InvalidSchemaError",this.schemaPath=t;}};function _(n){return n instanceof u}function q(n){return _(n)?n.retryable:false}var F="http://localhost:3000",$=6e5,B=3,N=[1e3,2e3,5e3];function J(n){let e=n.trim(),t=e.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);t&&(e=t[1].trim());let r=e.indexOf("{"),o=e.indexOf("["),s;if(r===-1&&o===-1)return e;r===-1?s=o:o===-1?s=r:s=Math.min(r,o);let c=e[s],d=c==="{"?"}":"]",a=0,i=false,m=false,y=-1;for(let h=s;h<e.length;h++){let b=e[h];if(m){m=false;continue}if(b==="\\"&&i){m=true;continue}if(b==='"'&&!m){i=!i;continue}if(!i){if(b===c)a++;else if(b===d&&(a--,a===0)){y=h;break}}}if(y!==-1)return e.slice(s,y+1);let p=e.lastIndexOf(d);return p>s?e.slice(s,p+1):e}var D=class n{constructor(e){if(!e.secretKey)throw new k("secretKey is required");if(!C(e.secretKey))throw new k("Invalid secret key format. Expected format: hyd_sk_<key>");this.secretKey=e.secretKey,this.baseUrl=(e.baseUrl||F).replace(/\/$/,""),this.timeout=e.timeout??$,this.maxRetries=e.maxRetries??B,this.keyPrefix=U(e.secretKey),this.debug=e.debug??false,this.debug&&this.log("Initialized",{baseUrl:this.baseUrl,timeout:this.timeout});}async generate(e){if(e.useOptimized!==false)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)});}return this.generateLegacy(e)}async generateOptimized(e){let t=Date.now(),r="/api/v1/generate/token",o=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",o);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 f("Token generation failed - no token or worker URL returned",[]);let c=JSON.stringify({token:s.token,prompt:e.prompt,systemPrompt:e.options?.systemPrompt,timeout:e.timeout??this.timeout}),a=await(await this.fetchWithTimeout(s.workerUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:c},e.timeout??this.timeout)).json();if(!a.success||!a.content)return {success:false,data:{valid:[],invalid:[]},meta:{cached:false,modelUsed:a.modelId||"",tokensUsed:a.tokensUsed||{input:0,output:0},latencyMs:Date.now()-t,attemptedModels:[a.modelId||""]},error:{code:a.errorCode||"GENERATION_FAILED",message:a.errorMessage||"Generation failed",retryable:false}};let i=[],m=[],y=J(a.content);try{let h=JSON.parse(y);Array.isArray(h)?i=h:i=[h];}catch{i=[y];}a.completionToken&&this.cacheCompletionAsync({completionToken:a.completionToken,content:a.content,tokensUsed:a.tokensUsed||{input:0,output:0},finishReason:a.finishReason||"stop",chainId:e.chainId,modelUsed:a.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(h=>{this.debug&&this.log("cacheCompletion failed (non-blocking)",{error:h});});let p=Date.now()-t;return this.debug&&this.log("generate success (optimized 3-step)",{latencyMs:p,modelUsed:a.modelId,tokensUsed:a.tokensUsed}),{success:true,data:{valid:i,invalid:m},meta:{cached:false,modelUsed:a.modelId,tokensUsed:a.tokensUsed||{input:0,output:0},latencyMs:p,attemptedModels:[a.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",o=JSON.stringify({chainId:e.chainId,prompt:e.prompt,schema:e.schema,noCache:e.noCache,files:e.files,options:{...e.options,timeout:e.timeout??this.timeout}}),s=null;for(let a=0;a<=this.maxRetries;a++)try{let i=await this.makeAuthenticatedRequest(r,"POST",o,e.timeout),m=Date.now()-t;return this.debug&&this.log("generate success (legacy)",{latencyMs:m,attempt:a+1}),i.meta&&(i.meta.latencyMs=m),i}catch(i){if(s=i instanceof Error?i:new Error(String(i)),this.debug&&this.log(`generate attempt ${a+1} failed`,{error:s.message}),!this.isRetryable(i)||a>=this.maxRetries)break;let m=N[Math.min(a,N.length-1)];i instanceof E&&i.retryAfterMs>m?await this.sleep(i.retryAfterMs):await this.sleep(m);}let c=Date.now()-t,d=s instanceof R;return {success:false,data:{valid:[],invalid:[]},meta:{cached:false,modelUsed:"",tokensUsed:{input:0,output:0},latencyMs:c,attemptedModels:[]},error:{code:d?"TIMEOUT":"REQUEST_FAILED",message:s?.message||"Request failed after all retries",retryable:false}}}async generateStream(e,t){let r="/api/v1/generate/stream",o=JSON.stringify({chainId:e.chainId,prompt:e.prompt,schema:e.schema,options:e.options});try{let s=Date.now(),c=await T(this.secretKey,"POST",r,o,s),d=await this.fetchWithTimeout(`${this.baseUrl}${r}`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"text/event-stream","x-hydra-signature":c,"x-hydra-timestamp":s.toString(),"x-hydra-key":this.keyPrefix},body:o},e.timeout??this.timeout);if(!d.ok){let p=await d.json();throw this.parseError(d.status,p)}let a=d.body?.getReader();if(!a)throw new w("Response body is not readable");let i=new TextDecoder,m="",y=[];for(;;){let{done:p,value:h}=await a.read();if(p)break;m+=i.decode(h,{stream:!0});let b=m.split(`
4
- `);m=b.pop()||"";let A="";for(let S of b){if(S.startsWith("event: ")){A=S.slice(7).trim();continue}if(S.startsWith("data: ")){let j=S.slice(6);if(j==="[DONE]")break;try{let l=JSON.parse(j);switch(A){case "start":this.debug&&this.log("Stream started",l);break;case "object":l.object!==void 0&&(y.push(l.object),t.onObject?.(l.object,l.index)),t.onChunk&&l.object&&t.onChunk(JSON.stringify(l.object));break;case "array_start":t.onArrayStart?.();break;case "complete":t.onComplete&&t.onComplete({objects:y,objectCount:l.objectCount,tokensUsed:l.tokensUsed,cached:l.cached});break;case "error":t.onError&&t.onError(new f(l.error||"Stream error",["STREAM_ERROR"]));break;default:l.chunk&&t.onChunk?.(l.chunk);}}catch{}A="";}}}}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 g(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 g(t.error?.message||"Failed to list chains");return t.data}async getUsage(e){let t=new URLSearchParams;e?.startDate&&t.set("startDate",e.startDate.toISOString()),e?.endDate&&t.set("endDate",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 g(s.error?.message||"Failed to get usage");return s.data}async health(){let e=Date.now();try{let t=await this.fetchWithTimeout(`${this.baseUrl}/api/health`,{method:"GET"},5e3),r=Date.now()-e,o=await t.json().catch(()=>({}));return {ok:t.ok,latencyMs:r,version:o.version}}catch{return {ok:false,latencyMs:Date.now()-e}}}withConfig(e){return new n({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 g(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 g(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 g(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 g(r.error?.message||"Failed to update webhook");return r.data}async healthDetailed(){return (await fetch(`${this.baseUrl}/api/v1/health/detailed`,{headers:{Accept:"application/json"}})).json()}async generateBatch(e,t,r={}){let o=await this.makeAuthenticatedRequest("/api/v1/generate/batch","POST",JSON.stringify({chainId:e,items:t,continueOnError:r.continueOnError??true}));if(!o.success||!o.data)throw new f(o.error?.message||"Batch generation failed",[]);return o.data}async makeAuthenticatedRequest(e,t,r,o){let s=Date.now(),c=await T(this.secretKey,t,e,r,s),d=await this.fetchWithTimeout(`${this.baseUrl}${e}`,{method:t,headers:{"Content-Type":"application/json",Accept:"application/json","x-hydra-signature":c,"x-hydra-timestamp":s.toString(),"x-hydra-key":this.keyPrefix},body:r},o??this.timeout),a=await d.json();if(!d.ok)throw this.parseError(d.status,a);return a}async fetchWithTimeout(e,t,r=this.timeout){let o=new AbortController,s=setTimeout(()=>o.abort(),r);try{return await fetch(e,{...t,signal:o.signal})}catch(c){throw c instanceof Error&&c.name==="AbortError"?new R(r):c instanceof Error?new w(c.message,c):new w(String(c))}finally{clearTimeout(s);}}parseError(e,t){let r=typeof t?.error=="string"?t.error:t?.error?.message||`HTTP ${e}`,o=t?.error?.code||"UNKNOWN";switch(e){case 401:return new x(r,e);case 429:let s=t?.retryAfterMs||6e4;return new E(r,s);case 500:case 502:case 503:case 504:return new g(r,e);default:return o==="CHAIN_FAILED"?new f(r,t?.error?.attemptedModels||[]):new u(r,o,{statusCode:e,retryable:e>=500})}}isRetryable(e){if(e instanceof u)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);}};exports.AuthenticationError=x;exports.ChainExecutionError=f;exports.ConfigurationError=k;exports.FileProcessingError=I;exports.Hydra=D;exports.HydraError=u;exports.InvalidSchemaError=M;exports.NetworkError=w;exports.QuotaExceededError=v;exports.RateLimitError=E;exports.ServerError=g;exports.TimeoutError=R;exports.ValidationError=P;exports.WorkerError=O;exports.generateSignature=T;exports.getKeyPrefix=U;exports.isHydraError=_;exports.isRetryableError=q;exports.isValidSecretKey=C;
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 K=typeof process<"u"&&process.versions?.node;async function H(n){let{createHash:e}=await import('crypto');return e("sha256").update(n).digest("hex")}async function G(n,e,t,r,o){let{createHmac:s}=await import('crypto'),c=[e.toUpperCase(),t,o.toString(),r].join(`
2
- `),d=await H(n);return s("sha256",d).update(c).digest("hex")}async function L(n){let t=new TextEncoder().encode(n),r=await crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(r)).map(s=>s.toString(16).padStart(2,"0")).join("")}async function W(n,e,t,r,o){let s=new TextEncoder,c=[e.toUpperCase(),t,o.toString(),r].join(`
3
- `),d=await L(n),a=s.encode(d),i=await crypto.subtle.importKey("raw",a,{name:"HMAC",hash:"SHA-256"},false,["sign"]),m=await crypto.subtle.sign("HMAC",i,s.encode(c));return Array.from(new Uint8Array(m)).map(p=>p.toString(16).padStart(2,"0")).join("")}async function T(n,e,t,r,o){return K?G(n,e,t,r,o):W(n,e,t,r,o)}function U(n){return n.slice(0,12)}function C(n){return typeof n=="string"&&n.startsWith("hyd_sk_")&&n.length>=20}var u=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;}},k=class extends u{constructor(e){super(e,"CONFIGURATION_ERROR",{retryable:false}),this.name="ConfigurationError";}},x=class extends u{constructor(e,t){super(e,"AUTH_ERROR",{retryable:false,statusCode:t}),this.name="AuthenticationError";}},E=class extends u{constructor(e,t=6e4){super(e,"RATE_LIMITED",{retryable:true,statusCode:429}),this.name="RateLimitError",this.retryAfterMs=t;}},R=class extends u{constructor(e){super(`Request timed out after ${e}ms`,"TIMEOUT",{retryable:true}),this.name="TimeoutError",this.timeoutMs=e;}},w=class extends u{constructor(e,t){super(e,"NETWORK_ERROR",{retryable:true,cause:t}),this.name="NetworkError";}},f=class extends u{constructor(e,t=[]){super(e,"CHAIN_FAILED",{retryable:false}),this.name="ChainExecutionError",this.attemptedModels=t;}},P=class extends u{constructor(e,t=[]){super(e,"VALIDATION_ERROR",{retryable:false}),this.name="ValidationError",this.validationErrors=t;}},g=class extends u{constructor(e,t=500){super(e,"SERVER_ERROR",{retryable:true,statusCode:t}),this.name="ServerError";}},v=class extends u{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;}},O=class extends u{constructor(e,t){super(e,"WORKER_ERROR",{retryable:true}),this.name="WorkerError",this.workerDurationMs=t;}},I=class extends u{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;}},M=class extends u{constructor(e,t){super(e,"INVALID_SCHEMA",{retryable:false}),this.name="InvalidSchemaError",this.schemaPath=t;}};function _(n){return n instanceof u}function q(n){return _(n)?n.retryable:false}var F="http://localhost:3000",$=6e5,B=3,N=[1e3,2e3,5e3];function J(n){let e=n.trim(),t=e.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);t&&(e=t[1].trim());let r=e.indexOf("{"),o=e.indexOf("["),s;if(r===-1&&o===-1)return e;r===-1?s=o:o===-1?s=r:s=Math.min(r,o);let c=e[s],d=c==="{"?"}":"]",a=0,i=false,m=false,y=-1;for(let h=s;h<e.length;h++){let b=e[h];if(m){m=false;continue}if(b==="\\"&&i){m=true;continue}if(b==='"'&&!m){i=!i;continue}if(!i){if(b===c)a++;else if(b===d&&(a--,a===0)){y=h;break}}}if(y!==-1)return e.slice(s,y+1);let p=e.lastIndexOf(d);return p>s?e.slice(s,p+1):e}var D=class n{constructor(e){if(!e.secretKey)throw new k("secretKey is required");if(!C(e.secretKey))throw new k("Invalid secret key format. Expected format: hyd_sk_<key>");this.secretKey=e.secretKey,this.baseUrl=(e.baseUrl||F).replace(/\/$/,""),this.timeout=e.timeout??$,this.maxRetries=e.maxRetries??B,this.keyPrefix=U(e.secretKey),this.debug=e.debug??false,this.debug&&this.log("Initialized",{baseUrl:this.baseUrl,timeout:this.timeout});}async generate(e){if(e.useOptimized!==false)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)});}return this.generateLegacy(e)}async generateOptimized(e){let t=Date.now(),r="/api/v1/generate/token",o=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",o);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 f("Token generation failed - no token or worker URL returned",[]);let c=JSON.stringify({token:s.token,prompt:e.prompt,systemPrompt:e.options?.systemPrompt,timeout:e.timeout??this.timeout}),a=await(await this.fetchWithTimeout(s.workerUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:c},e.timeout??this.timeout)).json();if(!a.success||!a.content)return {success:false,data:{valid:[],invalid:[]},meta:{cached:false,modelUsed:a.modelId||"",tokensUsed:a.tokensUsed||{input:0,output:0},latencyMs:Date.now()-t,attemptedModels:[a.modelId||""]},error:{code:a.errorCode||"GENERATION_FAILED",message:a.errorMessage||"Generation failed",retryable:false}};let i=[],m=[],y=J(a.content);try{let h=JSON.parse(y);Array.isArray(h)?i=h:i=[h];}catch{i=[y];}a.completionToken&&this.cacheCompletionAsync({completionToken:a.completionToken,content:a.content,tokensUsed:a.tokensUsed||{input:0,output:0},finishReason:a.finishReason||"stop",chainId:e.chainId,modelUsed:a.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(h=>{this.debug&&this.log("cacheCompletion failed (non-blocking)",{error:h});});let p=Date.now()-t;return this.debug&&this.log("generate success (optimized 3-step)",{latencyMs:p,modelUsed:a.modelId,tokensUsed:a.tokensUsed}),{success:true,data:{valid:i,invalid:m},meta:{cached:false,modelUsed:a.modelId,tokensUsed:a.tokensUsed||{input:0,output:0},latencyMs:p,attemptedModels:[a.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",o=JSON.stringify({chainId:e.chainId,prompt:e.prompt,schema:e.schema,noCache:e.noCache,files:e.files,options:{...e.options,timeout:e.timeout??this.timeout}}),s=null;for(let a=0;a<=this.maxRetries;a++)try{let i=await this.makeAuthenticatedRequest(r,"POST",o,e.timeout),m=Date.now()-t;return this.debug&&this.log("generate success (legacy)",{latencyMs:m,attempt:a+1}),i.meta&&(i.meta.latencyMs=m),i}catch(i){if(s=i instanceof Error?i:new Error(String(i)),this.debug&&this.log(`generate attempt ${a+1} failed`,{error:s.message}),!this.isRetryable(i)||a>=this.maxRetries)break;let m=N[Math.min(a,N.length-1)];i instanceof E&&i.retryAfterMs>m?await this.sleep(i.retryAfterMs):await this.sleep(m);}let c=Date.now()-t,d=s instanceof R;return {success:false,data:{valid:[],invalid:[]},meta:{cached:false,modelUsed:"",tokensUsed:{input:0,output:0},latencyMs:c,attemptedModels:[]},error:{code:d?"TIMEOUT":"REQUEST_FAILED",message:s?.message||"Request failed after all retries",retryable:false}}}async generateStream(e,t){let r="/api/v1/generate/stream",o=JSON.stringify({chainId:e.chainId,prompt:e.prompt,schema:e.schema,options:e.options});try{let s=Date.now(),c=await T(this.secretKey,"POST",r,o,s),d=await this.fetchWithTimeout(`${this.baseUrl}${r}`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"text/event-stream","x-hydra-signature":c,"x-hydra-timestamp":s.toString(),"x-hydra-key":this.keyPrefix},body:o},e.timeout??this.timeout);if(!d.ok){let p=await d.json();throw this.parseError(d.status,p)}let a=d.body?.getReader();if(!a)throw new w("Response body is not readable");let i=new TextDecoder,m="",y=[];for(;;){let{done:p,value:h}=await a.read();if(p)break;m+=i.decode(h,{stream:!0});let b=m.split(`
4
- `);m=b.pop()||"";let A="";for(let S of b){if(S.startsWith("event: ")){A=S.slice(7).trim();continue}if(S.startsWith("data: ")){let j=S.slice(6);if(j==="[DONE]")break;try{let l=JSON.parse(j);switch(A){case "start":this.debug&&this.log("Stream started",l);break;case "object":l.object!==void 0&&(y.push(l.object),t.onObject?.(l.object,l.index)),t.onChunk&&l.object&&t.onChunk(JSON.stringify(l.object));break;case "array_start":t.onArrayStart?.();break;case "complete":t.onComplete&&t.onComplete({objects:y,objectCount:l.objectCount,tokensUsed:l.tokensUsed,cached:l.cached});break;case "error":t.onError&&t.onError(new f(l.error||"Stream error",["STREAM_ERROR"]));break;default:l.chunk&&t.onChunk?.(l.chunk);}}catch{}A="";}}}}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 g(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 g(t.error?.message||"Failed to list chains");return t.data}async getUsage(e){let t=new URLSearchParams;e?.startDate&&t.set("startDate",e.startDate.toISOString()),e?.endDate&&t.set("endDate",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 g(s.error?.message||"Failed to get usage");return s.data}async health(){let e=Date.now();try{let t=await this.fetchWithTimeout(`${this.baseUrl}/api/health`,{method:"GET"},5e3),r=Date.now()-e,o=await t.json().catch(()=>({}));return {ok:t.ok,latencyMs:r,version:o.version}}catch{return {ok:false,latencyMs:Date.now()-e}}}withConfig(e){return new n({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 g(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 g(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 g(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 g(r.error?.message||"Failed to update webhook");return r.data}async healthDetailed(){return (await fetch(`${this.baseUrl}/api/v1/health/detailed`,{headers:{Accept:"application/json"}})).json()}async generateBatch(e,t,r={}){let o=await this.makeAuthenticatedRequest("/api/v1/generate/batch","POST",JSON.stringify({chainId:e,items:t,continueOnError:r.continueOnError??true}));if(!o.success||!o.data)throw new f(o.error?.message||"Batch generation failed",[]);return o.data}async makeAuthenticatedRequest(e,t,r,o){let s=Date.now(),c=await T(this.secretKey,t,e,r,s),d=await this.fetchWithTimeout(`${this.baseUrl}${e}`,{method:t,headers:{"Content-Type":"application/json",Accept:"application/json","x-hydra-signature":c,"x-hydra-timestamp":s.toString(),"x-hydra-key":this.keyPrefix},body:r},o??this.timeout),a=await d.json();if(!d.ok)throw this.parseError(d.status,a);return a}async fetchWithTimeout(e,t,r=this.timeout){let o=new AbortController,s=setTimeout(()=>o.abort(),r);try{return await fetch(e,{...t,signal:o.signal})}catch(c){throw c instanceof Error&&c.name==="AbortError"?new R(r):c instanceof Error?new w(c.message,c):new w(String(c))}finally{clearTimeout(s);}}parseError(e,t){let r=typeof t?.error=="string"?t.error:t?.error?.message||`HTTP ${e}`,o=t?.error?.code||"UNKNOWN";switch(e){case 401:return new x(r,e);case 429:let s=t?.retryAfterMs||6e4;return new E(r,s);case 500:case 502:case 503:case 504:return new g(r,e);default:return o==="CHAIN_FAILED"?new f(r,t?.error?.attemptedModels||[]):new u(r,o,{statusCode:e,retryable:e>=500})}}isRetryable(e){if(e instanceof u)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);}};export{x as AuthenticationError,f as ChainExecutionError,k as ConfigurationError,I as FileProcessingError,D as Hydra,u as HydraError,M as InvalidSchemaError,w as NetworkError,v as QuotaExceededError,E as RateLimitError,g as ServerError,R as TimeoutError,P as ValidationError,O as WorkerError,T as generateSignature,U as getKeyPrefix,_ as isHydraError,q as isRetryableError,C as isValidSecretKey};
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.0",
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": "npm run build",
32
+ "prepublishOnly": "bun run build",
33
33
  "clean": "rimraf dist",
34
- "publish:check": "npm pack --dry-run",
35
- "release": "npm run build && npm publish --access public",
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": "npm run build && npm publish --tag beta"
37
+ "release:beta": "bun run build && bun publish --access public --tag beta"
38
38
  },
39
39
  "keywords": [
40
40
  "ai",