corent-sdk 0.1.0 → 0.3.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/README.md +129 -4
- package/dist/index.cjs +288 -16
- package/dist/index.d.cts +262 -7
- package/dist/index.d.ts +262 -7
- package/dist/index.js +288 -16
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Corent SDK
|
|
2
2
|
|
|
3
|
-
One API for AI **image, video, and
|
|
3
|
+
One API for AI **image, video, voice, and text** generation. You pick a quality tier; Corent's router picks the best live model, reroutes failures, verifies the output, and returns the exact charge on every response. Failed generations are never billed.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install corent-sdk
|
|
@@ -26,6 +26,9 @@ console.log(video.url, video.resolution, video.costCents);
|
|
|
26
26
|
|
|
27
27
|
const speech = await client.speech.generate("Welcome to Corent.");
|
|
28
28
|
console.log(speech.url, speech.costCents);
|
|
29
|
+
|
|
30
|
+
const answer = await client.text.generate("Name three uses for a paperclip.", { tier: "premium" });
|
|
31
|
+
console.log(answer.text, answer.costCents);
|
|
29
32
|
```
|
|
30
33
|
|
|
31
34
|
## What the SDK handles for you
|
|
@@ -35,14 +38,136 @@ console.log(speech.url, speech.costCents);
|
|
|
35
38
|
- **Backoff** — 429/5xx retried with `Retry-After` respected.
|
|
36
39
|
- **Honest receipts** — `width`/`height` are the *measured* pixels of the delivered file; `costCents` is the exact charge.
|
|
37
40
|
|
|
41
|
+
## Pick a model yourself (direct access)
|
|
42
|
+
|
|
43
|
+
Pass `model` instead of `tier` to pin an exact model. It is never substituted:
|
|
44
|
+
if that model can't deliver, the call fails and you are not charged.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
const image = await client.images.generate("a lighthouse at dusk", { model: "corent-flux-schnell" });
|
|
48
|
+
console.log(image.model); // "corent-flux-schnell" — the name you asked for
|
|
49
|
+
|
|
50
|
+
await client.models(); // the menu: every model with its kind, quality and live status
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Each model has one name, published as `corent-` + the model: `corent-flux-schnell`,
|
|
54
|
+
`corent-seedance-2.0`, `corent-claude-opus-5`. That is the spelling `client.models()`
|
|
55
|
+
lists and the one every receipt echoes back. Older spellings you may already have
|
|
56
|
+
hard-coded (`flux-schnell`, `seedream-5.0-direct`) keep working — the API accepts
|
|
57
|
+
them and answers with the published name.
|
|
58
|
+
|
|
59
|
+
When we can reach a model by more than one route, Corent serves whichever is
|
|
60
|
+
cheapest at that moment and charges you that price — you never have to shop
|
|
61
|
+
between near-identical entries.
|
|
62
|
+
|
|
63
|
+
## Text (language models)
|
|
64
|
+
|
|
65
|
+
Every frontier lab on one key and one bill, priced per token.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const answer = await client.text.generate("Explain reserve-then-settle billing.", {
|
|
69
|
+
system: "Answer in two sentences.",
|
|
70
|
+
tier: "premium",
|
|
71
|
+
});
|
|
72
|
+
console.log(answer.text, answer.promptTokens, answer.completionTokens, answer.costCents);
|
|
73
|
+
|
|
74
|
+
// a real conversation, tool calls included
|
|
75
|
+
const reply = await client.text.chat(
|
|
76
|
+
[
|
|
77
|
+
{ role: "system", content: "Be terse." },
|
|
78
|
+
{ role: "user", content: "What's the weather?" },
|
|
79
|
+
],
|
|
80
|
+
{ model: "corent-claude-opus-5" },
|
|
81
|
+
);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Streaming isn't wrapped here: point any OpenAI-compatible client at
|
|
85
|
+
`https://api.corent.tech/v1` with your Corent key and it works as-is.
|
|
86
|
+
|
|
87
|
+
## Keep a character or product consistent
|
|
88
|
+
|
|
89
|
+
Pass 1–4 reference images and the prompt is applied as an *edit* of them, so
|
|
90
|
+
the same face, character, or product carries into a new scene.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
const shot = await client.images.generate("the same woman, now on a beach", {
|
|
94
|
+
tier: "pro", // edit-capable models sit at premium and up
|
|
95
|
+
referenceImageUrls: ["https://cdn.example/her.png"],
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`air` and `lite` cannot do this and say so with a 400.
|
|
100
|
+
`client.tiers()` reports `capabilities.supports_reference_images` per tier.
|
|
101
|
+
|
|
102
|
+
## Batches and webhooks
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
// Up to 50 renders in one call. Each item bills at the normal rate.
|
|
106
|
+
const batch = await client.batches.images(
|
|
107
|
+
[{ prompt: "a fox", tier: "air" }, { prompt: "a heron", tier: "air" }],
|
|
108
|
+
{ idempotencyKey: "campaign-9" }, // a retry replays instead of re-billing
|
|
109
|
+
);
|
|
110
|
+
const progress = await client.batches.progress(batch.batchId);
|
|
111
|
+
|
|
112
|
+
// Or have the server deliver each result and skip polling entirely.
|
|
113
|
+
await client.videos.generate("a drone shot", {
|
|
114
|
+
tier: "premium",
|
|
115
|
+
webhookUrl: "https://your-server.com/webhooks/corent",
|
|
116
|
+
webhookSecret: "your_shared_secret", // signs every delivery
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## What else you can ask for
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
// Repeat an image exactly, then change one thing.
|
|
124
|
+
const first = await client.images.generate("a fox in a library", { tier: "pro" });
|
|
125
|
+
const again = await client.images.generate("a fox in a library, wearing glasses", {
|
|
126
|
+
tier: "pro", seed: 12345, negativePrompt: "text, watermark",
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Four versions of one prompt in one call (four real renders, four charges).
|
|
130
|
+
const { images, totalCostCents } = await client.images.generateMany("a fox", 4, { tier: "air" });
|
|
131
|
+
|
|
132
|
+
// A transparent logo, at a size you choose.
|
|
133
|
+
await client.images.generate("a minimal fox mark", { transparent: true, width: 1024, height: 1024 });
|
|
134
|
+
|
|
135
|
+
// Video with sound, going from one picture to another.
|
|
136
|
+
await client.videos.generate("the camera pulls back", {
|
|
137
|
+
tier: "premium", audio: true,
|
|
138
|
+
imageUrl: "https://.../start.png", endImageUrl: "https://.../end.png",
|
|
139
|
+
camera: "zoom_out",
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Pick a voice, and shape how it reads.
|
|
143
|
+
const voices = await client.voices();
|
|
144
|
+
await client.speech.generate("Welcome aboard.", {
|
|
145
|
+
voiceId: voices[0].voice_id, stability: 0.3, speed: 1.1,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Started something by mistake? Stop it. Costs nothing.
|
|
149
|
+
await client.jobs.cancel(job.id);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`audio: true` routes only to models that actually render sound, so a silent
|
|
153
|
+
model can never quietly serve the request. Not every model takes every
|
|
154
|
+
setting: anything the chosen one could not honour comes back in
|
|
155
|
+
`meta.unsupported_options` rather than being silently ignored.
|
|
156
|
+
|
|
38
157
|
## Fine-grained control
|
|
39
158
|
|
|
40
159
|
```ts
|
|
41
160
|
const job = await client.images.generate("...", { tier: "max_pro", wait: false });
|
|
42
161
|
const done = await client.jobs.wait(job.id); // resume any time
|
|
43
162
|
|
|
44
|
-
await client.tiers();
|
|
45
|
-
await client.
|
|
163
|
+
await client.tiers(); // live catalog with honest min–max price ranges
|
|
164
|
+
await client.models(); // direct-access menu with per-model prices
|
|
165
|
+
await client.balance(); // { balanceCents, heldCents, availableCents }
|
|
166
|
+
await client.usage(); // what this account has spent
|
|
46
167
|
```
|
|
47
168
|
|
|
48
|
-
|
|
169
|
+
Every generate call sends an `Idempotency-Key`, so the SDK's own retries can
|
|
170
|
+
never double-charge. Pass your own `idempotencyKey` to make that survive a
|
|
171
|
+
process restart too.
|
|
172
|
+
|
|
173
|
+
Tiers: `air` | `lite` | `premium` | `pro` | `max_pro` — see [corent.tech/pricing](https://corent.tech/pricing). Models: [corent.tech/models](https://corent.tech/models). Docs: [corent.tech/docs](https://corent.tech/docs). MCP server for agents: [`corent-mcp`](https://www.npmjs.com/package/corent-mcp).
|
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,8 @@ var DEFAULT_BASE_URL = "https://api.corent.tech";
|
|
|
31
31
|
var MAX_RETRIES = 3;
|
|
32
32
|
var POLL_INTERVAL_MS = 3e3;
|
|
33
33
|
var DEFAULT_WAIT_TIMEOUT_MS = 9e5;
|
|
34
|
+
var MAX_BATCH_ITEMS = 50;
|
|
35
|
+
var MAX_IMAGES_PER_REQUEST = 10;
|
|
34
36
|
var CorentError = class extends Error {
|
|
35
37
|
constructor(message, statusCode) {
|
|
36
38
|
super(message);
|
|
@@ -58,7 +60,9 @@ var Corent = class {
|
|
|
58
60
|
images;
|
|
59
61
|
videos;
|
|
60
62
|
speech;
|
|
63
|
+
text;
|
|
61
64
|
jobs;
|
|
65
|
+
batches;
|
|
62
66
|
constructor(apiKey, options = {}) {
|
|
63
67
|
this.apiKey = apiKey;
|
|
64
68
|
this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
|
|
@@ -66,26 +70,63 @@ var Corent = class {
|
|
|
66
70
|
this.images = new Images(this);
|
|
67
71
|
this.videos = new Videos(this);
|
|
68
72
|
this.speech = new Speech(this);
|
|
73
|
+
this.text = new Text(this);
|
|
69
74
|
this.jobs = new Jobs(this);
|
|
75
|
+
this.batches = new Batches(this);
|
|
70
76
|
}
|
|
71
|
-
/** Prepaid balance in cents. */
|
|
77
|
+
/** Prepaid balance in cents. See `balance()` for what is actually spendable. */
|
|
72
78
|
async balanceCents() {
|
|
73
79
|
const r = await this.request("GET", "/v1/account/balance");
|
|
74
80
|
return r.balance_cents;
|
|
75
81
|
}
|
|
82
|
+
/** The full picture: the total, what running generations have reserved, and
|
|
83
|
+
* what a new request can actually spend. availableCents is the number a 402
|
|
84
|
+
* is decided against — check that one before an expensive batch. */
|
|
85
|
+
async balance() {
|
|
86
|
+
const r = await this.request("GET", "/v1/account/balance");
|
|
87
|
+
return {
|
|
88
|
+
balanceCents: r.balance_cents,
|
|
89
|
+
heldCents: r.held_cents ?? 0,
|
|
90
|
+
availableCents: r.available_cents ?? r.balance_cents
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** The voices `speech.generate` will accept as voiceId. This exists
|
|
94
|
+
* because the API requires a voice id and, until 2026-08-30, published no
|
|
95
|
+
* list of legal values. */
|
|
96
|
+
async voices() {
|
|
97
|
+
const r = await this.request("GET", "/v1/voices");
|
|
98
|
+
return r.voices ?? [];
|
|
99
|
+
}
|
|
100
|
+
/** What this account has spent, by lane and over time. */
|
|
101
|
+
async usage() {
|
|
102
|
+
return this.request("GET", "/v1/account/usage");
|
|
103
|
+
}
|
|
104
|
+
/** Live operational status of the generation tiers. Public. */
|
|
105
|
+
async status() {
|
|
106
|
+
return this.request("GET", "/v1/status");
|
|
107
|
+
}
|
|
76
108
|
/** The live tier catalog with honest min–max price ranges. Public. */
|
|
77
109
|
async tiers() {
|
|
78
110
|
const r = await this.request("GET", "/v1/tiers");
|
|
79
111
|
return r.tiers;
|
|
80
112
|
}
|
|
113
|
+
/** The direct-access menu: every model you can pin by name, with its kind,
|
|
114
|
+
* quality score and live status. Names come back in their published
|
|
115
|
+
* spelling ("corent-flux-schnell") and can be passed straight back as
|
|
116
|
+
* `model`. Carries no price -- billing is flat cost-plus and the exact
|
|
117
|
+
* charge comes back on each generation. Public. */
|
|
118
|
+
async models() {
|
|
119
|
+
const r = await this.request("GET", "/v1/models");
|
|
120
|
+
return r.models;
|
|
121
|
+
}
|
|
81
122
|
/** @internal */
|
|
82
|
-
async request(method, path, body, idempotent = false) {
|
|
123
|
+
async request(method, path, body, idempotent = false, retryTransport = true, idempotencyKey) {
|
|
83
124
|
const headers = {
|
|
84
125
|
Authorization: `Bearer ${this.apiKey}`,
|
|
85
|
-
"User-Agent": "corent-js/0.
|
|
126
|
+
"User-Agent": "corent-js/0.3.0"
|
|
86
127
|
};
|
|
87
128
|
if (body) headers["Content-Type"] = "application/json";
|
|
88
|
-
if (idempotent) headers["Idempotency-Key"] = crypto.randomUUID();
|
|
129
|
+
if (idempotent) headers["Idempotency-Key"] = idempotencyKey ?? crypto.randomUUID();
|
|
89
130
|
let lastError;
|
|
90
131
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
91
132
|
let resp;
|
|
@@ -97,6 +138,7 @@ var Corent = class {
|
|
|
97
138
|
});
|
|
98
139
|
} catch (err) {
|
|
99
140
|
lastError = err;
|
|
141
|
+
if (!retryTransport) throw new CorentError(`request failed: ${err}`);
|
|
100
142
|
await sleep(Math.min(2 ** attempt * 1e3, 8e3));
|
|
101
143
|
continue;
|
|
102
144
|
}
|
|
@@ -139,22 +181,108 @@ var Images = class {
|
|
|
139
181
|
}
|
|
140
182
|
c;
|
|
141
183
|
/** Render an image. Submits as a background job and polls, so a client
|
|
142
|
-
* timeout can never lose a finished render. Pass wait:false for the Job.
|
|
184
|
+
* timeout can never lose a finished render. Pass wait:false for the Job.
|
|
185
|
+
*
|
|
186
|
+
* `model` pins an exact model from client.models() instead of letting the
|
|
187
|
+
* router choose — direct access: never substituted, flat cost-plus price.
|
|
188
|
+
* e.g. model: "corent-flux-schnell". Pass tier OR model, not both. */
|
|
143
189
|
async generate(prompt, options = {}) {
|
|
190
|
+
if (options.referenceImageUrls && (options.referenceImageUrls.length < 1 || options.referenceImageUrls.length > 4)) {
|
|
191
|
+
throw new InvalidRequestError("referenceImageUrls must contain 1 to 4 image URLs", 400);
|
|
192
|
+
}
|
|
144
193
|
const body = {
|
|
145
194
|
prompt,
|
|
146
195
|
aspect_ratio: options.aspectRatio ?? "1:1",
|
|
147
196
|
async: true
|
|
148
197
|
};
|
|
149
198
|
if (options.tier) body.tier = options.tier;
|
|
199
|
+
if (options.model) body.model = options.model;
|
|
150
200
|
if (options.style) body.style = options.style;
|
|
151
|
-
|
|
201
|
+
if (options.referenceImageUrls) body.reference_image_urls = options.referenceImageUrls;
|
|
202
|
+
if (options.seed !== void 0) body.seed = options.seed;
|
|
203
|
+
if (options.negativePrompt) body.negative_prompt = options.negativePrompt;
|
|
204
|
+
if (options.sourceImageUrl) body.source_image_url = options.sourceImageUrl;
|
|
205
|
+
if (options.strength !== void 0) body.strength = options.strength;
|
|
206
|
+
if (options.outputFormat) body.output_format = options.outputFormat;
|
|
207
|
+
if (options.transparent) body.transparent = true;
|
|
208
|
+
if (options.width !== void 0) body.width = options.width;
|
|
209
|
+
if (options.height !== void 0) body.height = options.height;
|
|
210
|
+
if (options.enhancePrompt === false) body.enhance_prompt = false;
|
|
211
|
+
if (options.webhookUrl) {
|
|
212
|
+
body.webhook_url = options.webhookUrl;
|
|
213
|
+
if (options.webhookSecret) body.webhook_secret = options.webhookSecret;
|
|
214
|
+
delete body.async;
|
|
215
|
+
}
|
|
216
|
+
const submitted = await this.c.request(
|
|
217
|
+
"POST",
|
|
218
|
+
"/v1/images/generate",
|
|
219
|
+
body,
|
|
220
|
+
true,
|
|
221
|
+
true,
|
|
222
|
+
options.idempotencyKey
|
|
223
|
+
);
|
|
152
224
|
if (submitted.status === "completed") return imageFromJob(submitted);
|
|
153
|
-
if (options.wait === false) {
|
|
225
|
+
if (options.webhookUrl || options.wait === false) {
|
|
154
226
|
return { id: submitted.id, status: submitted.status, raw: submitted };
|
|
155
227
|
}
|
|
156
228
|
return imageFromJob(await this.c.waitForJob(submitted.id, options.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS));
|
|
157
229
|
}
|
|
230
|
+
/** Render several versions of one prompt in a single call, 2–10.
|
|
231
|
+
*
|
|
232
|
+
* Each one is a real render at the normal price, so asking for four costs
|
|
233
|
+
* four images. They come back together, and `totalCostCents` is the whole
|
|
234
|
+
* charge. Partial success is a real outcome: if three of four land you get
|
|
235
|
+
* three and are billed for three.
|
|
236
|
+
*
|
|
237
|
+
* Different from client.batches.images(), which runs DIFFERENT prompts and
|
|
238
|
+
* returns job ids to poll. This one runs the SAME prompt and waits. */
|
|
239
|
+
async generateMany(prompt, count, options = {}) {
|
|
240
|
+
if (count < 2 || count > MAX_IMAGES_PER_REQUEST) {
|
|
241
|
+
throw new InvalidRequestError(
|
|
242
|
+
`generateMany takes 2 to ${MAX_IMAGES_PER_REQUEST}; use generate() for one`,
|
|
243
|
+
400
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
const body = {
|
|
247
|
+
prompt,
|
|
248
|
+
aspect_ratio: options.aspectRatio ?? "1:1",
|
|
249
|
+
n: count
|
|
250
|
+
};
|
|
251
|
+
if (options.tier) body.tier = options.tier;
|
|
252
|
+
if (options.model) body.model = options.model;
|
|
253
|
+
if (options.style) body.style = options.style;
|
|
254
|
+
if (options.referenceImageUrls) body.reference_image_urls = options.referenceImageUrls;
|
|
255
|
+
if (options.seed !== void 0) body.seed = options.seed;
|
|
256
|
+
if (options.negativePrompt) body.negative_prompt = options.negativePrompt;
|
|
257
|
+
if (options.sourceImageUrl) body.source_image_url = options.sourceImageUrl;
|
|
258
|
+
if (options.strength !== void 0) body.strength = options.strength;
|
|
259
|
+
if (options.outputFormat) body.output_format = options.outputFormat;
|
|
260
|
+
if (options.transparent) body.transparent = true;
|
|
261
|
+
if (options.width !== void 0) body.width = options.width;
|
|
262
|
+
if (options.height !== void 0) body.height = options.height;
|
|
263
|
+
if (options.enhancePrompt === false) body.enhance_prompt = false;
|
|
264
|
+
const r = await this.c.request(
|
|
265
|
+
"POST",
|
|
266
|
+
"/v1/images/generate",
|
|
267
|
+
body,
|
|
268
|
+
true,
|
|
269
|
+
true,
|
|
270
|
+
options.idempotencyKey
|
|
271
|
+
);
|
|
272
|
+
const meta = r.meta ?? {};
|
|
273
|
+
return {
|
|
274
|
+
images: (r.images ?? []).map((image) => ({
|
|
275
|
+
id: r.id,
|
|
276
|
+
url: image.url,
|
|
277
|
+
width: image.width,
|
|
278
|
+
height: image.height,
|
|
279
|
+
model: meta.model,
|
|
280
|
+
costCents: void 0
|
|
281
|
+
})),
|
|
282
|
+
// The total for every image that actually landed.
|
|
283
|
+
totalCostCents: meta.cost_cents
|
|
284
|
+
};
|
|
285
|
+
}
|
|
158
286
|
};
|
|
159
287
|
var Videos = class {
|
|
160
288
|
constructor(c) {
|
|
@@ -163,18 +291,41 @@ var Videos = class {
|
|
|
163
291
|
c;
|
|
164
292
|
/** Render a video (1–5 minutes typical). resolution: 720p | 1080p | 4k —
|
|
165
293
|
* tier-capped, clamped down rather than rejected. imageUrl animates an
|
|
166
|
-
* existing image (image-to-video).
|
|
294
|
+
* existing image (image-to-video).
|
|
295
|
+
*
|
|
296
|
+
* `model` pins an exact model from client.models() — direct access, never
|
|
297
|
+
* substituted, and duration/resolution snap to THAT model's own menu rather
|
|
298
|
+
* than a tier cap. e.g. model: "corent-seedance-2.0". Pass tier OR model,
|
|
299
|
+
* not both. */
|
|
167
300
|
async generate(prompt, options = {}) {
|
|
168
|
-
const body = {
|
|
169
|
-
|
|
170
|
-
aspect_ratio: options.aspectRatio ?? "16:9"
|
|
171
|
-
};
|
|
301
|
+
const body = { prompt };
|
|
302
|
+
if (options.aspectRatio) body.aspect_ratio = options.aspectRatio;
|
|
172
303
|
if (options.tier) body.tier = options.tier;
|
|
304
|
+
if (options.model) body.model = options.model;
|
|
305
|
+
if (options.style) body.style = options.style;
|
|
173
306
|
if (options.durationS !== void 0) body.duration_s = options.durationS;
|
|
174
307
|
if (options.resolution) body.resolution = options.resolution;
|
|
175
308
|
if (options.imageUrl) body.image_url = options.imageUrl;
|
|
176
|
-
|
|
177
|
-
if (options.
|
|
309
|
+
if (options.audio !== void 0) body.audio = options.audio;
|
|
310
|
+
if (options.endImageUrl) body.end_image_url = options.endImageUrl;
|
|
311
|
+
if (options.camera) body.camera = options.camera;
|
|
312
|
+
if (options.negativePrompt) body.negative_prompt = options.negativePrompt;
|
|
313
|
+
if (options.seed !== void 0) body.seed = options.seed;
|
|
314
|
+
if (options.fps !== void 0) body.fps = options.fps;
|
|
315
|
+
if (options.enhancePrompt === false) body.enhance_prompt = false;
|
|
316
|
+
if (options.webhookUrl) {
|
|
317
|
+
body.webhook_url = options.webhookUrl;
|
|
318
|
+
if (options.webhookSecret) body.webhook_secret = options.webhookSecret;
|
|
319
|
+
}
|
|
320
|
+
const submitted = await this.c.request(
|
|
321
|
+
"POST",
|
|
322
|
+
"/v1/videos/generate",
|
|
323
|
+
body,
|
|
324
|
+
true,
|
|
325
|
+
true,
|
|
326
|
+
options.idempotencyKey
|
|
327
|
+
);
|
|
328
|
+
if (options.webhookUrl || options.wait === false) {
|
|
178
329
|
return { id: submitted.id, status: submitted.status, raw: submitted };
|
|
179
330
|
}
|
|
180
331
|
return videoFromJob(await this.c.waitForJob(submitted.id, options.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS));
|
|
@@ -185,15 +336,111 @@ var Speech = class {
|
|
|
185
336
|
this.c = c;
|
|
186
337
|
}
|
|
187
338
|
c;
|
|
188
|
-
/** Text to speech; synchronous, returns the finished audio and exact charge.
|
|
339
|
+
/** Text to speech; synchronous, returns the finished audio and exact charge.
|
|
340
|
+
* `model` pins an exact speech model from client.models(),
|
|
341
|
+
* e.g. model: "corent-eleven-multilingual-v2". */
|
|
189
342
|
async generate(text, options = {}) {
|
|
190
343
|
const body = { text };
|
|
191
344
|
if (options.voiceId) body.voice_id = options.voiceId;
|
|
192
|
-
|
|
345
|
+
if (options.model) body.model = options.model;
|
|
346
|
+
if (options.stability !== void 0) body.stability = options.stability;
|
|
347
|
+
if (options.similarity !== void 0) body.similarity = options.similarity;
|
|
348
|
+
if (options.style !== void 0) body.style = options.style;
|
|
349
|
+
if (options.speed !== void 0) body.speed = options.speed;
|
|
350
|
+
if (options.language) body.language = options.language;
|
|
351
|
+
if (options.outputFormat) body.output_format = options.outputFormat;
|
|
352
|
+
const r = await this.c.request("POST", "/v1/audio/speech", body, true, true, options.idempotencyKey);
|
|
193
353
|
const meta = r.meta ?? {};
|
|
194
354
|
return { id: r.id, url: r.audio_url, model: meta.model, costCents: meta.cost_cents };
|
|
195
355
|
}
|
|
196
356
|
};
|
|
357
|
+
var Text = class {
|
|
358
|
+
constructor(c) {
|
|
359
|
+
this.c = c;
|
|
360
|
+
}
|
|
361
|
+
c;
|
|
362
|
+
/** One prompt in, the finished text out. `system` frames the request.
|
|
363
|
+
*
|
|
364
|
+
* `model` pins an exact text model from client.models() (kind "text"),
|
|
365
|
+
* e.g. model: "corent-claude-opus-5"; otherwise `tier` picks the routed
|
|
366
|
+
* lane. Pass tier OR model, not both. */
|
|
367
|
+
async generate(prompt, options = {}) {
|
|
368
|
+
const messages = options.system ? [{ role: "system", content: options.system }, { role: "user", content: prompt }] : [{ role: "user", content: prompt }];
|
|
369
|
+
return this.chat(messages, options);
|
|
370
|
+
}
|
|
371
|
+
/** A full OpenAI-shaped conversation, including tool results. Streaming is
|
|
372
|
+
* not wrapped here — point any OpenAI client at https://api.corent.tech/v1
|
|
373
|
+
* with your Corent key for that. */
|
|
374
|
+
async chat(messages, options = {}) {
|
|
375
|
+
const body = {
|
|
376
|
+
// The API's `model` field carries both modes: a pinned catalog name
|
|
377
|
+
// ("corent-claude-opus-5"), or "corent/text-<tier>" for the routed lane.
|
|
378
|
+
// Note the two are different namespaces -- the routed lane keeps its
|
|
379
|
+
// slash form. Whatever the caller passes goes through verbatim; the API
|
|
380
|
+
// still accepts the older bare spellings. lite matches the router's own
|
|
381
|
+
// default for an unrecognised tier.
|
|
382
|
+
model: options.model ?? `corent/text-${options.tier ?? "lite"}`,
|
|
383
|
+
messages
|
|
384
|
+
};
|
|
385
|
+
if (options.maxTokens !== void 0) body.max_tokens = options.maxTokens;
|
|
386
|
+
if (options.maxCompletionTokens !== void 0) body.max_completion_tokens = options.maxCompletionTokens;
|
|
387
|
+
if (options.temperature !== void 0) body.temperature = options.temperature;
|
|
388
|
+
if (options.tools) body.tools = options.tools;
|
|
389
|
+
if (options.toolChoice !== void 0) body.tool_choice = options.toolChoice;
|
|
390
|
+
if (options.responseFormat) body.response_format = options.responseFormat;
|
|
391
|
+
const r = await this.c.request("POST", "/v1/chat/completions", body, false, false);
|
|
392
|
+
const choice = (r.choices ?? [{}])[0];
|
|
393
|
+
const message = choice.message ?? {};
|
|
394
|
+
const usage = r.usage ?? {};
|
|
395
|
+
return {
|
|
396
|
+
id: r.id,
|
|
397
|
+
text: message.content ?? "",
|
|
398
|
+
model: r.model,
|
|
399
|
+
finishReason: choice.finish_reason,
|
|
400
|
+
toolCalls: message.tool_calls,
|
|
401
|
+
promptTokens: usage.prompt_tokens,
|
|
402
|
+
completionTokens: usage.completion_tokens,
|
|
403
|
+
costCents: (r.corent ?? {}).cost_cents
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
var Batches = class {
|
|
408
|
+
constructor(c) {
|
|
409
|
+
this.c = c;
|
|
410
|
+
}
|
|
411
|
+
c;
|
|
412
|
+
async images(items, options = {}) {
|
|
413
|
+
return this.submit("/v1/images/generate/batch", items.map(imageBatchItemBody), options);
|
|
414
|
+
}
|
|
415
|
+
async videos(items, options = {}) {
|
|
416
|
+
return this.submit("/v1/videos/generate/batch", items.map(videoBatchItemBody), options);
|
|
417
|
+
}
|
|
418
|
+
/** How far along a submitted batch is, and each item's job id. Fetch a
|
|
419
|
+
* finished item's media with client.jobs.get(). */
|
|
420
|
+
async progress(batchId) {
|
|
421
|
+
const r = await this.c.request("GET", `/v1/batches/${batchId}`);
|
|
422
|
+
return {
|
|
423
|
+
batchId: r.batch_id,
|
|
424
|
+
total: r.total,
|
|
425
|
+
completed: r.completed,
|
|
426
|
+
failed: r.failed,
|
|
427
|
+
pending: r.pending,
|
|
428
|
+
jobs: r.jobs ?? []
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
async submit(path, items, options) {
|
|
432
|
+
if (items.length < 1 || items.length > MAX_BATCH_ITEMS) {
|
|
433
|
+
throw new InvalidRequestError(`a batch takes 1 to ${MAX_BATCH_ITEMS} items, got ${items.length}`, 400);
|
|
434
|
+
}
|
|
435
|
+
const body = { items };
|
|
436
|
+
if (options.webhookUrl) {
|
|
437
|
+
body.webhook_url = options.webhookUrl;
|
|
438
|
+
if (options.webhookSecret) body.webhook_secret = options.webhookSecret;
|
|
439
|
+
}
|
|
440
|
+
const r = await this.c.request("POST", path, body, true, true, options.idempotencyKey);
|
|
441
|
+
return { batchId: r.batch_id, jobIds: r.job_ids ?? [] };
|
|
442
|
+
}
|
|
443
|
+
};
|
|
197
444
|
var Jobs = class {
|
|
198
445
|
constructor(c) {
|
|
199
446
|
this.c = c;
|
|
@@ -203,6 +450,15 @@ var Jobs = class {
|
|
|
203
450
|
const raw = await this.c.request("GET", `/v1/jobs/${jobId}`);
|
|
204
451
|
return { id: raw.id, status: raw.status, raw };
|
|
205
452
|
}
|
|
453
|
+
/** Stop a job that has not finished and release its money hold.
|
|
454
|
+
*
|
|
455
|
+
* Charges nothing. A job that completed just before the cancel landed stays
|
|
456
|
+
* completed and stays billed for what was really delivered — `cancelled`
|
|
457
|
+
* says which happened. */
|
|
458
|
+
async cancel(jobId) {
|
|
459
|
+
const r = await this.c.request("POST", `/v1/jobs/${jobId}/cancel`);
|
|
460
|
+
return { id: r.id, status: r.status, cancelled: Boolean(r.cancelled) };
|
|
461
|
+
}
|
|
206
462
|
async wait(jobId, timeoutMs = DEFAULT_WAIT_TIMEOUT_MS) {
|
|
207
463
|
const raw = await this.c.waitForJob(jobId, timeoutMs);
|
|
208
464
|
return { id: raw.id, status: raw.status, raw };
|
|
@@ -246,6 +502,22 @@ function videoFromJob(job) {
|
|
|
246
502
|
costCents: meta.cost_cents
|
|
247
503
|
};
|
|
248
504
|
}
|
|
505
|
+
function imageBatchItemBody(item) {
|
|
506
|
+
const body = { prompt: item.prompt };
|
|
507
|
+
if (item.tier) body.tier = item.tier;
|
|
508
|
+
if (item.style) body.style = item.style;
|
|
509
|
+
if (item.aspectRatio) body.aspect_ratio = item.aspectRatio;
|
|
510
|
+
return body;
|
|
511
|
+
}
|
|
512
|
+
function videoBatchItemBody(item) {
|
|
513
|
+
const body = { prompt: item.prompt };
|
|
514
|
+
if (item.tier) body.tier = item.tier;
|
|
515
|
+
if (item.style) body.style = item.style;
|
|
516
|
+
if (item.aspectRatio) body.aspect_ratio = item.aspectRatio;
|
|
517
|
+
if (item.durationS !== void 0) body.duration_s = item.durationS;
|
|
518
|
+
if (item.resolution) body.resolution = item.resolution;
|
|
519
|
+
return body;
|
|
520
|
+
}
|
|
249
521
|
function sleep(ms) {
|
|
250
522
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
251
523
|
}
|