@tryhakim/voice 1.0.0

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.
Files changed (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +188 -0
  3. package/dist/audio/speech-stream-ws.d.ts +40 -0
  4. package/dist/audio/speech-stream-ws.d.ts.map +1 -0
  5. package/dist/audio/speech-stream-ws.js +565 -0
  6. package/dist/audio/speech-stream-ws.js.map +1 -0
  7. package/dist/audio/speech.d.ts +46 -0
  8. package/dist/audio/speech.d.ts.map +1 -0
  9. package/dist/audio/speech.js +125 -0
  10. package/dist/audio/speech.js.map +1 -0
  11. package/dist/audio/stream.d.ts +38 -0
  12. package/dist/audio/stream.d.ts.map +1 -0
  13. package/dist/audio/stream.js +452 -0
  14. package/dist/audio/stream.js.map +1 -0
  15. package/dist/audio/to-blob.d.ts +10 -0
  16. package/dist/audio/to-blob.d.ts.map +1 -0
  17. package/dist/audio/to-blob.js +78 -0
  18. package/dist/audio/to-blob.js.map +1 -0
  19. package/dist/audio/transcriptions.d.ts +35 -0
  20. package/dist/audio/transcriptions.d.ts.map +1 -0
  21. package/dist/audio/transcriptions.js +105 -0
  22. package/dist/audio/transcriptions.js.map +1 -0
  23. package/dist/audio/translate-stream-ws.d.ts +37 -0
  24. package/dist/audio/translate-stream-ws.d.ts.map +1 -0
  25. package/dist/audio/translate-stream-ws.js +665 -0
  26. package/dist/audio/translate-stream-ws.js.map +1 -0
  27. package/dist/audio/translate.d.ts +36 -0
  28. package/dist/audio/translate.d.ts.map +1 -0
  29. package/dist/audio/translate.js +39 -0
  30. package/dist/audio/translate.js.map +1 -0
  31. package/dist/audio/voices.d.ts +47 -0
  32. package/dist/audio/voices.d.ts.map +1 -0
  33. package/dist/audio/voices.js +91 -0
  34. package/dist/audio/voices.js.map +1 -0
  35. package/dist/chat/completions.d.ts +76 -0
  36. package/dist/chat/completions.d.ts.map +1 -0
  37. package/dist/chat/completions.js +114 -0
  38. package/dist/chat/completions.js.map +1 -0
  39. package/dist/chat/index.d.ts +2 -0
  40. package/dist/chat/index.d.ts.map +1 -0
  41. package/dist/chat/index.js +2 -0
  42. package/dist/chat/index.js.map +1 -0
  43. package/dist/chat/sse.d.ts +12 -0
  44. package/dist/chat/sse.d.ts.map +1 -0
  45. package/dist/chat/sse.js +142 -0
  46. package/dist/chat/sse.js.map +1 -0
  47. package/dist/client.d.ts +63 -0
  48. package/dist/client.d.ts.map +1 -0
  49. package/dist/client.js +80 -0
  50. package/dist/client.js.map +1 -0
  51. package/dist/errors.d.ts +99 -0
  52. package/dist/errors.d.ts.map +1 -0
  53. package/dist/errors.js +158 -0
  54. package/dist/errors.js.map +1 -0
  55. package/dist/index.d.ts +27 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +25 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/jobs.d.ts +34 -0
  60. package/dist/jobs.d.ts.map +1 -0
  61. package/dist/jobs.js +62 -0
  62. package/dist/jobs.js.map +1 -0
  63. package/dist/observability.d.ts +28 -0
  64. package/dist/observability.d.ts.map +1 -0
  65. package/dist/observability.js +107 -0
  66. package/dist/observability.js.map +1 -0
  67. package/dist/settings.d.ts +49 -0
  68. package/dist/settings.d.ts.map +1 -0
  69. package/dist/settings.js +84 -0
  70. package/dist/settings.js.map +1 -0
  71. package/dist/transport.d.ts +82 -0
  72. package/dist/transport.d.ts.map +1 -0
  73. package/dist/transport.js +289 -0
  74. package/dist/transport.js.map +1 -0
  75. package/dist/types.d.ts +969 -0
  76. package/dist/types.d.ts.map +1 -0
  77. package/dist/types.js +11 -0
  78. package/dist/types.js.map +1 -0
  79. package/dist/usage.d.ts +43 -0
  80. package/dist/usage.d.ts.map +1 -0
  81. package/dist/usage.js +85 -0
  82. package/dist/usage.js.map +1 -0
  83. package/dist/version.d.ts +13 -0
  84. package/dist/version.d.ts.map +1 -0
  85. package/dist/version.js +13 -0
  86. package/dist/version.js.map +1 -0
  87. package/dist/webhooks.d.ts +84 -0
  88. package/dist/webhooks.d.ts.map +1 -0
  89. package/dist/webhooks.js +157 -0
  90. package/dist/webhooks.js.map +1 -0
  91. package/package.json +57 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hakim AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,188 @@
1
+ # @tryhakim/voice
2
+
3
+ Official Node.js / TypeScript client for the [Hakim](https://tryhakim.ai) API.
4
+
5
+ > **Status:** `1.0.0` — stable.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @tryhakim/voice
11
+ ```
12
+
13
+ Requires Node.js **>= 18** (uses native `fetch`, `AbortController`,
14
+ `FormData`, `Blob`). Works on Bun and Deno too.
15
+
16
+ ## Quickstart
17
+
18
+ ```ts
19
+ import { Hakim } from '@tryhakim/voice';
20
+ import { writeFile } from 'node:fs/promises';
21
+
22
+ const hakim = new Hakim({
23
+ apiKey: process.env.HAKIM_API_KEY!,
24
+ });
25
+
26
+ // 0. Discover available voices. Each row carries an `id` (stable,
27
+ // unique — recommended for production traffic) and a `slug` (e.g.
28
+ // `priya-hi`) for readability.
29
+ const voices = await hakim.audio.voices.list();
30
+ const priya = voices.find((v) => v.slug === 'priya-hi');
31
+ if (!priya) throw new Error('voice not provisioned');
32
+
33
+ // 1. Synthesize speech (buffered). Pass `id` rather than `slug` —
34
+ // slugs may collide between a preset and an org clone with the
35
+ // same name, and they shift if a voice is renamed.
36
+ const speech = await hakim.audio.speech.create({
37
+ model: 'hakim-fast-v1',
38
+ input: 'مرحبا بك في منصتنا',
39
+ voice: priya.id,
40
+ response_format: 'mp3',
41
+ });
42
+ await writeFile('out.mp3', speech.audio);
43
+ console.log('billed', speech.usageCharacters, 'characters');
44
+
45
+ // 2. Streaming synthesis (low-latency start).
46
+ const { stream } = await hakim.audio.speech.stream({
47
+ model: 'hakim-fast-v1',
48
+ input: 'مرحبا',
49
+ voice: priya.id,
50
+ });
51
+ for await (const chunk of stream) {
52
+ process.stdout.write(chunk);
53
+ }
54
+
55
+ // 3. Transcribe audio.
56
+ import { readFile } from 'node:fs/promises';
57
+ const audio = await readFile('clip.wav');
58
+ const result = await hakim.audio.transcriptions.create({
59
+ file: audio,
60
+ model: 'hakim-arab-v2',
61
+ language: 'ar',
62
+ response_format: 'json',
63
+ });
64
+ if (result.kind === 'sync_json') console.log(result.data.text);
65
+
66
+ // 4. List voices.
67
+ const { data: voices } = await hakim.audio.voices.list({ language: 'ar' });
68
+
69
+ // 5. Usage summary.
70
+ const summary = await hakim.usage.summary();
71
+ console.log(`${summary.tts.characters} / ${summary.tts.included} TTS characters used`);
72
+ ```
73
+
74
+ ## Chat completions (`hakim.chat.completions`)
75
+
76
+ OpenAI-compatible chat API backed by Hakim's Arabic-first LLM
77
+ (`hakim-chat-v1`). Drop-in compatible with any code that targets
78
+ the OpenAI Chat Completions reference — swap the base URL and key
79
+ and you're done.
80
+
81
+ ```ts
82
+ // Non-streaming.
83
+ const completion = await hakim.chat.completions.create({
84
+ model: 'hakim-chat-v1',
85
+ messages: [
86
+ { role: 'system', content: 'أنت مساعد عربي مفيد.' },
87
+ { role: 'user', content: 'اكتب قصيدة قصيرة عن البحر.' },
88
+ ],
89
+ temperature: 0.7,
90
+ });
91
+ console.log(completion.choices[0].message.content);
92
+ console.log('billed', completion.usage_headers?.credits, 'credits');
93
+
94
+ // Streaming (SSE). Stream replies token-by-token; the final chunk
95
+ // carries the rolled-up `usage` block.
96
+ const { stream } = await hakim.chat.completions.stream({
97
+ model: 'hakim-chat-v1',
98
+ messages: [{ role: 'user', content: 'مرحبا!' }],
99
+ });
100
+ for await (const chunk of stream) {
101
+ const delta = chunk.choices[0]?.delta.content;
102
+ if (delta) process.stdout.write(delta);
103
+ if (chunk.usage) console.log('\n[usage]', chunk.usage);
104
+ }
105
+ ```
106
+
107
+ ### Reasoning / chain-of-thought
108
+
109
+ `hakim-chat-v1` is a thinking-capable model. The chain-of-thought
110
+ trace is **off by default** — it adds 10–50× latency and burns
111
+ completion tokens that don't reach your UI in time.
112
+
113
+ Opt in on **non-streaming** requests only:
114
+
115
+ ```ts
116
+ const completion = await hakim.chat.completions.create({
117
+ model: 'hakim-chat-v1',
118
+ messages: [{ role: 'user', content: 'حلّ ٢٤ × ١٧ خطوة بخطوة.' }],
119
+ reasoning: { enabled: true },
120
+ });
121
+ console.log(completion.choices[0].message.reasoning); // chain-of-thought
122
+ console.log(completion.choices[0].message.content); // final answer
123
+ ```
124
+
125
+ Streaming requests with `reasoning: { enabled: true }` are
126
+ rejected with a `400 invalid_request_error` — real-time agents
127
+ cannot afford the latency cost. Make a non-streaming request if
128
+ you need CoT.
129
+
130
+ ## Configuration
131
+
132
+ ```ts
133
+ new Hakim({
134
+ apiKey: 'hk_live_...', // or HAKIM_API_KEY env
135
+ baseURL: 'https://api.tryhakim.ai', // or HAKIM_BASE_URL env
136
+ timeoutMs: 120_000, // per-request; TTS can be slow
137
+ maxRetries: 2, // on 5xx / 429 / connection errors
138
+ userAgentSuffix: 'my-app/1.0.0', // appended to the SDK UA
139
+ });
140
+ ```
141
+
142
+ ## Feature highlights
143
+
144
+ - **Native `fetch`** — no `axios` / `undici` dependency; bundle stays small.
145
+ - **Typed request/response shapes** mirroring the Hakim API schema.
146
+ - **Uniform error model**: every non-2xx surfaces as a `HakimError`
147
+ subclass (`AuthenticationError`, `RateLimitError`, `QuotaExceededError`,
148
+ `IdempotencyConflictError`, …). All errors carry `status`, `type`,
149
+ `code`, and `requestId` for support tickets.
150
+ - **Automatic retries** with jittered exponential backoff on `5xx`,
151
+ `429`, `408`, and low-level connection errors. Honors `Retry-After`.
152
+ - **Auto-`Idempotency-Key`** on mutating JSON calls (can be overridden
153
+ per call via `{ idempotencyKey: '…' }`).
154
+ - **`X-Request-Id`** generated per call and echoed back on both
155
+ success (`response.meta.requestId`) and failure (`err.requestId`).
156
+ - **Streaming TTS** via `AsyncIterable<Uint8Array>` — no need to touch
157
+ the raw `ReadableStream`.
158
+ - **AbortSignal support** on every call.
159
+ - **Cursor pagination** auto-walked by `usage.eventsIter`.
160
+
161
+ ## Errors
162
+
163
+ ```ts
164
+ import {
165
+ Hakim,
166
+ HakimError,
167
+ RateLimitError,
168
+ QuotaExceededError,
169
+ } from '@tryhakim/voice';
170
+
171
+ try {
172
+ await hakim.audio.speech.create({ ... });
173
+ } catch (err) {
174
+ if (err instanceof RateLimitError && err.retryAfterMs) {
175
+ await new Promise((r) => setTimeout(r, err.retryAfterMs));
176
+ } else if (err instanceof QuotaExceededError) {
177
+ console.error('plan exhausted — upgrade at /app/settings/billing');
178
+ } else if (err instanceof HakimError) {
179
+ console.error(`[${err.requestId}] ${err.type}/${err.code}: ${err.message}`);
180
+ } else {
181
+ throw err;
182
+ }
183
+ }
184
+ ```
185
+
186
+ ## License
187
+
188
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Realtime TTS over WebSocket (`WSS /v1/audio/speech/stream`).
3
+ *
4
+ * The SDK translates server frames into friendly `SpeechStreamEvent`s
5
+ * and emits caller `sendSpeech({...})` requests as `speech.create`
6
+ * frames upstream.
7
+ *
8
+ * Like the STT counterpart in `./stream.ts`, this helper stays
9
+ * dependency-free. We prefer `globalThis.WebSocket` when present
10
+ * (Node 22+, Bun, Deno, browsers). On Node 18/20 the global isn't
11
+ * there — we dynamically import `ws` at runtime and surface a
12
+ * helpful error if it's missing.
13
+ *
14
+ * The handle is lazy: the socket opens on the first `sendSpeech` /
15
+ * iteration of `events` / `audio` / `close`. This keeps the hot
16
+ * path fast and avoids dangling connections when a caller
17
+ * constructs a handle but bails before using it.
18
+ *
19
+ * Binary chunks coming back from the server are surfaced as
20
+ * `speech.audio` events (each carries a `chunk: Uint8Array`) so
21
+ * consumers can iterate a single stream of events instead of
22
+ * juggling a separate audio channel. The companion `audio` iterable
23
+ * filters to those chunks for callers who only want raw PCM.
24
+ */
25
+ import type { Transport } from '../transport.js';
26
+ import type { SpeechStreamEvent, SpeechStreamHandle, SpeechStreamOptions } from '../types.js';
27
+ export declare function openSpeechStream(transport: Transport, opts?: SpeechStreamOptions): SpeechStreamHandle;
28
+ export declare function translateFrame(frame: unknown): SpeechStreamEvent | null;
29
+ export declare function buildSessionUpdate(session: Partial<SpeechStreamOptions>): {
30
+ type: 'session.update';
31
+ session: Record<string, unknown>;
32
+ } | null;
33
+ declare function buildWsUrl(baseURL: string): string;
34
+ export declare const __internals: {
35
+ translateFrame: typeof translateFrame;
36
+ buildSessionUpdate: typeof buildSessionUpdate;
37
+ buildWsUrl: typeof buildWsUrl;
38
+ };
39
+ export {};
40
+ //# sourceMappingURL=speech-stream-ws.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"speech-stream-ws.d.ts","sourceRoot":"","sources":["../../src/audio/speech-stream-ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAEV,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EAEpB,MAAM,aAAa,CAAC;AAcrB,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,SAAS,EACpB,IAAI,GAAE,mBAAwB,GAC7B,kBAAkB,CAmTpB;AAyGD,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,GAAG,IAAI,CAqEvE;AAED,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,CAAC,mBAAmB,CAAC,GACpC;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAAG,IAAI,CAQrE;AAED,iBAAS,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAK3C;AAqED,eAAO,MAAM,WAAW;;;;CAAqD,CAAC"}