@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.
- package/LICENSE +21 -0
- package/README.md +188 -0
- package/dist/audio/speech-stream-ws.d.ts +40 -0
- package/dist/audio/speech-stream-ws.d.ts.map +1 -0
- package/dist/audio/speech-stream-ws.js +565 -0
- package/dist/audio/speech-stream-ws.js.map +1 -0
- package/dist/audio/speech.d.ts +46 -0
- package/dist/audio/speech.d.ts.map +1 -0
- package/dist/audio/speech.js +125 -0
- package/dist/audio/speech.js.map +1 -0
- package/dist/audio/stream.d.ts +38 -0
- package/dist/audio/stream.d.ts.map +1 -0
- package/dist/audio/stream.js +452 -0
- package/dist/audio/stream.js.map +1 -0
- package/dist/audio/to-blob.d.ts +10 -0
- package/dist/audio/to-blob.d.ts.map +1 -0
- package/dist/audio/to-blob.js +78 -0
- package/dist/audio/to-blob.js.map +1 -0
- package/dist/audio/transcriptions.d.ts +35 -0
- package/dist/audio/transcriptions.d.ts.map +1 -0
- package/dist/audio/transcriptions.js +105 -0
- package/dist/audio/transcriptions.js.map +1 -0
- package/dist/audio/translate-stream-ws.d.ts +37 -0
- package/dist/audio/translate-stream-ws.d.ts.map +1 -0
- package/dist/audio/translate-stream-ws.js +665 -0
- package/dist/audio/translate-stream-ws.js.map +1 -0
- package/dist/audio/translate.d.ts +36 -0
- package/dist/audio/translate.d.ts.map +1 -0
- package/dist/audio/translate.js +39 -0
- package/dist/audio/translate.js.map +1 -0
- package/dist/audio/voices.d.ts +47 -0
- package/dist/audio/voices.d.ts.map +1 -0
- package/dist/audio/voices.js +91 -0
- package/dist/audio/voices.js.map +1 -0
- package/dist/chat/completions.d.ts +76 -0
- package/dist/chat/completions.d.ts.map +1 -0
- package/dist/chat/completions.js +114 -0
- package/dist/chat/completions.js.map +1 -0
- package/dist/chat/index.d.ts +2 -0
- package/dist/chat/index.d.ts.map +1 -0
- package/dist/chat/index.js +2 -0
- package/dist/chat/index.js.map +1 -0
- package/dist/chat/sse.d.ts +12 -0
- package/dist/chat/sse.d.ts.map +1 -0
- package/dist/chat/sse.js +142 -0
- package/dist/chat/sse.js.map +1 -0
- package/dist/client.d.ts +63 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +80 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +99 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +158 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/jobs.d.ts +34 -0
- package/dist/jobs.d.ts.map +1 -0
- package/dist/jobs.js +62 -0
- package/dist/jobs.js.map +1 -0
- package/dist/observability.d.ts +28 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +107 -0
- package/dist/observability.js.map +1 -0
- package/dist/settings.d.ts +49 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +84 -0
- package/dist/settings.js.map +1 -0
- package/dist/transport.d.ts +82 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +289 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +969 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/dist/usage.d.ts +43 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +85 -0
- package/dist/usage.js.map +1 -0
- package/dist/version.d.ts +13 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +13 -0
- package/dist/version.js.map +1 -0
- package/dist/webhooks.d.ts +84 -0
- package/dist/webhooks.d.ts.map +1 -0
- package/dist/webhooks.js +157 -0
- package/dist/webhooks.js.map +1 -0
- 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"}
|