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 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