craterview 0.4.23 → 0.4.24
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 +15 -11
- package/index.ts +49 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -144,7 +144,7 @@ the same job.
|
|
|
144
144
|
## Errors
|
|
145
145
|
|
|
146
146
|
Every answer the API or storage gives that is not a success is thrown as a
|
|
147
|
-
`CraterViewError`. `.status` carries the HTTP status where there was one, and `.traceId` the
|
|
147
|
+
`CraterViewError`. `.status` carries the HTTP status where there was one, `.code` the API's stable name for a refusal — branch on it, not on the message — and `.traceId` the
|
|
148
148
|
API's `X-Trace-Id` for a request it answered and refused — the id to quote when you ask us
|
|
149
149
|
about it. `.jobId` names the job an error is about, where there is one. A request that got
|
|
150
150
|
no answer at all throws the runtime's own error from `fetch`, which is how the retry loop
|
|
@@ -152,7 +152,7 @@ above tells the two apart.
|
|
|
152
152
|
|
|
153
153
|
| Class | Meaning |
|
|
154
154
|
|---|---|
|
|
155
|
-
| `RateLimited` | 429 — one of five limits, and
|
|
155
|
+
| `RateLimited` | 429 — one of five limits, and `.code` says which: `rate_limited` (the key's request rate), `too_many_uploads`, `too_many_keys`, `too_many_jobs` or `queue_full`. `.retryAfter` is seconds to wait: until the window rolls over for the two per-minute limits, until a new key can be made for the hourly one, or an interval to poll on for the other two. |
|
|
156
156
|
| `JobFailed` | The job ran and did not succeed. `.message` says what you can do about it; `.errorCode` is the half to branch on; `.jobId` is the job. |
|
|
157
157
|
| `InvalidSignature` | A webhook delivery did not verify. Do not act on its body. |
|
|
158
158
|
| `CraterViewError` | Everything else, including 4xx and 5xx from the API, and storage refusing an upload or a download. |
|
|
@@ -172,7 +172,7 @@ Every field the API publishes on a job is exposed here.
|
|
|
172
172
|
| `succeeded`, `done` | `done` covers both terminal states |
|
|
173
173
|
| `error` | Set when the job failed. Safe to show a user |
|
|
174
174
|
| `errorCode` | The same fact, as a stable identifier. Branch on this, show the other |
|
|
175
|
-
| `credits` | **What you were billed
|
|
175
|
+
| `credits` | **What you were billed.** Nothing past your balance is collected |
|
|
176
176
|
| `etaSeconds` | Seconds until the job is expected to finish, recomputed on every read — it counts down while the job runs. Absent once the job has settled. Estimated for your image's size when `upload()` could read it (or when you pass `inputMegapixels` to `submit()`), for a typical image otherwise |
|
|
177
177
|
| `community` | True when the job is on the community queue, which runs on shared, free capacity and can wait longer at busy times |
|
|
178
178
|
| `outputUrl`, `downloadUrl` | The result, presigned. One to display, one to save |
|
|
@@ -199,9 +199,9 @@ in hand, which is what makes it impossible to get wrong.
|
|
|
199
199
|
**Running out of credit does not stop you.** A job submitted against a balance of zero is
|
|
200
200
|
accepted and run, and charged when it succeeds — it goes to the community queue, which runs
|
|
201
201
|
on shared, free capacity and can wait longer at busy times. It comes back with `community`
|
|
202
|
-
set
|
|
203
|
-
|
|
204
|
-
submit.
|
|
202
|
+
set, and `usage()` reports the same flag for your next job before you send it. There is no
|
|
203
|
+
payment error to handle: paying — with credit, or with a subscription — moves work onto paid
|
|
204
|
+
compute that scales with demand, rather than buying the right to submit.
|
|
205
205
|
`etaSeconds` covers the whole wait, queue time included, so a community job simply reports
|
|
206
206
|
a longer one.
|
|
207
207
|
|
|
@@ -213,15 +213,19 @@ result rather than storing the link.
|
|
|
213
213
|
page of jobs costs kilobytes, and the picture the model worked from so a result can be
|
|
214
214
|
shown against it. Where you named a region, `inputUrl` is that region — so a before-and-after
|
|
215
215
|
is a true pair, and what was used is something you can look at rather than something to take
|
|
216
|
-
on trust. It is not the file you uploaded
|
|
217
|
-
|
|
218
|
-
|
|
216
|
+
on trust. It is not the file you uploaded but a new one written from it, not copied —
|
|
217
|
+
so do not expect a hash of it to match your file's, and a JPEG or WebP is compressed
|
|
218
|
+
again. Yours is removed on its own schedule. Both expire with the job's other pictures. A
|
|
219
|
+
model that produces no file has no result to preview, so `thumbnailUrl` is a preview of the
|
|
220
|
+
picture it answered about.
|
|
219
221
|
|
|
220
222
|
## Webhooks
|
|
221
223
|
|
|
222
224
|
Pass `webhookUrl` on submit — an absolute `http` or `https` URL, refused at submit if it is
|
|
223
225
|
not one — and we `POST` to it once the job settles. Verify every delivery before you act on
|
|
224
|
-
it: anyone who learns your URL can send you a plausible-looking body.
|
|
226
|
+
it: anyone who learns your URL can send you a plausible-looking body. Retries stop within a
|
|
227
|
+
minute; to find a delivery that never arrived, keep the ids of jobs you submitted and have not
|
|
228
|
+
heard about, and read each with `cv.job(id)`.
|
|
225
229
|
|
|
226
230
|
```ts
|
|
227
231
|
import { CraterView, verifyWebhook, InvalidSignature } from "craterview";
|
|
@@ -293,7 +297,7 @@ old one. You cannot revoke the key you are calling with.
|
|
|
293
297
|
```ts
|
|
294
298
|
await cv.models(); // models, what each takes and answers with, which queue you are on
|
|
295
299
|
await cv.job("job_..."); // one job by id
|
|
296
|
-
await cv.usage(); // credit balance, spend and job counts
|
|
300
|
+
await cv.usage(); // credit balance, next job's queue, spend and job counts
|
|
297
301
|
|
|
298
302
|
for await (const job of cv.jobs({ limit: 50, status: "succeeded" })) {
|
|
299
303
|
console.log(job.id, job.status); // newest first, paging transparently
|
package/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { imageSize } from "image-size";
|
|
|
18
18
|
// Mirrored from package.json, which is the number a release bumps. It cannot be imported
|
|
19
19
|
// from there — this ships as TypeScript, so the import would have to resolve in the
|
|
20
20
|
// consumer's toolchain — so test/version.test.ts asserts the two agree.
|
|
21
|
-
export const VERSION = "0.4.
|
|
21
|
+
export const VERSION = "0.4.24";
|
|
22
22
|
const DEFAULT_BASE_URL = "https://api.craterview.ai";
|
|
23
23
|
// The server rejects a longer wait outright, so asking for one costs a 422 rather than the
|
|
24
24
|
// wait you asked for. `run()` clamps to this rather than letting that happen.
|
|
@@ -131,7 +131,9 @@ function queuedNothing(e: unknown): boolean {
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
|
-
* `status` is the HTTP status where there was one
|
|
134
|
+
* `status` is the HTTP status where there was one, and `code` the API's stable name for a
|
|
135
|
+
* refusal: branch on `code`, not on the message, and handle one you do not know by its status.
|
|
136
|
+
* `traceId` is the API's `X-Trace-Id` for a
|
|
135
137
|
* request it answered and refused — the id to quote when asking about it. Absent when the
|
|
136
138
|
* refusal came from storage rather than the API.
|
|
137
139
|
*
|
|
@@ -141,15 +143,16 @@ function queuedNothing(e: unknown): boolean {
|
|
|
141
143
|
*/
|
|
142
144
|
export class CraterViewError extends Error {
|
|
143
145
|
constructor(message: string, readonly status?: number, readonly traceId?: string,
|
|
144
|
-
readonly jobId?: string) {
|
|
146
|
+
readonly jobId?: string, readonly code?: string) {
|
|
145
147
|
super(message);
|
|
146
148
|
this.name = "CraterViewError";
|
|
147
149
|
}
|
|
148
150
|
}
|
|
149
151
|
|
|
150
152
|
/**
|
|
151
|
-
* 429 — too fast, or too much at once. Five different limits answer with this, and
|
|
152
|
-
*
|
|
153
|
+
* 429 — too fast, or too much at once. Five different limits answer with this, and `code`
|
|
154
|
+
* says which — `rate_limited`, `too_many_keys`, `too_many_uploads`, `too_many_jobs` or
|
|
155
|
+
* `queue_full`, in this order:
|
|
153
156
|
*
|
|
154
157
|
* - the key's request rate, per minute;
|
|
155
158
|
* - the account's new keys, per hour — counted across all its keys, and only from
|
|
@@ -167,8 +170,8 @@ export class CraterViewError extends Error {
|
|
|
167
170
|
* on, since nothing here predicts when a slot frees.
|
|
168
171
|
*/
|
|
169
172
|
export class RateLimited extends CraterViewError {
|
|
170
|
-
constructor(message: string, readonly retryAfter?: number, traceId?: string) {
|
|
171
|
-
super(message, 429, traceId);
|
|
173
|
+
constructor(message: string, readonly retryAfter?: number, traceId?: string, code?: string) {
|
|
174
|
+
super(message, 429, traceId, undefined, code);
|
|
172
175
|
this.name = "RateLimited";
|
|
173
176
|
}
|
|
174
177
|
}
|
|
@@ -412,6 +415,19 @@ export interface ModelInfo {
|
|
|
412
415
|
* `denoise`. Zero means this axis is unbounded for you.
|
|
413
416
|
*/
|
|
414
417
|
max_denoise_input_megapixels?: number | null;
|
|
418
|
+
/**
|
|
419
|
+
* The most of a picture this model works on in one job with `tiled` sent as true, in
|
|
420
|
+
* megapixels: the region you name with `roi`, or the whole image, worked on at its own
|
|
421
|
+
* resolution and returned at its own size. Without `tiled`, a larger picture is reduced
|
|
422
|
+
* rather than refused: see `working_megapixels`. Null for a model that takes no `tiled`.
|
|
423
|
+
*/
|
|
424
|
+
max_tiled_input_megapixels?: number | null;
|
|
425
|
+
/**
|
|
426
|
+
* The most this model works on at a picture's own resolution, in megapixels: a larger
|
|
427
|
+
* picture or region is reduced to about this area first and comes back at about this size,
|
|
428
|
+
* unless `tiled` is sent as true. Null for a model that works at the size it is given.
|
|
429
|
+
*/
|
|
430
|
+
working_megapixels?: number | null;
|
|
415
431
|
/** Whole credits. The price is flat and knowable before you send anything. */
|
|
416
432
|
credits_per_image: number;
|
|
417
433
|
/** Whole credits. Null means this model takes stills only. */
|
|
@@ -559,6 +575,8 @@ export interface JobData {
|
|
|
559
575
|
* A submitted job. Mirrors the API's job representation field for field.
|
|
560
576
|
*
|
|
561
577
|
* `credits` is what you were billed, and it is the only figure about cost the API states.
|
|
578
|
+
* What your balance does not cover is never collected: it is written off when credit next
|
|
579
|
+
* arrives or a subscription renews.
|
|
562
580
|
* For how long a job took, `finishedAt` minus `createdAt` is the wait you actually had,
|
|
563
581
|
* queue included — both are here and both are on the webhook, which states the same fields
|
|
564
582
|
* by the same names.
|
|
@@ -585,9 +603,10 @@ export interface JobData {
|
|
|
585
603
|
*
|
|
586
604
|
* `community` says the job is on the community queue, which runs on shared, free capacity —
|
|
587
605
|
* so it waits longer at busy times, and never stops. That is where an account's work goes
|
|
588
|
-
* when it is not paying,
|
|
589
|
-
* a reason to refuse a job — paying moves work onto paid compute that
|
|
590
|
-
* the right to submit — but on some models the community queue takes
|
|
606
|
+
* when it is not paying: no credit, and no subscription with work left this period. An empty
|
|
607
|
+
* balance is never itself a reason to refuse a job — paying moves work onto paid compute that
|
|
608
|
+
* scales with demand, not the right to submit — but on some models the community queue takes
|
|
609
|
+
* smaller files.
|
|
591
610
|
* `models()` lists each model's limits for your key, and a file over them is refused with
|
|
592
611
|
* the limit named.
|
|
593
612
|
*/
|
|
@@ -642,8 +661,9 @@ export class Job {
|
|
|
642
661
|
/**
|
|
643
662
|
* A link to the picture the model worked from. Where you named a region, this is that
|
|
644
663
|
* region — so what was used is something you can look at rather than something to take on
|
|
645
|
-
* trust. It is not the file you uploaded
|
|
646
|
-
*
|
|
664
|
+
* trust. It is not the file you uploaded but a new one written from it, not copied — so do
|
|
665
|
+
* not expect a hash of it to match your file's, and a JPEG or WebP is compressed again.
|
|
666
|
+
* Yours is removed on its own schedule.
|
|
647
667
|
*
|
|
648
668
|
* Null for a job that has not finished. A model that answers about a picture rather than
|
|
649
669
|
* producing one has it too: it is the picture that answer is about.
|
|
@@ -823,11 +843,12 @@ export class CraterView {
|
|
|
823
843
|
|
|
824
844
|
const traceId = resp.headers.get("X-Trace-Id") ?? undefined;
|
|
825
845
|
if (!resp.ok) {
|
|
826
|
-
const detail = await this.
|
|
846
|
+
const { detail, code } = await this.refusal(resp);
|
|
827
847
|
if (resp.status === 429) {
|
|
828
|
-
throw new RateLimited(detail, Number(resp.headers.get("Retry-After") ?? 0), traceId
|
|
848
|
+
throw new RateLimited(detail, Number(resp.headers.get("Retry-After") ?? 0), traceId,
|
|
849
|
+
code);
|
|
829
850
|
}
|
|
830
|
-
throw new CraterViewError(detail, resp.status, traceId);
|
|
851
|
+
throw new CraterViewError(detail, resp.status, traceId, undefined, code);
|
|
831
852
|
}
|
|
832
853
|
// A 204 carries no body, so asking for JSON throws on a call that succeeded.
|
|
833
854
|
if (resp.status === 204) return undefined as T;
|
|
@@ -838,15 +859,18 @@ export class CraterView {
|
|
|
838
859
|
}
|
|
839
860
|
}
|
|
840
861
|
|
|
841
|
-
/** The API's own words for a refusal, or a sentence saying the answer was not
|
|
842
|
-
|
|
862
|
+
/** The API's own words for a refusal and its code, or a sentence saying the answer was not
|
|
863
|
+
* its. One read of the body, which can be read once. */
|
|
864
|
+
private async refusal(resp: Response): Promise<{ detail: string; code?: string }> {
|
|
843
865
|
let body: unknown;
|
|
844
866
|
try {
|
|
845
867
|
body = await resp.json();
|
|
846
868
|
} catch {
|
|
847
|
-
return this.notTheApi(resp.status);
|
|
869
|
+
return { detail: this.notTheApi(resp.status) };
|
|
848
870
|
}
|
|
849
|
-
|
|
871
|
+
const said = body as { detail?: unknown; code?: unknown } | null;
|
|
872
|
+
return { detail: spoken(said?.detail) ?? this.notTheApi(resp.status),
|
|
873
|
+
code: typeof said?.code === "string" ? said.code : undefined };
|
|
850
874
|
}
|
|
851
875
|
|
|
852
876
|
/**
|
|
@@ -973,13 +997,12 @@ export class CraterView {
|
|
|
973
997
|
* until the history runs out. 200 is the largest page the API serves, and a bigger number
|
|
974
998
|
* is fetched as 200 rather than sent and refused.
|
|
975
999
|
*/
|
|
976
|
-
async *jobs(options: { limit?: number
|
|
1000
|
+
async *jobs(options: { limit?: number } = {}): AsyncGenerator<Job> {
|
|
977
1001
|
let before: string | undefined;
|
|
978
1002
|
for (;;) {
|
|
979
1003
|
const params = new URLSearchParams({
|
|
980
1004
|
limit: String(Math.min(options.limit ?? 50, MAX_PAGE)),
|
|
981
1005
|
});
|
|
982
|
-
if (options.status) params.set("status", options.status);
|
|
983
1006
|
if (before) params.set("before", before);
|
|
984
1007
|
|
|
985
1008
|
const page = await this.request<{ data: JobData[]; has_more: boolean; next_before?: string }>(
|
|
@@ -1000,7 +1023,9 @@ export class CraterView {
|
|
|
1000
1023
|
* charged when it succeeds, so queued and running work is not in the figures yet, and a
|
|
1001
1024
|
* failed job costs nothing.
|
|
1002
1025
|
*
|
|
1003
|
-
* `credits_remaining` is always a number, floored at zero.
|
|
1026
|
+
* `credits_remaining` is always a number, floored at zero. A balance of zero never stops a
|
|
1027
|
+
* submission: `community` says whether your next job runs on the community queue, which is
|
|
1028
|
+
* free and can wait longer at busy times, or on paid compute. `jobs_in_flight` and
|
|
1004
1029
|
* `max_jobs_in_flight` are the state of the queue and are what a 429 on submit is about.
|
|
1005
1030
|
*
|
|
1006
1031
|
* Account-wide, so every key on an account reports the same figures.
|
|
@@ -1010,7 +1035,8 @@ export class CraterView {
|
|
|
1010
1035
|
}
|
|
1011
1036
|
|
|
1012
1037
|
/**
|
|
1013
|
-
* The secret your callbacks are signed with, created the first time you ask
|
|
1038
|
+
* The secret your callbacks are signed with, created the first time you ask or with your
|
|
1039
|
+
* first delivery, whichever comes first.
|
|
1014
1040
|
*
|
|
1015
1041
|
* Pass it to {@link verifyWebhook}. Unlike an API key this can be read back whenever you
|
|
1016
1042
|
* need it — but treat it as a credential all the same: anyone holding it can produce a
|