@lumavox/server 0.0.0-stage → 0.2.1

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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +134 -2
  3. package/dist/client.d.ts +59 -0
  4. package/dist/client.js +52 -0
  5. package/dist/errors.d.ts +86 -0
  6. package/dist/errors.js +137 -0
  7. package/dist/gen/index.d.ts +1 -0
  8. package/dist/gen/index.js +2 -0
  9. package/dist/gen/types.gen.d.ts +6563 -0
  10. package/dist/gen/types.gen.js +2 -0
  11. package/dist/index.d.ts +23 -0
  12. package/dist/index.js +8 -0
  13. package/dist/paging.d.ts +34 -0
  14. package/dist/paging.js +41 -0
  15. package/dist/refuse.d.ts +11 -0
  16. package/dist/refuse.js +12 -0
  17. package/dist/resources/agents.d.ts +34 -0
  18. package/dist/resources/agents.js +33 -0
  19. package/dist/resources/assets.d.ts +59 -0
  20. package/dist/resources/assets.js +80 -0
  21. package/dist/resources/end-users.d.ts +73 -0
  22. package/dist/resources/end-users.js +139 -0
  23. package/dist/resources/ephemeral-keys.d.ts +65 -0
  24. package/dist/resources/ephemeral-keys.js +129 -0
  25. package/dist/resources/generations.d.ts +40 -0
  26. package/dist/resources/generations.js +55 -0
  27. package/dist/resources/knowledge.d.ts +58 -0
  28. package/dist/resources/knowledge.js +91 -0
  29. package/dist/resources/resource.d.ts +52 -0
  30. package/dist/resources/resource.js +68 -0
  31. package/dist/resources/speech.d.ts +80 -0
  32. package/dist/resources/speech.js +64 -0
  33. package/dist/resources/templates.d.ts +37 -0
  34. package/dist/resources/templates.js +50 -0
  35. package/dist/resources/usage.d.ts +15 -0
  36. package/dist/resources/usage.js +16 -0
  37. package/dist/resources/voices.d.ts +41 -0
  38. package/dist/resources/voices.js +53 -0
  39. package/dist/runtime.d.ts +22 -0
  40. package/dist/runtime.js +24 -0
  41. package/dist/stream.d.ts +6 -0
  42. package/dist/stream.js +37 -0
  43. package/dist/transport.d.ts +79 -0
  44. package/dist/transport.js +215 -0
  45. package/dist/upload.d.ts +37 -0
  46. package/dist/upload.js +34 -0
  47. package/dist/wait.d.ts +42 -0
  48. package/dist/wait.js +51 -0
  49. package/package.json +50 -4
