corent-sdk 0.2.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 +118 -10
- package/dist/index.cjs +271 -17
- package/dist/index.d.cts +248 -8
- package/dist/index.d.ts +248 -8
- package/dist/index.js +271 -17
- 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
|
|
@@ -41,15 +44,115 @@ Pass `model` instead of `tier` to pin an exact model. It is never substituted:
|
|
|
41
44
|
if that model can't deliver, the call fails and you are not charged.
|
|
42
45
|
|
|
43
46
|
```ts
|
|
44
|
-
const image = await client.images.generate("a lighthouse at dusk", { model: "flux-schnell" });
|
|
45
|
-
console.log(image.model); // "flux-schnell" — the name you asked for
|
|
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.
|
|
46
62
|
|
|
47
|
-
|
|
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
|
+
);
|
|
48
82
|
```
|
|
49
83
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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.
|
|
53
156
|
|
|
54
157
|
## Fine-grained control
|
|
55
158
|
|
|
@@ -57,9 +160,14 @@ never have to shop between near-identical entries.
|
|
|
57
160
|
const job = await client.images.generate("...", { tier: "max_pro", wait: false });
|
|
58
161
|
const done = await client.jobs.wait(job.id); // resume any time
|
|
59
162
|
|
|
60
|
-
await client.tiers();
|
|
61
|
-
await client.models();
|
|
62
|
-
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
|
|
63
167
|
```
|
|
64
168
|
|
|
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
|
+
|
|
65
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,32 +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
|
}
|
|
81
113
|
/** The direct-access menu: every model you can pin by name, with its kind,
|
|
82
|
-
* quality score and
|
|
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. */
|
|
83
118
|
async models() {
|
|
84
119
|
const r = await this.request("GET", "/v1/models");
|
|
85
120
|
return r.models;
|
|
86
121
|
}
|
|
87
122
|
/** @internal */
|
|
88
|
-
async request(method, path, body, idempotent = false) {
|
|
123
|
+
async request(method, path, body, idempotent = false, retryTransport = true, idempotencyKey) {
|
|
89
124
|
const headers = {
|
|
90
125
|
Authorization: `Bearer ${this.apiKey}`,
|
|
91
|
-
"User-Agent": "corent-js/0.
|
|
126
|
+
"User-Agent": "corent-js/0.3.0"
|
|
92
127
|
};
|
|
93
128
|
if (body) headers["Content-Type"] = "application/json";
|
|
94
|
-
if (idempotent) headers["Idempotency-Key"] = crypto.randomUUID();
|
|
129
|
+
if (idempotent) headers["Idempotency-Key"] = idempotencyKey ?? crypto.randomUUID();
|
|
95
130
|
let lastError;
|
|
96
131
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
97
132
|
let resp;
|
|
@@ -103,6 +138,7 @@ var Corent = class {
|
|
|
103
138
|
});
|
|
104
139
|
} catch (err) {
|
|
105
140
|
lastError = err;
|
|
141
|
+
if (!retryTransport) throw new CorentError(`request failed: ${err}`);
|
|
106
142
|
await sleep(Math.min(2 ** attempt * 1e3, 8e3));
|
|
107
143
|
continue;
|
|
108
144
|
}
|
|
@@ -149,8 +185,11 @@ var Images = class {
|
|
|
149
185
|
*
|
|
150
186
|
* `model` pins an exact model from client.models() instead of letting the
|
|
151
187
|
* router choose — direct access: never substituted, flat cost-plus price.
|
|
152
|
-
* Pass tier OR model, not both. */
|
|
188
|
+
* e.g. model: "corent-flux-schnell". Pass tier OR model, not both. */
|
|
153
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
|
+
}
|
|
154
193
|
const body = {
|
|
155
194
|
prompt,
|
|
156
195
|
aspect_ratio: options.aspectRatio ?? "1:1",
|
|
@@ -159,13 +198,91 @@ var Images = class {
|
|
|
159
198
|
if (options.tier) body.tier = options.tier;
|
|
160
199
|
if (options.model) body.model = options.model;
|
|
161
200
|
if (options.style) body.style = options.style;
|
|
162
|
-
|
|
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
|
+
);
|
|
163
224
|
if (submitted.status === "completed") return imageFromJob(submitted);
|
|
164
|
-
if (options.wait === false) {
|
|
225
|
+
if (options.webhookUrl || options.wait === false) {
|
|
165
226
|
return { id: submitted.id, status: submitted.status, raw: submitted };
|
|
166
227
|
}
|
|
167
228
|
return imageFromJob(await this.c.waitForJob(submitted.id, options.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS));
|
|
168
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
|
+
}
|
|
169
286
|
};
|
|
170
287
|
var Videos = class {
|
|
171
288
|
constructor(c) {
|
|
@@ -178,19 +295,37 @@ var Videos = class {
|
|
|
178
295
|
*
|
|
179
296
|
* `model` pins an exact model from client.models() — direct access, never
|
|
180
297
|
* substituted, and duration/resolution snap to THAT model's own menu rather
|
|
181
|
-
* than a tier cap. Pass tier OR model,
|
|
298
|
+
* than a tier cap. e.g. model: "corent-seedance-2.0". Pass tier OR model,
|
|
299
|
+
* not both. */
|
|
182
300
|
async generate(prompt, options = {}) {
|
|
183
|
-
const body = {
|
|
184
|
-
|
|
185
|
-
aspect_ratio: options.aspectRatio ?? "16:9"
|
|
186
|
-
};
|
|
301
|
+
const body = { prompt };
|
|
302
|
+
if (options.aspectRatio) body.aspect_ratio = options.aspectRatio;
|
|
187
303
|
if (options.tier) body.tier = options.tier;
|
|
188
304
|
if (options.model) body.model = options.model;
|
|
305
|
+
if (options.style) body.style = options.style;
|
|
189
306
|
if (options.durationS !== void 0) body.duration_s = options.durationS;
|
|
190
307
|
if (options.resolution) body.resolution = options.resolution;
|
|
191
308
|
if (options.imageUrl) body.image_url = options.imageUrl;
|
|
192
|
-
|
|
193
|
-
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) {
|
|
194
329
|
return { id: submitted.id, status: submitted.status, raw: submitted };
|
|
195
330
|
}
|
|
196
331
|
return videoFromJob(await this.c.waitForJob(submitted.id, options.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS));
|
|
@@ -202,16 +337,110 @@ var Speech = class {
|
|
|
202
337
|
}
|
|
203
338
|
c;
|
|
204
339
|
/** Text to speech; synchronous, returns the finished audio and exact charge.
|
|
205
|
-
* `model` pins an exact speech model from client.models()
|
|
340
|
+
* `model` pins an exact speech model from client.models(),
|
|
341
|
+
* e.g. model: "corent-eleven-multilingual-v2". */
|
|
206
342
|
async generate(text, options = {}) {
|
|
207
343
|
const body = { text };
|
|
208
344
|
if (options.voiceId) body.voice_id = options.voiceId;
|
|
209
345
|
if (options.model) body.model = options.model;
|
|
210
|
-
|
|
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);
|
|
211
353
|
const meta = r.meta ?? {};
|
|
212
354
|
return { id: r.id, url: r.audio_url, model: meta.model, costCents: meta.cost_cents };
|
|
213
355
|
}
|
|
214
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
|
+
};
|
|
215
444
|
var Jobs = class {
|
|
216
445
|
constructor(c) {
|
|
217
446
|
this.c = c;
|
|
@@ -221,6 +450,15 @@ var Jobs = class {
|
|
|
221
450
|
const raw = await this.c.request("GET", `/v1/jobs/${jobId}`);
|
|
222
451
|
return { id: raw.id, status: raw.status, raw };
|
|
223
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
|
+
}
|
|
224
462
|
async wait(jobId, timeoutMs = DEFAULT_WAIT_TIMEOUT_MS) {
|
|
225
463
|
const raw = await this.c.waitForJob(jobId, timeoutMs);
|
|
226
464
|
return { id: raw.id, status: raw.status, raw };
|
|
@@ -264,6 +502,22 @@ function videoFromJob(job) {
|
|
|
264
502
|
costCents: meta.cost_cents
|
|
265
503
|
};
|
|
266
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
|
+
}
|
|
267
521
|
function sleep(ms) {
|
|
268
522
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
269
523
|
}
|