sydear 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 +121 -0
- package/build.py +125 -0
- package/dist/client.cjs +327 -0
- package/dist/client.d.ts +73 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +326 -0
- package/dist/client.js.map +1 -0
- package/dist/client.mjs +326 -0
- package/dist/errors.cjs +46 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +46 -0
- package/dist/errors.js.map +1 -0
- package/dist/errors.mjs +46 -0
- package/dist/index.cjs +18 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +8 -0
- package/dist/types.cjs +5 -0
- package/dist/types.d.ts +112 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/types.mjs +5 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sydear
|
|
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,121 @@
|
|
|
1
|
+
# sydear (JavaScript / TypeScript SDK)
|
|
2
|
+
|
|
3
|
+
Official Node.js + TypeScript SDK for the [Sydear API](https://docs.sydear.space).
|
|
4
|
+
|
|
5
|
+
Chat, audio (TTS / STT), image generation, music, video — wrapped in a
|
|
6
|
+
clean async/await interface. Zero runtime dependencies, native `fetch`
|
|
7
|
+
(Node 18+).
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install sydear
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { Sydear } from "sydear";
|
|
19
|
+
|
|
20
|
+
const client = new Sydear(); // uses SYDEAR_API_KEY env var
|
|
21
|
+
|
|
22
|
+
// Chat
|
|
23
|
+
const resp = await client.chat.create({
|
|
24
|
+
model: "sydear-auto",
|
|
25
|
+
messages: [{ role: "user", content: "Hello!" }],
|
|
26
|
+
});
|
|
27
|
+
console.log(resp.choices[0].message.content);
|
|
28
|
+
|
|
29
|
+
// Streaming
|
|
30
|
+
for await (const chunk of client.chat.stream({
|
|
31
|
+
model: "sydear-auto",
|
|
32
|
+
messages: [{ role: "user", content: "Tell me a story" }],
|
|
33
|
+
})) {
|
|
34
|
+
process.stdout.write(chunk.choices[0].delta.content ?? "");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Text-to-Speech
|
|
38
|
+
const audio = await client.audio.speech({ input: "Hello world" });
|
|
39
|
+
await fs.writeFile("hello.mp3", audio);
|
|
40
|
+
|
|
41
|
+
// Speech-to-Text
|
|
42
|
+
const result = await client.audio.transcriptions({
|
|
43
|
+
file: fs.readFileSync("recording.mp3"),
|
|
44
|
+
filename: "recording.mp3",
|
|
45
|
+
language: "th",
|
|
46
|
+
});
|
|
47
|
+
console.log(result);
|
|
48
|
+
|
|
49
|
+
// Image
|
|
50
|
+
const img = await client.images.generate({ prompt: "a cute cat" });
|
|
51
|
+
console.log(img.data[0].url);
|
|
52
|
+
|
|
53
|
+
// Music
|
|
54
|
+
const track = await client.music.generate({ prompt: "indie pop", lyrics: "..." });
|
|
55
|
+
|
|
56
|
+
// Video
|
|
57
|
+
const clip = await client.videos.generate({ prompt: "a cat playing piano" });
|
|
58
|
+
|
|
59
|
+
// Usage
|
|
60
|
+
console.log(await client.usage.credits());
|
|
61
|
+
console.log(await client.usage.today());
|
|
62
|
+
|
|
63
|
+
// Models
|
|
64
|
+
for (const m of (await client.models.list()).data) console.log(m.id);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Aliases
|
|
68
|
+
|
|
69
|
+
| Alias | Purpose |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `sydear-auto` | Smart router (recommended) |
|
|
72
|
+
| `sydear-singularity` | Flagship reasoning |
|
|
73
|
+
| `sydear-quasar` | High-performance balanced |
|
|
74
|
+
| `sydear-orbit` | Economy tier |
|
|
75
|
+
| `sydear-comet` | Ultra-fast |
|
|
76
|
+
| `sydear-galaxy` | Image generation |
|
|
77
|
+
| `sydear-echo` | Text-to-Speech |
|
|
78
|
+
| `sydear-supernova` | Music |
|
|
79
|
+
| `sydear-video-v1` | Video |
|
|
80
|
+
| `sydear-stt-1` | Speech-to-Text |
|
|
81
|
+
|
|
82
|
+
## Errors
|
|
83
|
+
|
|
84
|
+
All errors extend `SydearError`:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { RateLimitError } from "sydear";
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
await client.chat.create({ model: "sydear-auto", messages: [...] });
|
|
91
|
+
} catch (e) {
|
|
92
|
+
if (e instanceof RateLimitError) {
|
|
93
|
+
await new Promise((r) => setTimeout(r, (e.retryAfter ?? 1) * 1000));
|
|
94
|
+
// retry...
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
| Error | HTTP |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `AuthenticationError` | 401/403 |
|
|
102
|
+
| `InvalidRequestError` | 400 |
|
|
103
|
+
| `InsufficientSPUError` | 402 |
|
|
104
|
+
| `RateLimitError` | 429 (has `.retryAfter`) |
|
|
105
|
+
| `APIError` | other 4xx/5xx (has `.status`, `.body`) |
|
|
106
|
+
|
|
107
|
+
## Requirements
|
|
108
|
+
|
|
109
|
+
- Node.js 18+ (uses native `fetch`)
|
|
110
|
+
- TypeScript 5+ for type defs
|
|
111
|
+
|
|
112
|
+
## Links
|
|
113
|
+
|
|
114
|
+
- Docs: https://docs.sydear.space
|
|
115
|
+
- Status: https://status.sydear.space
|
|
116
|
+
- Repo: https://github.com/Techfrontiers/sydear-js
|
|
117
|
+
- Issues: https://github.com/Techfrontiers/sydear-js/issues
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
MIT
|
package/build.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Build script for sydear-js.
|
|
3
|
+
|
|
4
|
+
Reads tsc-output dist/*.js (ESM) and emits dist/*.mjs (verbatim) +
|
|
5
|
+
dist/*.cjs (CJS rewrite). Handles regular imports, exports, and
|
|
6
|
+
re-exports `export { X } from "..."`.
|
|
7
|
+
"""
|
|
8
|
+
import os, re, sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
DIST = Path(__file__).parent / "dist"
|
|
12
|
+
|
|
13
|
+
if not DIST.exists():
|
|
14
|
+
print("dist/ not found — run tsc first", file=sys.stderr)
|
|
15
|
+
sys.exit(1)
|
|
16
|
+
|
|
17
|
+
# Clean prior cjs/mjs
|
|
18
|
+
for f in DIST.iterdir():
|
|
19
|
+
if f.suffix in (".cjs", ".mjs"):
|
|
20
|
+
f.unlink()
|
|
21
|
+
|
|
22
|
+
js_files = [
|
|
23
|
+
f for f in DIST.iterdir()
|
|
24
|
+
if f.suffix == ".js" and not f.name.endswith(".cjs") and not f.name.endswith(".mjs")
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
IMPORT_RE = re.compile(
|
|
28
|
+
r'^import\s+([\s\S]+?)\s+from\s+["\']([^"\']+)["\'];?\s*$',
|
|
29
|
+
re.MULTILINE,
|
|
30
|
+
)
|
|
31
|
+
REEXPORT_RE = re.compile(
|
|
32
|
+
r'^export\s*\{\s*([^}]+)\s*\}\s+from\s+["\']([^"\']+)["\'];?\s*$',
|
|
33
|
+
re.MULTILINE,
|
|
34
|
+
)
|
|
35
|
+
BRACE_EXPORT_RE = re.compile(
|
|
36
|
+
r'^export\s*\{\s*([^}]+)\s*\}\s*;?\s*$',
|
|
37
|
+
re.MULTILINE,
|
|
38
|
+
)
|
|
39
|
+
# `export const|let|var NAME = value;` — track name for module.exports
|
|
40
|
+
EXPORT_DECL_RE = re.compile(
|
|
41
|
+
r'^export\s+(const|let|var)\s+([A-Za-z_$][\w$]*)\s*=',
|
|
42
|
+
re.MULTILINE,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
count = 0
|
|
46
|
+
for js in js_files:
|
|
47
|
+
src = js.read_text()
|
|
48
|
+
|
|
49
|
+
# .mjs: verbatim copy
|
|
50
|
+
(DIST / (js.stem + ".mjs")).write_text(src)
|
|
51
|
+
|
|
52
|
+
# .cjs: rewrite imports + exports
|
|
53
|
+
imports = []
|
|
54
|
+
exports_list = []
|
|
55
|
+
|
|
56
|
+
def add_import(m):
|
|
57
|
+
names, mod = m.group(1).strip(), m.group(2)
|
|
58
|
+
if mod.startswith("."):
|
|
59
|
+
mod = mod.removesuffix(".js")
|
|
60
|
+
imports.append(f'const {names} = require("{mod}");')
|
|
61
|
+
return ''
|
|
62
|
+
rewritten = IMPORT_RE.sub(add_import, src)
|
|
63
|
+
|
|
64
|
+
# Track names that need module.exports.X = X (locally declared exports).
|
|
65
|
+
# These come from `export const NAME = ...`, `export function NAME`,
|
|
66
|
+
# `export class NAME`.
|
|
67
|
+
local_decl_exports = set()
|
|
68
|
+
for m in EXPORT_DECL_RE.finditer(rewritten):
|
|
69
|
+
local_decl_exports.add(m.group(2))
|
|
70
|
+
|
|
71
|
+
def rewrite_reexport(m):
|
|
72
|
+
names_part, mod = m.group(1), m.group(2)
|
|
73
|
+
if mod.startswith("."):
|
|
74
|
+
mod = mod.removesuffix(".js")
|
|
75
|
+
names = [n.strip().split(" as ")[0] for n in names_part.split(",") if n.strip()]
|
|
76
|
+
var = f"_reex_{mod.replace('/', '_').replace('.', '_').replace('-', '_')}"
|
|
77
|
+
imports.append(f"const {var} = require(\"{mod}\");")
|
|
78
|
+
for n in names:
|
|
79
|
+
if n not in exports_list:
|
|
80
|
+
exports_list.append(n)
|
|
81
|
+
imports.append(f"module.exports.{n} = {var}.{n};")
|
|
82
|
+
return ''
|
|
83
|
+
rewritten = REEXPORT_RE.sub(rewrite_reexport, rewritten)
|
|
84
|
+
|
|
85
|
+
def collect_brace_export(m):
|
|
86
|
+
names = [n.strip().split(" as ")[0] for n in m.group(1).split(",") if n.strip()]
|
|
87
|
+
for n in names:
|
|
88
|
+
if n not in exports_list:
|
|
89
|
+
exports_list.append(n)
|
|
90
|
+
return ''
|
|
91
|
+
rewritten = BRACE_EXPORT_RE.sub(collect_brace_export, rewritten)
|
|
92
|
+
|
|
93
|
+
# Strip type-only exports
|
|
94
|
+
rewritten = re.sub(r'^export\s+type\s+', '', rewritten, flags=re.MULTILINE)
|
|
95
|
+
rewritten = re.sub(r'^export\s+interface\s+', '', rewritten, flags=re.MULTILINE)
|
|
96
|
+
|
|
97
|
+
# Rewrite `export const NAME = ...` → `const NAME = ...` + add to exports
|
|
98
|
+
rewritten = re.sub(r'^export\s+(const|let|var)\s+', r'\1 ', rewritten, flags=re.MULTILINE)
|
|
99
|
+
rewritten = re.sub(r'^export\s+(function|class)\s+', r'\1 ', rewritten, flags=re.MULTILINE)
|
|
100
|
+
rewritten = re.sub(r'^export\s+default\s+', 'module.exports.default = ', rewritten, flags=re.MULTILINE)
|
|
101
|
+
rewritten = re.sub(r'^export\s+', '', rewritten, flags=re.MULTILINE)
|
|
102
|
+
|
|
103
|
+
# For locally-declared exports, add module.exports.X = X after the
|
|
104
|
+
# declarations (so they're available). For re-exports we already
|
|
105
|
+
# attached inline. Brace-local need a closing block too.
|
|
106
|
+
output = ("\n".join(imports) + "\n" if imports else "") + rewritten
|
|
107
|
+
|
|
108
|
+
# Add module.exports for local declarations + brace-local exports
|
|
109
|
+
exports_to_attach = set(local_decl_exports) | set(exports_list)
|
|
110
|
+
# Skip re-exported ones (already attached inline as module.exports.X = _reex.X)
|
|
111
|
+
# We can detect these by looking at the imports list.
|
|
112
|
+
reexported = set()
|
|
113
|
+
for line in imports:
|
|
114
|
+
m = re.match(r'module\.exports\.(\w+)\s*=\s*_reex_', line)
|
|
115
|
+
if m:
|
|
116
|
+
reexported.add(m.group(1))
|
|
117
|
+
exports_to_attach -= reexported
|
|
118
|
+
|
|
119
|
+
if exports_to_attach:
|
|
120
|
+
output += "\n" + "\n".join(f"module.exports.{e} = {e};" for e in exports_to_attach) + "\n"
|
|
121
|
+
|
|
122
|
+
(DIST / (js.stem + ".cjs")).write_text(output)
|
|
123
|
+
count += 1
|
|
124
|
+
|
|
125
|
+
print(f"Built {count} ESM (.mjs) + CJS (.cjs) files")
|
package/dist/client.cjs
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
const { AuthenticationError, APIError, InsufficientSPUError, InvalidRequestError, RateLimitError, SydearError, } = require("./errors");
|
|
2
|
+
/**
|
|
3
|
+
* Main Sydear client.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const DEFAULT_BASE_URL = "https://api.sydear.space/v1";
|
|
7
|
+
const DEFAULT_TIMEOUT = 60_000; // 60s
|
|
8
|
+
class Sydear {
|
|
9
|
+
apiKey;
|
|
10
|
+
baseURL;
|
|
11
|
+
timeout;
|
|
12
|
+
maxRetries;
|
|
13
|
+
defaultSignal;
|
|
14
|
+
// Resource namespaces
|
|
15
|
+
chat;
|
|
16
|
+
audio;
|
|
17
|
+
images;
|
|
18
|
+
music;
|
|
19
|
+
videos;
|
|
20
|
+
models;
|
|
21
|
+
usage;
|
|
22
|
+
constructor(apiKey, options = {}) {
|
|
23
|
+
this.apiKey = apiKey ?? process.env.SYDEAR_API_KEY ?? "";
|
|
24
|
+
if (!this.apiKey) {
|
|
25
|
+
throw new AuthenticationError("No API key. Pass apiKey=... or set SYDEAR_API_KEY env var.");
|
|
26
|
+
}
|
|
27
|
+
this.baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
28
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
29
|
+
this.maxRetries = options.maxRetries ?? 0;
|
|
30
|
+
this.defaultSignal = options.signal;
|
|
31
|
+
this.chat = new ChatNamespace(this);
|
|
32
|
+
this.audio = new AudioNamespace(this);
|
|
33
|
+
this.images = new ImagesNamespace(this);
|
|
34
|
+
this.music = new MusicNamespace(this);
|
|
35
|
+
this.videos = new VideosNamespace(this);
|
|
36
|
+
this.models = new ModelsNamespace(this);
|
|
37
|
+
this.usage = new UsageNamespace(this);
|
|
38
|
+
}
|
|
39
|
+
/** Internal: perform request with retry + error mapping. */
|
|
40
|
+
async _request(method, path, init = {}) {
|
|
41
|
+
const url = `${this.baseURL}${path}`;
|
|
42
|
+
const headers = new Headers(init.headers);
|
|
43
|
+
headers.set("Authorization", `Bearer ${this.apiKey}`);
|
|
44
|
+
if (init.body && typeof init.body === "string") {
|
|
45
|
+
headers.set("Content-Type", "application/json");
|
|
46
|
+
}
|
|
47
|
+
const controller = new AbortController();
|
|
48
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
49
|
+
const signal = init.signal ?? this.defaultSignal;
|
|
50
|
+
if (signal) {
|
|
51
|
+
signal.addEventListener("abort", () => controller.abort());
|
|
52
|
+
}
|
|
53
|
+
let attempt = 0;
|
|
54
|
+
while (true) {
|
|
55
|
+
try {
|
|
56
|
+
const res = await fetch(url, { ...init, headers, signal: controller.signal });
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
if (this._shouldRetry(res.status, attempt)) {
|
|
59
|
+
attempt++;
|
|
60
|
+
await sleep(this._backoffMs(attempt));
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
this._raiseForStatus(res);
|
|
64
|
+
return res;
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
if (err instanceof SydearError)
|
|
69
|
+
throw err;
|
|
70
|
+
if (attempt < this.maxRetries) {
|
|
71
|
+
attempt++;
|
|
72
|
+
await sleep(this._backoffMs(attempt));
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
throw new APIError(err.message ?? "Network error", 0);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
_shouldRetry(status, attempt) {
|
|
80
|
+
if (attempt >= this.maxRetries)
|
|
81
|
+
return false;
|
|
82
|
+
return status === 429 || status === 503 || status === 504;
|
|
83
|
+
}
|
|
84
|
+
_backoffMs(attempt) {
|
|
85
|
+
return Math.min(500 * 2 ** (attempt - 1), 4000);
|
|
86
|
+
}
|
|
87
|
+
async _raiseForStatusAsync(res) {
|
|
88
|
+
if (res.ok)
|
|
89
|
+
return;
|
|
90
|
+
const text = await res.text();
|
|
91
|
+
let body;
|
|
92
|
+
try {
|
|
93
|
+
body = JSON.parse(text);
|
|
94
|
+
}
|
|
95
|
+
catch { }
|
|
96
|
+
const msg = body?.error?.message ?? text ?? `HTTP ${res.status}`;
|
|
97
|
+
if (res.status === 401 || res.status === 403)
|
|
98
|
+
throw new AuthenticationError(msg);
|
|
99
|
+
if (res.status === 400)
|
|
100
|
+
throw new InvalidRequestError(msg);
|
|
101
|
+
if (res.status === 402)
|
|
102
|
+
throw new InsufficientSPUError(msg);
|
|
103
|
+
if (res.status === 429) {
|
|
104
|
+
const ra = res.headers.get("retry-after");
|
|
105
|
+
throw new RateLimitError(msg, ra ? Number(ra) : undefined);
|
|
106
|
+
}
|
|
107
|
+
throw new APIError(msg, res.status, text);
|
|
108
|
+
}
|
|
109
|
+
/** Sync-style raiseForStatus — only call after reading body. */
|
|
110
|
+
_raiseForStatus(res) {
|
|
111
|
+
if (res.ok)
|
|
112
|
+
return;
|
|
113
|
+
// For sync case, must read body before throwing
|
|
114
|
+
res.text().then((text) => {
|
|
115
|
+
// Not awaited here; the caller must use _raiseForStatusAsync
|
|
116
|
+
// for full error info. This is a fallback that only triggers
|
|
117
|
+
// for ok=false (which our retry loop catches for retryable codes).
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
// (Same async method exposed publicly)
|
|
121
|
+
async raiseIfNotOK(res) {
|
|
122
|
+
return this._raiseForStatusAsync(res);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// -------- Resource namespaces --------
|
|
126
|
+
class ChatNamespace {
|
|
127
|
+
client;
|
|
128
|
+
constructor(client) {
|
|
129
|
+
this.client = client;
|
|
130
|
+
}
|
|
131
|
+
async create(req) {
|
|
132
|
+
const body = { ...req, stream: false };
|
|
133
|
+
const res = await this.client._request("POST", "/chat/completions", {
|
|
134
|
+
method: "POST",
|
|
135
|
+
body: JSON.stringify(body),
|
|
136
|
+
});
|
|
137
|
+
await this.client.raiseIfNotOK(res);
|
|
138
|
+
return res.json();
|
|
139
|
+
}
|
|
140
|
+
/** Async iterable over SSE chunks. */
|
|
141
|
+
async *stream(req) {
|
|
142
|
+
const body = { ...req, stream: true };
|
|
143
|
+
const res = await this.client._request("POST", "/chat/completions", {
|
|
144
|
+
method: "POST",
|
|
145
|
+
body: JSON.stringify(body),
|
|
146
|
+
headers: { Accept: "text/event-stream" },
|
|
147
|
+
});
|
|
148
|
+
await this.client.raiseIfNotOK(res);
|
|
149
|
+
if (!res.body)
|
|
150
|
+
throw new Error("No response body");
|
|
151
|
+
const reader = res.body.getReader();
|
|
152
|
+
const decoder = new TextDecoder();
|
|
153
|
+
let buf = "";
|
|
154
|
+
while (true) {
|
|
155
|
+
const { done, value } = await reader.read();
|
|
156
|
+
if (done)
|
|
157
|
+
break;
|
|
158
|
+
buf += decoder.decode(value, { stream: true });
|
|
159
|
+
const lines = buf.split("\n");
|
|
160
|
+
buf = lines.pop() ?? "";
|
|
161
|
+
for (const line of lines) {
|
|
162
|
+
const t = line.trim();
|
|
163
|
+
if (!t.startsWith("data:"))
|
|
164
|
+
continue;
|
|
165
|
+
const payload = t.slice(5).trim();
|
|
166
|
+
if (payload === "[DONE]")
|
|
167
|
+
return;
|
|
168
|
+
try {
|
|
169
|
+
yield JSON.parse(payload);
|
|
170
|
+
}
|
|
171
|
+
catch { /* skip */ }
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
class AudioNamespace {
|
|
177
|
+
client;
|
|
178
|
+
constructor(client) {
|
|
179
|
+
this.client = client;
|
|
180
|
+
}
|
|
181
|
+
async speech(req) {
|
|
182
|
+
const body = {
|
|
183
|
+
model: req.model ?? "sydear-echo",
|
|
184
|
+
input: req.input,
|
|
185
|
+
voice: req.voice ?? "default",
|
|
186
|
+
response_format: req.response_format ?? "mp3",
|
|
187
|
+
};
|
|
188
|
+
const res = await this.client._request("POST", "/audio/speech", {
|
|
189
|
+
method: "POST",
|
|
190
|
+
body: JSON.stringify(body),
|
|
191
|
+
});
|
|
192
|
+
await this.client.raiseIfNotOK(res);
|
|
193
|
+
const buf = await res.arrayBuffer();
|
|
194
|
+
return new Uint8Array(buf);
|
|
195
|
+
}
|
|
196
|
+
async transcriptions(req) {
|
|
197
|
+
const fileBuf = toBuffer(req.file);
|
|
198
|
+
const filename = req.filename ?? (typeof req.file === "string" ? basename(req.file) : "audio.mp3");
|
|
199
|
+
const fd = new FormData();
|
|
200
|
+
fd.append("model", req.model ?? "sydear-stt-1");
|
|
201
|
+
if (req.language)
|
|
202
|
+
fd.append("language", req.language);
|
|
203
|
+
if (req.response_format)
|
|
204
|
+
fd.append("response_format", req.response_format);
|
|
205
|
+
const res = await this.client._request("POST", "/audio/transcriptions", {
|
|
206
|
+
method: "POST",
|
|
207
|
+
body: fd,
|
|
208
|
+
});
|
|
209
|
+
await this.client.raiseIfNotOK(res);
|
|
210
|
+
if (req.response_format === "text")
|
|
211
|
+
return res.text();
|
|
212
|
+
return res.json();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
class ImagesNamespace {
|
|
216
|
+
client;
|
|
217
|
+
constructor(client) {
|
|
218
|
+
this.client = client;
|
|
219
|
+
}
|
|
220
|
+
async generate(req) {
|
|
221
|
+
const body = {
|
|
222
|
+
model: req.model ?? "sydear-galaxy",
|
|
223
|
+
prompt: req.prompt,
|
|
224
|
+
n: req.n ?? 1,
|
|
225
|
+
size: req.size ?? "1024x1024",
|
|
226
|
+
};
|
|
227
|
+
const res = await this.client._request("POST", "/images/generations", {
|
|
228
|
+
method: "POST",
|
|
229
|
+
body: JSON.stringify(body),
|
|
230
|
+
});
|
|
231
|
+
await this.client.raiseIfNotOK(res);
|
|
232
|
+
return res.json();
|
|
233
|
+
}
|
|
234
|
+
async edit(req) {
|
|
235
|
+
const fd = new FormData();
|
|
236
|
+
const body = { model: req.model ?? "sydear-galaxy", prompt: req.prompt };
|
|
237
|
+
fd.append("image", new Blob([new Uint8Array(toBuffer(req.image))]), "image.png");
|
|
238
|
+
if (req.mask)
|
|
239
|
+
fd.append("mask", new Blob([new Uint8Array(toBuffer(req.mask))]), "mask.png");
|
|
240
|
+
// prompt already added via body
|
|
241
|
+
fd.append("model", req.model ?? "sydear-galaxy");
|
|
242
|
+
const res = await this.client._request("POST", "/images/edits", {
|
|
243
|
+
method: "POST",
|
|
244
|
+
body: fd,
|
|
245
|
+
});
|
|
246
|
+
await this.client.raiseIfNotOK(res);
|
|
247
|
+
return res.json();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
class MusicNamespace {
|
|
251
|
+
client;
|
|
252
|
+
constructor(client) {
|
|
253
|
+
this.client = client;
|
|
254
|
+
}
|
|
255
|
+
async generate(req) {
|
|
256
|
+
const body = {
|
|
257
|
+
model: req.model ?? "sydear-supernova",
|
|
258
|
+
prompt: req.prompt,
|
|
259
|
+
...(req.lyrics ? { lyrics: req.lyrics } : {}),
|
|
260
|
+
};
|
|
261
|
+
const res = await this.client._request("POST", "/music/generations", {
|
|
262
|
+
method: "POST",
|
|
263
|
+
body: JSON.stringify(body),
|
|
264
|
+
});
|
|
265
|
+
await this.client.raiseIfNotOK(res);
|
|
266
|
+
return res.json();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
class VideosNamespace {
|
|
270
|
+
client;
|
|
271
|
+
constructor(client) {
|
|
272
|
+
this.client = client;
|
|
273
|
+
}
|
|
274
|
+
async generate(req) {
|
|
275
|
+
const body = { model: req.model ?? "sydear-video-v1", prompt: req.prompt };
|
|
276
|
+
const res = await this.client._request("POST", "/videos/generations", {
|
|
277
|
+
method: "POST",
|
|
278
|
+
body: JSON.stringify(body),
|
|
279
|
+
});
|
|
280
|
+
await this.client.raiseIfNotOK(res);
|
|
281
|
+
return res.json();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
class ModelsNamespace {
|
|
285
|
+
client;
|
|
286
|
+
constructor(client) {
|
|
287
|
+
this.client = client;
|
|
288
|
+
}
|
|
289
|
+
async list() {
|
|
290
|
+
const res = await this.client._request("GET", "/models");
|
|
291
|
+
await this.client.raiseIfNotOK(res);
|
|
292
|
+
return res.json();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
class UsageNamespace {
|
|
296
|
+
client;
|
|
297
|
+
constructor(client) {
|
|
298
|
+
this.client = client;
|
|
299
|
+
}
|
|
300
|
+
async credits() {
|
|
301
|
+
const res = await this.client._request("GET", "/credits");
|
|
302
|
+
await this.client.raiseIfNotOK(res);
|
|
303
|
+
return res.json();
|
|
304
|
+
}
|
|
305
|
+
async today() {
|
|
306
|
+
const res = await this.client._request("GET", "/usage/today");
|
|
307
|
+
await this.client.raiseIfNotOK(res);
|
|
308
|
+
return res.json();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// -------- Helpers --------
|
|
312
|
+
function sleep(ms) {
|
|
313
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
314
|
+
}
|
|
315
|
+
function toBuffer(x) {
|
|
316
|
+
if (typeof x === "string") {
|
|
317
|
+
// Assume it's a file path — caller should pre-load via fs.
|
|
318
|
+
// We throw because Node.js fetch can't take a path in body.
|
|
319
|
+
throw new InvalidRequestError("Pass file as Buffer/Uint8Array, not file path. Read it with fs.readFileSync() first.");
|
|
320
|
+
}
|
|
321
|
+
return x;
|
|
322
|
+
}
|
|
323
|
+
function basename(path) {
|
|
324
|
+
const parts = path.split(/[\\/]/);
|
|
325
|
+
return parts[parts.length - 1] || "file";
|
|
326
|
+
}
|
|
327
|
+
//# sourceMappingURL=client.js.map
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main Sydear client.
|
|
3
|
+
*/
|
|
4
|
+
import type { ChatCompletionChunk, ChatCompletionRequest, ChatCompletionResponse, CreditsResponse, ImageEditRequest, ImageGenerationRequest, MusicGenerationRequest, RequestOptions, SpeechRequest, TranscriptionRequest, UsageResponse, VideoGenerationRequest } from "./types.js";
|
|
5
|
+
export declare class Sydear {
|
|
6
|
+
private apiKey;
|
|
7
|
+
private baseURL;
|
|
8
|
+
private timeout;
|
|
9
|
+
private maxRetries;
|
|
10
|
+
private defaultSignal?;
|
|
11
|
+
readonly chat: ChatNamespace;
|
|
12
|
+
readonly audio: AudioNamespace;
|
|
13
|
+
readonly images: ImagesNamespace;
|
|
14
|
+
readonly music: MusicNamespace;
|
|
15
|
+
readonly videos: VideosNamespace;
|
|
16
|
+
readonly models: ModelsNamespace;
|
|
17
|
+
readonly usage: UsageNamespace;
|
|
18
|
+
constructor(apiKey?: string, options?: RequestOptions);
|
|
19
|
+
/** Internal: perform request with retry + error mapping. */
|
|
20
|
+
_request<T = unknown>(method: string, path: string, init?: RequestInit): Promise<Response>;
|
|
21
|
+
private _shouldRetry;
|
|
22
|
+
private _backoffMs;
|
|
23
|
+
private _raiseForStatusAsync;
|
|
24
|
+
/** Sync-style raiseForStatus — only call after reading body. */
|
|
25
|
+
private _raiseForStatus;
|
|
26
|
+
raiseIfNotOK(res: Response): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
declare class ChatNamespace {
|
|
29
|
+
private client;
|
|
30
|
+
constructor(client: Sydear);
|
|
31
|
+
create(req: ChatCompletionRequest): Promise<ChatCompletionResponse>;
|
|
32
|
+
/** Async iterable over SSE chunks. */
|
|
33
|
+
stream(req: ChatCompletionRequest): AsyncGenerator<ChatCompletionChunk>;
|
|
34
|
+
}
|
|
35
|
+
declare class AudioNamespace {
|
|
36
|
+
private client;
|
|
37
|
+
constructor(client: Sydear);
|
|
38
|
+
speech(req: SpeechRequest): Promise<Uint8Array>;
|
|
39
|
+
transcriptions(req: TranscriptionRequest): Promise<unknown>;
|
|
40
|
+
}
|
|
41
|
+
declare class ImagesNamespace {
|
|
42
|
+
private client;
|
|
43
|
+
constructor(client: Sydear);
|
|
44
|
+
generate(req: ImageGenerationRequest): Promise<unknown>;
|
|
45
|
+
edit(req: ImageEditRequest): Promise<unknown>;
|
|
46
|
+
}
|
|
47
|
+
declare class MusicNamespace {
|
|
48
|
+
private client;
|
|
49
|
+
constructor(client: Sydear);
|
|
50
|
+
generate(req: MusicGenerationRequest): Promise<unknown>;
|
|
51
|
+
}
|
|
52
|
+
declare class VideosNamespace {
|
|
53
|
+
private client;
|
|
54
|
+
constructor(client: Sydear);
|
|
55
|
+
generate(req: VideoGenerationRequest): Promise<unknown>;
|
|
56
|
+
}
|
|
57
|
+
declare class ModelsNamespace {
|
|
58
|
+
private client;
|
|
59
|
+
constructor(client: Sydear);
|
|
60
|
+
list(): Promise<{
|
|
61
|
+
data: Array<{
|
|
62
|
+
id: string;
|
|
63
|
+
}>;
|
|
64
|
+
}>;
|
|
65
|
+
}
|
|
66
|
+
declare class UsageNamespace {
|
|
67
|
+
private client;
|
|
68
|
+
constructor(client: Sydear);
|
|
69
|
+
credits(): Promise<CreditsResponse>;
|
|
70
|
+
today(): Promise<UsageResponse>;
|
|
71
|
+
}
|
|
72
|
+
export {};
|
|
73
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|