@@ -0,0 +1,129 @@
1
+ /**
2
+ * `client.ephemeralKeys`: the keys an end user's device talks to agents
3
+ * with — `/v1/ephemeral-keys*` and `/v1/token`.
4
+ *
5
+ * - **A key is prepaid**: its `credit_limit` is set aside from the balance
6
+ * when it is minted (402 when it cannot be), and what it does not spend
7
+ * comes back when it settles.
8
+ * - **`create` clamps the lifetime by default** to the platform's maximum,
9
+ * read from `limits()` — which the API never does: past a cap is a 422
10
+ * (`apps/api/src/org/minting.ts`). The plan's own caps for one end
11
+ * user's key are not reported yet (left-overs #107), so a key within the
12
+ * platform's can still be refused.
13
+ * - **`limits()` is cached five minutes per client**, so a mint is two
14
+ * requests cold and one warm. Concurrent cold callers share one read.
15
+ * - **`revoke` answers at once**, with the figures from before the key's
16
+ * end user DO settles: revocation leaves through the org DO's outbox
17
+ * (`apps/api/src/org/areas/keys.ts` `revokeEphemeral`). Waiting for the
18
+ * final figures needs the key's read route (left-overs #106).
19
+ * - **`inspect` reads a key as itself** (`GET /v1/token` with that key).
20
+ * Its `credits_spent` moves only when the key settles — at the end, or
21
+ * daily — so a live key reads as unspent (left-overs #105).
22
+ */
23
+ import { AuthenticationError } from "../errors.js";
24
+ import { byOffset } from "../paging.js";
25
+ import { orNull } from "../transport.js";
26
+ import { Resource } from "./resource.js";
27
+ const LIMITS_TTL_MS = 5 * 60_000;
28
+ /** The route's own page bound. */
29
+ const PAGE = 500;
30
+ /** `promise`, or the signal's reason when it aborts first. */
31
+ function until(promise, signal) {
32
+ if (!signal)
33
+ return promise;
34
+ if (signal.aborted)
35
+ return Promise.reject(signal.reason);
36
+ return new Promise((resolve, reject) => {
37
+ const stop = () => reject(signal.reason);
38
+ signal.addEventListener("abort", stop, { once: true });
39
+ promise.then((value) => {
40
+ signal.removeEventListener("abort", stop);
41
+ resolve(value);
42
+ }, (error) => {
43
+ signal.removeEventListener("abort", stop);
44
+ reject(error);
45
+ });
46
+ });
47
+ }
48
+ export class EphemeralKeys extends Resource {
49
+ cached = null;
50
+ /**
51
+ * What a key may be minted with: the **platform's** longest lifetime and
52
+ * largest credit limit, and the scopes this API key may grant. Cached
53
+ * five minutes per client.
54
+ */
55
+ limits(options = {}) {
56
+ const now = Date.now();
57
+ if (!this.cached || now - this.cached.at > LIMITS_TTL_MS) {
58
+ // Shared by every caller until it settles, so it is read without
59
+ // any one caller's signal; each waits on its own below.
60
+ const { signal: _, ...shared } = options;
61
+ const limits = this.send("GET", "/limits", shared);
62
+ const entry = { at: now, limits };
63
+ this.cached = entry;
64
+ limits.catch(() => {
65
+ // swallowed: a failed read is not cached; its callers get the error.
66
+ if (this.cached === entry)
67
+ this.cached = null;
68
+ });
69
+ }
70
+ return until(this.cached.limits, options.signal);
71
+ }
72
+ /**
73
+ * Mints a key for one end user — returned **once**, as `key`. Scopes
74
+ * are exactly `["agent"]` (the default). 402 when the balance cannot set
75
+ * `credit_limit` aside, 422 past a cap, 429 past the plan's mints a
76
+ * minute (retried after its `Retry-After`).
77
+ */
78
+ async create(body, options = {}) {
79
+ const { clampTtl = true, ...request } = options;
80
+ let sent = body;
81
+ if (clampTtl) {
82
+ const limits = await this.limits(request);
83
+ const wanted = body.expires_in_seconds ?? limits.default_ttl_seconds;
84
+ sent = {
85
+ ...body,
86
+ expires_in_seconds: Math.min(wanted, limits.max_ttl_seconds),
87
+ };
88
+ }
89
+ return this.send("POST", "", request, sent);
90
+ }
91
+ /**
92
+ * Revokes a key: it is refused from now on, and what it did not spend
93
+ * comes back when it settles. `null` when there is no such key — a
94
+ * revoked, expired or unknown id answers as itself, a non-UUID as none.
95
+ */
96
+ revoke(id, options) {
97
+ return orNull(this.send("DELETE", this.at(id), options));
98
+ }
99
+ /**
100
+ * A key as it reads itself: its scopes, limit, spend and expiry. `null`
101
+ * when the key is refused — expired, revoked, spent or not a key.
102
+ */
103
+ async inspect(key, options = {}) {
104
+ let self;
105
+ try {
106
+ const answer = await this.api.request({ method: "GET", path: "/v1/token", token: key }, options);
107
+ self = answer.data;
108
+ }
109
+ catch (error) {
110
+ if (error instanceof AuthenticationError)
111
+ return null;
112
+ throw error;
113
+ }
114
+ if (self.kind !== "ephemeral") {
115
+ throw new TypeError("inspect() reads an ephemeral key; this is an API key.");
116
+ }
117
+ return self;
118
+ }
119
+ /** One page of this organisation's keys: live ones unless `live_only` is `false`. */
120
+ page(params = {}, options) {
121
+ return this.send("GET", "", options, params);
122
+ }
123
+ /** Every key, page after page (`limit` a page, 500 by default). */
124
+ list(params = {}, options) {
125
+ return byOffset(async (offset, limit) => ({
126
+ items: await this.page({ ...params, offset, limit }, options),
127
+ }), params.limit ?? PAGE);
128
+ }
129
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Everything queued that a provider makes: `client.images`, `videos`,
3
+ * `audio`, `skybox` and `transcriptions` — each a create that answers 202
4
+ * with a `gen_…` id, and its own read — and `client.generations`, every
5
+ * capability's at once.
6
+ *
7
+ * - **Charged when accepted**, refunded in full when it fails or is
8
+ * moderated. A create is a POST, so the transport resends it only after
9
+ * a 429, which the API answers before charging (left-overs #108 would
10
+ * make it safe to resend).
11
+ * - **`generate(body)` is `create` and `wait`**, answering the finished
12
+ * generation; `wait(id)` polls the capability's own read (`wait.ts` has
13
+ * the intervals and their ceiling). A video takes minutes: give it a
14
+ * `signal` with the deadline you want.
15
+ * - **Each read sees its own capability**: an image's id is a 404 on
16
+ * `videos.get`. `generations.get` reads any the key may generate.
17
+ */
18
+ import type { GenerationInfo, GenerationPage, ListGenerationsData, SkyboxStyle } from "../gen/index.js";
19
+ import type { RequestOptions } from "../transport.js";
20
+ import { Polled } from "./resource.js";
21
+ export type GenerationListParams = NonNullable<ListGenerationsData["query"]>;
22
+ /** One capability's queue: `/v1/images`, `/v1/videos`, … */
23
+ export declare class Jobs<Body> extends Polled<GenerationInfo> {
24
+ /** Accepts and charges it, answering at once with its `gen_…` id. */
25
+ create(body: Body, options?: RequestOptions): Promise<GenerationInfo>;
26
+ /** Creates it and waits: the generation once it is final. */
27
+ generate(body: Body, options?: RequestOptions): Promise<GenerationInfo>;
28
+ }
29
+ /** `/v1/skybox`, and the provider's styles. */
30
+ export declare class Skybox<Body> extends Jobs<Body> {
31
+ /** The styles available now, each with its `style_id`; empty when the provider is unreachable. */
32
+ styles(options?: RequestOptions): Promise<SkyboxStyle[]>;
33
+ }
34
+ /** Every generation the key may make, read-only. */
35
+ export declare class Generations extends Polled<GenerationInfo> {
36
+ /** One page, newest first, with the `next_cursor` of the next. */
37
+ page(params?: GenerationListParams, options?: RequestOptions): Promise<GenerationPage>;
38
+ /** Every generation, newest first, page after page (100 a page by default). */
39
+ list(params?: Omit<GenerationListParams, "cursor">, options?: RequestOptions): AsyncGenerator<GenerationInfo, void, undefined>;
40
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Everything queued that a provider makes: `client.images`, `videos`,
3
+ * `audio`, `skybox` and `transcriptions` — each a create that answers 202
4
+ * with a `gen_…` id, and its own read — and `client.generations`, every
5
+ * capability's at once.
6
+ *
7
+ * - **Charged when accepted**, refunded in full when it fails or is
8
+ * moderated. A create is a POST, so the transport resends it only after
9
+ * a 429, which the API answers before charging (left-overs #108 would
10
+ * make it safe to resend).
11
+ * - **`generate(body)` is `create` and `wait`**, answering the finished
12
+ * generation; `wait(id)` polls the capability's own read (`wait.ts` has
13
+ * the intervals and their ceiling). A video takes minutes: give it a
14
+ * `signal` with the deadline you want.
15
+ * - **Each read sees its own capability**: an image's id is a 404 on
16
+ * `videos.get`. `generations.get` reads any the key may generate.
17
+ */
18
+ import { byCursor } from "../paging.js";
19
+ import { poll } from "../wait.js";
20
+ import { Polled } from "./resource.js";
21
+ /** The route's own page bound. */
22
+ const PAGE = 100;
23
+ /** One capability's queue: `/v1/images`, `/v1/videos`, … */
24
+ export class Jobs extends Polled {
25
+ /** Accepts and charges it, answering at once with its `gen_…` id. */
26
+ create(body, options) {
27
+ return this.send("POST", "", options, body);
28
+ }
29
+ /** Creates it and waits: the generation once it is final. */
30
+ async generate(body, options) {
31
+ const job = await this.create(body, options);
32
+ return poll(() => this.get(job.id, options), options, job);
33
+ }
34
+ }
35
+ /** `/v1/skybox`, and the provider's styles. */
36
+ export class Skybox extends Jobs {
37
+ /** The styles available now, each with its `style_id`; empty when the provider is unreachable. */
38
+ styles(options) {
39
+ return this.send("GET", "/styles", options);
40
+ }
41
+ }
42
+ /** Every generation the key may make, read-only. */
43
+ export class Generations extends Polled {
44
+ /** One page, newest first, with the `next_cursor` of the next. */
45
+ page(params = {}, options) {
46
+ return this.send("GET", "", options, params);
47
+ }
48
+ /** Every generation, newest first, page after page (100 a page by default). */
49
+ list(params = {}, options) {
50
+ return byCursor(async (cursor) => {
51
+ const page = await this.page({ limit: PAGE, ...params, cursor }, options);
52
+ return { items: page.generations, next: page.next_cursor };
53
+ });
54
+ }
55
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * `client.knowledge`: what agents may look up — `/v1/knowledge*`. An entry
3
+ * is written, read from a page (`fromUrl`) or from a file (`fromFile`,
4
+ * `upload`), indexed on the jobs Worker, and found by the agents it is
5
+ * given to (`setAgents`, or `agents.knowledge.set`).
6
+ *
7
+ * - **Indexing is a job**: an entry is `pending` until it is `ready` or
8
+ * `failed` (`error` says why); `wait(id)` reads it until then. An edit
9
+ * or a failed re-index keeps the previous version `searchable`.
10
+ * - **`upload(file, fields)`** is the ticket, the `PUT` and `fromFile` in
11
+ * one (`upload.ts`); the file's extension says how it is read, 10 MB at
12
+ * most.
13
+ * - **`list()` pages by offset**, 200 a page, with the `total` the filter
14
+ * matches.
15
+ * - **`update` with `expected_version`** refuses an edit made over
16
+ * somebody else's (409); `delete` answers the entry as it was, and
17
+ * `null` for one already gone.
18
+ */
19
+ import type { KnowledgeAssigned, KnowledgeAssignment, KnowledgeBulk, KnowledgeBulkResult, KnowledgeCategories, KnowledgeCreate, KnowledgeFileCreate, KnowledgeInfo, KnowledgeInsights, KnowledgeInsightsData, KnowledgeSearchList, KnowledgeSearchRequest, KnowledgeSearchResult, KnowledgeUpdate, KnowledgeUploadRequest, KnowledgeUploadTicket, KnowledgeUrlCreate, ListKnowledgeData } from "../gen/index.js";
20
+ import type { RequestOptions } from "../transport.js";
21
+ import { type UploadData, type UploadOptions } from "../upload.js";
22
+ import { Waitable } from "./resource.js";
23
+ export type KnowledgeListParams = NonNullable<ListKnowledgeData["query"]>;
24
+ export type KnowledgeInsightsParams = NonNullable<KnowledgeInsightsData["query"]>;
25
+ /** A file's entry: `fromFile`'s fields, and its type when not its extension's. */
26
+ export type KnowledgeUploadFields = Omit<KnowledgeFileCreate, "upload_id"> & {
27
+ content_type?: string | null;
28
+ };
29
+ export declare class Knowledge extends Waitable<KnowledgeInfo, KnowledgeUpdate> {
30
+ /** One page, with the `total` the filter matches. */
31
+ page(params?: KnowledgeListParams, options?: RequestOptions): Promise<KnowledgeSearchList>;
32
+ /** Every entry the filter matches, page after page (200 a page by default). */
33
+ list(params?: Omit<KnowledgeListParams, "offset">, options?: RequestOptions): AsyncGenerator<KnowledgeInfo, void, undefined>;
34
+ /** A written entry, indexed after it is saved. */
35
+ create(body: KnowledgeCreate, options?: RequestOptions): Promise<KnowledgeInfo>;
36
+ /** Many written entries at once: what was made, and what was refused. */
37
+ bulk(body: KnowledgeBulk, options?: RequestOptions): Promise<KnowledgeBulkResult>;
38
+ /** An entry read from a page, and refreshed on its schedule. */
39
+ fromUrl(body: KnowledgeUrlCreate, options?: RequestOptions): Promise<KnowledgeInfo>;
40
+ /** A signed URL to `PUT` a file to. */
41
+ uploadUrl(body: KnowledgeUploadRequest, options?: RequestOptions): Promise<KnowledgeUploadTicket>;
42
+ /** An entry read from a file already `PUT` to its ticket. */
43
+ fromFile(body: KnowledgeFileCreate, options?: RequestOptions): Promise<KnowledgeInfo>;
44
+ /** Uploads a file and reads it: `uploadUrl`, the `PUT`, `fromFile`. */
45
+ upload(file: UploadData, fields: KnowledgeUploadFields, options?: UploadOptions): Promise<KnowledgeInfo>;
46
+ /** Indexes the body it has again. */
47
+ reindex(id: string, options?: RequestOptions): Promise<KnowledgeInfo>;
48
+ /** Reads its page again now. */
49
+ refresh(id: string, options?: RequestOptions): Promise<KnowledgeInfo>;
50
+ /** Replaces the agents that may look it up. */
51
+ setAgents(id: string, body: KnowledgeAssignment, options?: RequestOptions): Promise<KnowledgeAssigned>;
52
+ /** What an agent would find for a question. */
53
+ search(body: KnowledgeSearchRequest, options?: RequestOptions): Promise<KnowledgeSearchResult>;
54
+ /** The categories in use. */
55
+ categories(options?: RequestOptions): Promise<KnowledgeCategories>;
56
+ /** What was looked up, and what went unanswered: 7 days by default. */
57
+ insights(params?: KnowledgeInsightsParams, options?: RequestOptions): Promise<KnowledgeInsights>;
58
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * `client.knowledge`: what agents may look up — `/v1/knowledge*`. An entry
3
+ * is written, read from a page (`fromUrl`) or from a file (`fromFile`,
4
+ * `upload`), indexed on the jobs Worker, and found by the agents it is
5
+ * given to (`setAgents`, or `agents.knowledge.set`).
6
+ *
7
+ * - **Indexing is a job**: an entry is `pending` until it is `ready` or
8
+ * `failed` (`error` says why); `wait(id)` reads it until then. An edit
9
+ * or a failed re-index keeps the previous version `searchable`.
10
+ * - **`upload(file, fields)`** is the ticket, the `PUT` and `fromFile` in
11
+ * one (`upload.ts`); the file's extension says how it is read, 10 MB at
12
+ * most.
13
+ * - **`list()` pages by offset**, 200 a page, with the `total` the filter
14
+ * matches.
15
+ * - **`update` with `expected_version`** refuses an edit made over
16
+ * somebody else's (409); `delete` answers the entry as it was, and
17
+ * `null` for one already gone.
18
+ */
19
+ import { byOffset } from "../paging.js";
20
+ import { put, sizeOf, typeOf, } from "../upload.js";
21
+ import { Waitable } from "./resource.js";
22
+ /** The route's own page bound. */
23
+ const PAGE = 200;
24
+ export class Knowledge extends Waitable {
25
+ /** One page, with the `total` the filter matches. */
26
+ page(params = {}, options) {
27
+ return this.send("GET", "", options, params);
28
+ }
29
+ /** Every entry the filter matches, page after page (200 a page by default). */
30
+ list(params = {}, options) {
31
+ return byOffset(async (offset, limit) => {
32
+ const page = await this.page({ ...params, offset, limit }, options);
33
+ return { items: page.entries, total: page.total };
34
+ }, params.limit ?? PAGE);
35
+ }
36
+ /** A written entry, indexed after it is saved. */
37
+ create(body, options) {
38
+ return this.send("POST", "", options, body);
39
+ }
40
+ /** Many written entries at once: what was made, and what was refused. */
41
+ bulk(body, options) {
42
+ return this.send("POST", "/bulk", options, body);
43
+ }
44
+ /** An entry read from a page, and refreshed on its schedule. */
45
+ fromUrl(body, options) {
46
+ return this.send("POST", "/urls", options, body);
47
+ }
48
+ /** A signed URL to `PUT` a file to. */
49
+ uploadUrl(body, options) {
50
+ return this.send("POST", "/upload-url", options, body);
51
+ }
52
+ /** An entry read from a file already `PUT` to its ticket. */
53
+ fromFile(body, options) {
54
+ return this.send("POST", "/files", options, body);
55
+ }
56
+ /** Uploads a file and reads it: `uploadUrl`, the `PUT`, `fromFile`. */
57
+ async upload(file, fields, options = {}) {
58
+ const { content_type, ...body } = fields;
59
+ const ticket = await this.uploadUrl({
60
+ filename: body.filename,
61
+ size: sizeOf(file, options.size) ?? null,
62
+ content_type: typeOf(file, content_type) || null,
63
+ }, options);
64
+ await put(this.api, ticket, file, ticket.content_type, options);
65
+ return this.settled(await this.fromFile({ ...body, upload_id: ticket.upload_id }, options), options);
66
+ }
67
+ /** Indexes the body it has again. */
68
+ reindex(id, options) {
69
+ return this.send("POST", this.at(id, "/reindex"), options);
70
+ }
71
+ /** Reads its page again now. */
72
+ refresh(id, options) {
73
+ return this.send("POST", this.at(id, "/refresh"), options);
74
+ }
75
+ /** Replaces the agents that may look it up. */
76
+ setAgents(id, body, options) {
77
+ return this.send("PUT", this.at(id, "/agents"), options, body);
78
+ }
79
+ /** What an agent would find for a question. */
80
+ search(body, options) {
81
+ return this.send("POST", "/search", options, body);
82
+ }
83
+ /** The categories in use. */
84
+ categories(options) {
85
+ return this.send("GET", "/categories", options);
86
+ }
87
+ /** What was looked up, and what went unanswered: 7 days by default. */
88
+ insights(params = {}, options) {
89
+ return this.send("GET", "/insights", options, params);
90
+ }
91
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * What every resource shares, as a ladder of bases each method is written
3
+ * once in — the SDK's size is a budget (`test/dist.test.ts`):
4
+ *
5
+ * - **`Resource`**: the transport, and the path its routes start with.
6
+ * `send` takes the rest of the path, and `at(id)` is one item's, the id
7
+ * encoded (an id the caller holds may carry `/` or `?`).
8
+ * - **`Readable`**: `get(id)`; **`Polled`** adds `wait(id)` (`wait.ts`),
9
+ * for a generation, which is read and waited on and nothing else.
10
+ * - **`Editable`**: `update(id, body)` (`PATCH`) and `delete(id)`, which
11
+ * answers `null` when there is nothing by that id — already done.
12
+ * - **`Waitable`**: an editable whose items are jobs — a voice, an asset,
13
+ * a knowledge entry — with `wait(id)` and `settled`, which an upload
14
+ * answers through.
15
+ *
16
+ * Fields are `declare`d, not defined: each is assigned in a constructor,
17
+ * and a definition is bytes in every customer's bundle.
18
+ */
19
+ import { type Method, type RequestOptions, type Transport } from "../transport.js";
20
+ import type { UploadOptions } from "../upload.js";
21
+ import { type Job } from "../wait.js";
22
+ export declare class Resource {
23
+ protected readonly api: Transport;
24
+ protected readonly base: string;
25
+ constructor(api: Transport, base: string);
26
+ /** A JSON call to `base` + `rest`: a GET's `data` is its query, any other's its body. */
27
+ protected send<T>(method: Method, rest: string, options?: RequestOptions, data?: unknown): Promise<T>;
28
+ /** The rest of item `id`'s path, and of what follows it. */
29
+ protected at(id: string, rest?: string): string;
30
+ }
31
+ export declare class Readable<Item> extends Resource {
32
+ /** One of them. A `NotFoundError` when there is none. */
33
+ get(id: string, options?: RequestOptions): Promise<Item>;
34
+ }
35
+ export declare class Polled<Item extends Job> extends Readable<Item> {
36
+ /** Reads it until its `status` is final (`wait.ts`). */
37
+ wait(id: string, options?: RequestOptions): Promise<Item>;
38
+ }
39
+ export declare class Editable<Item, Update, Updated = Item, Deleted = Item> extends Readable<Item> {
40
+ /** Changes what `body` gives; absent fields are untouched. */
41
+ update(id: string, body: Update, options?: RequestOptions): Promise<Updated>;
42
+ /** Deletes it; `null` when there is none by that id — already done. */
43
+ delete(id: string, options?: RequestOptions): Promise<Deleted | null>;
44
+ }
45
+ export declare class Waitable<Item extends Job & {
46
+ id: string;
47
+ }, Update, Updated = Item, Deleted = Item> extends Editable<Item, Update, Updated, Deleted> {
48
+ /** Reads it until its `status` is final (`wait.ts`). */
49
+ wait(id: string, options?: RequestOptions): Promise<Item>;
50
+ /** `item` as made, or as it ended when `options.wait` says to wait. */
51
+ protected settled(item: Item, options: UploadOptions): Promise<Item>;
52
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * What every resource shares, as a ladder of bases each method is written
3
+ * once in — the SDK's size is a budget (`test/dist.test.ts`):
4
+ *
5
+ * - **`Resource`**: the transport, and the path its routes start with.
6
+ * `send` takes the rest of the path, and `at(id)` is one item's, the id
7
+ * encoded (an id the caller holds may carry `/` or `?`).
8
+ * - **`Readable`**: `get(id)`; **`Polled`** adds `wait(id)` (`wait.ts`),
9
+ * for a generation, which is read and waited on and nothing else.
10
+ * - **`Editable`**: `update(id, body)` (`PATCH`) and `delete(id)`, which
11
+ * answers `null` when there is nothing by that id — already done.
12
+ * - **`Waitable`**: an editable whose items are jobs — a voice, an asset,
13
+ * a knowledge entry — with `wait(id)` and `settled`, which an upload
14
+ * answers through.
15
+ *
16
+ * Fields are `declare`d, not defined: each is assigned in a constructor,
17
+ * and a definition is bytes in every customer's bundle.
18
+ */
19
+ import { orNull, segment, } from "../transport.js";
20
+ import { poll } from "../wait.js";
21
+ export class Resource {
22
+ constructor(api, base) {
23
+ this.api = api;
24
+ this.base = base;
25
+ }
26
+ /** A JSON call to `base` + `rest`: a GET's `data` is its query, any other's its body. */
27
+ send(method, rest, options, data) {
28
+ return this.api.send(method, this.base + rest, options, data);
29
+ }
30
+ /** The rest of item `id`'s path, and of what follows it. */
31
+ at(id, rest = "") {
32
+ return `/${segment(id)}${rest}`;
33
+ }
34
+ }
35
+ export class Readable extends Resource {
36
+ /** One of them. A `NotFoundError` when there is none. */
37
+ get(id, options) {
38
+ return this.send("GET", this.at(id), options);
39
+ }
40
+ }
41
+ export class Polled extends Readable {
42
+ /** Reads it until its `status` is final (`wait.ts`). */
43
+ wait(id, options) {
44
+ return poll(() => this.get(id, options), options);
45
+ }
46
+ }
47
+ export class Editable extends Readable {
48
+ /** Changes what `body` gives; absent fields are untouched. */
49
+ update(id, body, options) {
50
+ return this.send("PATCH", this.at(id), options, body);
51
+ }
52
+ /** Deletes it; `null` when there is none by that id — already done. */
53
+ delete(id, options) {
54
+ return orNull(this.send("DELETE", this.at(id), options));
55
+ }
56
+ }
57
+ export class Waitable extends Editable {
58
+ /** Reads it until its `status` is final (`wait.ts`). */
59
+ wait(id, options) {
60
+ return poll(() => this.get(id, options), options);
61
+ }
62
+ /** `item` as made, or as it ended when `options.wait` says to wait. */
63
+ async settled(item, options) {
64
+ return options.wait
65
+ ? poll(() => this.get(item.id, options), options, item)
66
+ : item;
67
+ }
68
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Speech answered in the call: `client.tts` — `POST /v1/tts`, text to
3
+ * audio — and `client.stt` — `POST /v1/stt`, audio to text. Both metered
4
+ * POSTs, resent only after a 429. For long text or audio, the queued doors
5
+ * are `audio` and `transcriptions` (`generations.ts`).
6
+ *
7
+ * - **`tts.create(body)` answers the audio whole** (an `ArrayBuffer`, WAV
8
+ * or raw `pcm`) **or, with `stream: true`, as it is made** (a
9
+ * `ReadableStream` of `pcm`), beside what its headers say. A whole
10
+ * answer names its sample rate (`X-Sample-Rate`); a stream names none,
11
+ * so its rate is the one asked for, 24 000 Hz unset (the API's default,
12
+ * `@ino/core/speech/catalog`; left-overs #111).
13
+ * - **`stt.create(audio, params)` sends the audio as the body**, the
14
+ * options as the query — the route's raw form, which a stream can be
15
+ * sent as, unlike a multipart one. It answers the transcript, or with
16
+ * `stream: true` an async iterator of its NDJSON events (WAV or raw PCM
17
+ * only). Up to 25 MB.
18
+ * - **A stream's timeout ends at its headers**; `signal` still ends it.
19
+ */
20
+ import type { StreamDone, StreamError, StreamSegment, StreamTranscript, TranscribeData, TranscriptWord, TtsRequest } from "../gen/index.js";
21
+ import type { RequestOptions } from "../transport.js";
22
+ import { type UploadData } from "../upload.js";
23
+ import { Resource } from "./resource.js";
24
+ /** Speech, and what the response's headers say about it. */
25
+ export interface Speech<Body> {
26
+ /** The audio: an `ArrayBuffer`, or a `ReadableStream` when streamed. */
27
+ body: Body;
28
+ /** `audio/wav` or `audio/pcm` (16-bit, mono, little-endian). */
29
+ contentType: string;
30
+ /** Hz: what was produced, or for a stream the rate asked for. */
31
+ sampleRate: number;
32
+ /** The model that spoke. */
33
+ model: string | null;
34
+ /** Seconds of audio; `null` for a stream, which is not measured up front. */
35
+ audioSeconds: number | null;
36
+ requestId: string;
37
+ }
38
+ export declare class Tts extends Resource {
39
+ /** The audio as it is made: `pcm` only. */
40
+ create(body: TtsRequest & {
41
+ stream: true;
42
+ }, options?: RequestOptions): Promise<Speech<ReadableStream<Uint8Array>>>;
43
+ /** The audio whole, once it is all made. */
44
+ create(body: TtsRequest & {
45
+ stream?: false;
46
+ }, options?: RequestOptions): Promise<Speech<ArrayBuffer>>;
47
+ }
48
+ /** The options `/v1/stt` takes, and the audio's type when it is no `Blob`'s. */
49
+ export type SttParams = Omit<TranscribeData["body"], "file"> & {
50
+ /** The audio's `Content-Type`; a Blob's own, or `application/octet-stream`. */
51
+ content_type?: string;
52
+ };
53
+ /**
54
+ * `POST /v1/stt`'s whole answer. The document declares no schema for it
55
+ * (left-overs #112), so this is the route's own (`transcript()` in
56
+ * `apps/api/src/routes/stt.ts`), written out; `apps/api/test/sdk.test.ts`
57
+ * holds the two together.
58
+ */
59
+ export interface Transcript {
60
+ text: string;
61
+ language_code: string | null;
62
+ language_probability: number | null;
63
+ /** What was charged, per minute. */
64
+ duration_seconds: number;
65
+ model: string;
66
+ /** With `timestamps: true`; `null` otherwise. */
67
+ words: TranscriptWord[] | null;
68
+ }
69
+ /** One line of a streamed transcript. */
70
+ export type SttEvent = StreamSegment | StreamTranscript | StreamError | StreamDone;
71
+ export declare class Stt extends Resource {
72
+ /** The transcript as it is made: an iterator of its events. */
73
+ create(audio: UploadData, params: SttParams & {
74
+ stream: true;
75
+ }, options?: RequestOptions): AsyncGenerator<SttEvent, void, undefined>;
76
+ /** The transcript, once it is all made. */
77
+ create(audio: UploadData, params?: SttParams & {
78
+ stream?: false;
79
+ }, options?: RequestOptions): Promise<Transcript>;
80
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Speech answered in the call: `client.tts` — `POST /v1/tts`, text to
3
+ * audio — and `client.stt` — `POST /v1/stt`, audio to text. Both metered
4
+ * POSTs, resent only after a 429. For long text or audio, the queued doors
5
+ * are `audio` and `transcriptions` (`generations.ts`).
6
+ *
7
+ * - **`tts.create(body)` answers the audio whole** (an `ArrayBuffer`, WAV
8
+ * or raw `pcm`) **or, with `stream: true`, as it is made** (a
9
+ * `ReadableStream` of `pcm`), beside what its headers say. A whole
10
+ * answer names its sample rate (`X-Sample-Rate`); a stream names none,
11
+ * so its rate is the one asked for, 24 000 Hz unset (the API's default,
12
+ * `@ino/core/speech/catalog`; left-overs #111).
13
+ * - **`stt.create(audio, params)` sends the audio as the body**, the
14
+ * options as the query — the route's raw form, which a stream can be
15
+ * sent as, unlike a multipart one. It answers the transcript, or with
16
+ * `stream: true` an async iterator of its NDJSON events (WAV or raw PCM
17
+ * only). Up to 25 MB.
18
+ * - **A stream's timeout ends at its headers**; `signal` still ends it.
19
+ */
20
+ import { ndjson } from "../stream.js";
21
+ import { typeOf } from "../upload.js";
22
+ import { Resource } from "./resource.js";
23
+ /** The rate a streamed `pcm` is spoken at when the request named none. */
24
+ const DEFAULT_SAMPLE_RATE = 24_000;
25
+ export class Tts extends Resource {
26
+ async create(body, options) {
27
+ const answer = await this.api.request({
28
+ method: "POST",
29
+ path: this.base,
30
+ body,
31
+ read: body.stream ? "stream" : "bytes",
32
+ }, options);
33
+ const header = (name) => answer.headers.get(name);
34
+ const seconds = header("x-audio-seconds");
35
+ return {
36
+ body: answer.data,
37
+ contentType: header("content-type") ?? "",
38
+ sampleRate: Number(header("x-sample-rate") ?? body.sample_rate ?? DEFAULT_SAMPLE_RATE),
39
+ model: header("x-model"),
40
+ audioSeconds: seconds === null ? null : Number(seconds),
41
+ requestId: answer.requestId,
42
+ };
43
+ }
44
+ }
45
+ export class Stt extends Resource {
46
+ create(audio, params = {}, options) {
47
+ const { content_type, ...query } = params;
48
+ const call = {
49
+ method: "POST",
50
+ path: this.base,
51
+ query: query,
52
+ raw: audio,
53
+ type: typeOf(audio, content_type) || "application/octet-stream",
54
+ };
55
+ if (!params.stream) {
56
+ return this.api.request(call, options).then((a) => a.data);
57
+ }
58
+ const api = this.api;
59
+ return (async function* () {
60
+ const answer = await api.request({ ...call, read: "stream" }, options);
61
+ yield* ndjson(answer.data, answer.requestId);
62
+ })();
63
+ }
64
+ }