addisai 0.1.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 +300 -0
- package/dist/index.cjs +1465 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +718 -0
- package/dist/index.d.ts +718 -0
- package/dist/index.js +1434 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Addis 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,300 @@
|
|
|
1
|
+
# addisai
|
|
2
|
+
|
|
3
|
+
The official [Addis AI](https://addisai.com) SDK for Node.js — voice (text‑to‑speech), chat/LLM with system prompts, personas and function calling, speech‑to‑text, and translation for **Amharic (`am`)** and **Afan Oromo (`om`)**.
|
|
4
|
+
|
|
5
|
+
Designed to feel familiar if you've used the OpenAI, Anthropic, or ElevenLabs SDKs.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install addisai
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node.js 18+ (also runs on Deno, Bun, Cloudflare Workers, and Vercel Edge).
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import AddisAI from "addisai";
|
|
17
|
+
|
|
18
|
+
const addis = new AddisAI({ apiKey: process.env.ADDIS_API_KEY });
|
|
19
|
+
|
|
20
|
+
// Text-to-speech
|
|
21
|
+
const clip = await addis.voice.generate({
|
|
22
|
+
voiceId: "am-hiwot",
|
|
23
|
+
text: "ሰላም፣ እንኳን ወደ አዲስ ኤአይ በደህና መጡ።",
|
|
24
|
+
language: "am",
|
|
25
|
+
});
|
|
26
|
+
await clip.toFile("welcome.mp3");
|
|
27
|
+
|
|
28
|
+
// Chat
|
|
29
|
+
const res = await addis.chat.completions.create({
|
|
30
|
+
language: "am",
|
|
31
|
+
messages: [{ role: "user", content: "ስለ አዲስ አበባ ንገረኝ" }],
|
|
32
|
+
});
|
|
33
|
+
console.log(res.choices[0].message.content);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
CommonJS:
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
const { AddisAI } = require("addisai");
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
const addis = new AddisAI({
|
|
46
|
+
apiKey: process.env.ADDIS_API_KEY, // or set ADDIS_API_KEY
|
|
47
|
+
timeout: 60_000, // ms (voice.generate raises this to ≥95s automatically)
|
|
48
|
+
maxRetries: 2, // automatic backoff on 408/409/425/429/5xx
|
|
49
|
+
defaultHeaders: {},
|
|
50
|
+
logLevel: "warn", // "off" | "error" | "warn" | "info" | "debug" (never logs secrets)
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The API key is read from the `apiKey` option or the `ADDIS_API_KEY` environment variable. It is never logged and is redacted from errors. The SDK refuses to run in a browser unless you pass `dangerouslyAllowBrowser: true` — keep your key server‑side.
|
|
55
|
+
|
|
56
|
+
## Voice (text‑to‑speech)
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
const clip = await addis.voice.generate({
|
|
60
|
+
voiceId: "am-hiwot",
|
|
61
|
+
text: "ሰላም ለዓለም።",
|
|
62
|
+
language: "am", // must match the voice
|
|
63
|
+
outputFormat: "mp3_44100", // "mp3_44100" | "wav_44100" | "pcm_16000"
|
|
64
|
+
voiceSettings: { speed: 50, stability: 50, similarity: 50, style: 0 }, // 0–100
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
clip.id; // "clip_…"
|
|
68
|
+
clip.audioUrl; // short-lived signed playback URL
|
|
69
|
+
clip.durationSeconds;
|
|
70
|
+
clip.usage; // { creditsUsed, creditsRemaining, currency: "ETB", … }
|
|
71
|
+
|
|
72
|
+
const bytes = await clip.arrayBuffer(); // fetch audio into memory
|
|
73
|
+
await clip.toFile("out.mp3"); // …or write to disk
|
|
74
|
+
|
|
75
|
+
import { play } from "addisai";
|
|
76
|
+
await play(clip); // local playback (needs ffmpeg/mpv)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Idempotent billing.** `voice.generate` requires an idempotency key. The SDK generates one automatically and reuses it across retries, so a network retry is never billed twice. Pass your own for full control:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
await addis.voice.generate({ voiceId: "am-hiwot", text, language: "am", clientRequestId: myId });
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Reusing a key with the *same* inputs replays the existing clip (`clip.meta.idempotentReplay === true`, no new charge); reusing it with *changed* inputs throws `IdempotencyConflictError`.
|
|
86
|
+
|
|
87
|
+
### Voice catalog, estimates, usage, history
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
const voices = await addis.voices.list({ language: "am", gender: "female" });
|
|
91
|
+
const preview = await addis.voices.preview("am-hiwot");
|
|
92
|
+
|
|
93
|
+
const est = await addis.voice.estimate({ voiceId: "am-hiwot", text, language: "am" });
|
|
94
|
+
if (!est.canGenerate) console.log("Top up:", est.estimatedCost, est.currency);
|
|
95
|
+
|
|
96
|
+
const wallet = await addis.voice.usage();
|
|
97
|
+
|
|
98
|
+
for await (const c of addis.voice.clips.list({ language: "am" })) {
|
|
99
|
+
console.log(c.id, c.text); // auto-paginates
|
|
100
|
+
}
|
|
101
|
+
await addis.voice.clips.delete("clip_123");
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Migrating from ElevenLabs
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const clip = await addis.textToSpeech.convert("am-hiwot", { text: "ሰላም", language: "am" });
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Chat / LLM
|
|
111
|
+
|
|
112
|
+
Drop‑in OpenAI‑compatible chat, plus Addis extensions for language, system prompts, personas, and function calling.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const res = await addis.chat.completions.create({
|
|
116
|
+
language: "am", // "am" | "om"
|
|
117
|
+
system: "Answer in concise bullet points.", // behaviour; does not change identity
|
|
118
|
+
persona: "You are RecipeBot by AcmeCorp.", // optional branded identity
|
|
119
|
+
messages: [{ role: "user", content: "የእንጀራ አሰራር አስተምረኝ" }],
|
|
120
|
+
temperature: 0.7,
|
|
121
|
+
max_tokens: 1200,
|
|
122
|
+
});
|
|
123
|
+
console.log(res.choices[0].message.content);
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
> The model is selected by Addis AI; the response reports the model id `addis-1-alef`.
|
|
127
|
+
|
|
128
|
+
### Function calling (tools)
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const res = await addis.chat.completions.create({
|
|
132
|
+
language: "am",
|
|
133
|
+
messages: [{ role: "user", content: "Check order 123 and summarize it." }],
|
|
134
|
+
tools: [{
|
|
135
|
+
type: "function",
|
|
136
|
+
function: {
|
|
137
|
+
name: "get_order_status",
|
|
138
|
+
description: "Fetch order status by order ID.",
|
|
139
|
+
parameters: { type: "object", properties: { order_id: { type: "string" } }, required: ["order_id"] },
|
|
140
|
+
},
|
|
141
|
+
}],
|
|
142
|
+
tool_choice: "auto",
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (res.choices[0].finish_reason === "tool_calls") {
|
|
146
|
+
// execute res.choices[0].message.tool_calls, append a { role: "tool" } result, call again
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Or let the SDK run the loop for you:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
const final = await addis.chat.runTools({
|
|
154
|
+
language: "am",
|
|
155
|
+
messages: [{ role: "user", content: "Check order 123 and summarize it." }],
|
|
156
|
+
tools: [{
|
|
157
|
+
type: "function",
|
|
158
|
+
function: {
|
|
159
|
+
name: "get_order_status",
|
|
160
|
+
description: "Fetch order status by order ID.",
|
|
161
|
+
parameters: { type: "object", properties: { order_id: { type: "string" } }, required: ["order_id"] },
|
|
162
|
+
function: async ({ order_id }) => db.getOrder(order_id), // ← your implementation
|
|
163
|
+
},
|
|
164
|
+
}],
|
|
165
|
+
maxToolRoundtrips: 5,
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Attachments & audio input
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
import { fileFromPath } from "addisai";
|
|
173
|
+
|
|
174
|
+
const res = await addis.chat.completions.create({
|
|
175
|
+
language: "am",
|
|
176
|
+
messages: [{ role: "user", content: "Describe this image." }],
|
|
177
|
+
attachments: [{ file: await fileFromPath("photo.jpg") }],
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const voiceCmd = await addis.chat.completions.create({
|
|
181
|
+
language: "am",
|
|
182
|
+
messages: [{ role: "user", content: "" }],
|
|
183
|
+
audio: await fileFromPath("command.wav"),
|
|
184
|
+
});
|
|
185
|
+
voiceCmd.transcription?.clean; // the transcript
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Streaming (beta)
|
|
189
|
+
|
|
190
|
+
`stream: true` returns a `ChatStream` — an async‑iterable of OpenAI‑style chunks with accumulators and cancellation:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
const stream = await addis.chat.completions.create({ language: "am", messages, stream: true });
|
|
194
|
+
|
|
195
|
+
for await (const chunk of stream) {
|
|
196
|
+
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// or, instead of iterating:
|
|
200
|
+
const text = await stream.finalText(); // concatenated text
|
|
201
|
+
const completion = await stream.finalCompletion(); // assembled ChatCompletion (with usage)
|
|
202
|
+
stream.transcription; // present if audio input was sent
|
|
203
|
+
stream.abort(); // cancel mid-stream
|
|
204
|
+
stream.toReadableStream(); // re-encode as an SSE byte stream
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Pass `signal` to cancel via your own `AbortController`:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
const ac = new AbortController();
|
|
211
|
+
const stream = await addis.chat.completions.create({ language: "am", messages, stream: true }, { signal: ac.signal });
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Streaming is beta and not available with tools; non‑streaming is recommended for production.
|
|
215
|
+
|
|
216
|
+
## Speech‑to‑text & translation
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import { fileFromPath } from "addisai";
|
|
220
|
+
|
|
221
|
+
const t = await addis.speech.transcribe({ audio: await fileFromPath("call.wav"), language: "am" });
|
|
222
|
+
console.log(t.text);
|
|
223
|
+
|
|
224
|
+
const out = await addis.translate.create({ text: "Hello, how are you?", from: "en", to: "am" });
|
|
225
|
+
console.log(out.text);
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
STT supports `am | om | en | ha | sw` (max 25 MB / 120 s). Translation supports `am | om | en`.
|
|
229
|
+
|
|
230
|
+
## Legacy audio (deprecated)
|
|
231
|
+
|
|
232
|
+
The old `/audio` endpoint is exposed only as a migration bridge. Prefer `voice.generate`.
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
// ⚠️ deprecated — use addis.voice.generate instead
|
|
236
|
+
const out = await addis.legacy.audio.generate({ text: "ሰላም", language: "am" });
|
|
237
|
+
await out.toFile("legacy.wav"); // base64 audio, decoded for you
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
It logs a one‑time deprecation warning, is capped at 1500 characters, and lacks the durable clips, signed URLs, and idempotent billing of `voice.generate`.
|
|
241
|
+
|
|
242
|
+
The legacy endpoint also streams audio. `stream()` returns an `AudioStream` — an async‑iterable of `Uint8Array` chunks that normalizes both legacy encodings into one byte stream:
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
const audio = await addis.legacy.audio.stream({ text: "ሰላም", language: "am" });
|
|
246
|
+
for await (const chunk of audio) { /* Uint8Array */ }
|
|
247
|
+
// or:
|
|
248
|
+
await audio.toFile("legacy.wav");
|
|
249
|
+
const bytes = await audio.arrayBuffer();
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
(`addis.voice.stream(...)` exists with the same `AudioStream` shape and will work once the API enables streaming synthesis; today it raises `NotSupportedError`.)
|
|
253
|
+
|
|
254
|
+
## Errors
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
import {
|
|
258
|
+
AddisAIError, APIError, AuthenticationError, RateLimitError,
|
|
259
|
+
InsufficientCreditsError, IdempotencyConflictError, NotFoundError,
|
|
260
|
+
} from "addisai";
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
await addis.voice.generate({ voiceId: "am-hiwot", text, language: "am" });
|
|
264
|
+
} catch (err) {
|
|
265
|
+
if (err instanceof InsufficientCreditsError) showTopUp(err.availableBalance);
|
|
266
|
+
else if (err instanceof RateLimitError) await wait(err.retryAfter ?? 1);
|
|
267
|
+
else if (err instanceof APIError) console.error(err.status, err.code, err.message, err.details);
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
| Class | HTTP | Notes |
|
|
272
|
+
| --- | --- | --- |
|
|
273
|
+
| `BadRequestError` | 400 | |
|
|
274
|
+
| `AuthenticationError` | 401 | |
|
|
275
|
+
| `InsufficientCreditsError` | 402 | `.availableBalance` |
|
|
276
|
+
| `PermissionDeniedError` | 403 | |
|
|
277
|
+
| `NotFoundError` | 404 | |
|
|
278
|
+
| `ConflictError` / `IdempotencyConflictError` / `GenerationInProgressError` | 409 | `.retryAfter` |
|
|
279
|
+
| `UnprocessableEntityError` | 422 | `.details[]` |
|
|
280
|
+
| `RateLimitError` | 429 | `.retryAfter` `.limit` `.remaining` `.reset` |
|
|
281
|
+
| `InternalServerError` | ≥500 | |
|
|
282
|
+
| `APIConnectionError` / `APIConnectionTimeoutError` | — | network / timeout |
|
|
283
|
+
|
|
284
|
+
## Per‑request options
|
|
285
|
+
|
|
286
|
+
Every method accepts a final options argument:
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
await addis.voice.generate(params, {
|
|
290
|
+
timeout: 120_000,
|
|
291
|
+
maxRetries: 0,
|
|
292
|
+
signal: controller.signal,
|
|
293
|
+
idempotencyKey: "my-key",
|
|
294
|
+
headers: { "x-trace-id": traceId },
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## License
|
|
299
|
+
|
|
300
|
+
MIT
|