@lumavox/server 0.2.2 → 0.4.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/dist/index.d.ts CHANGED
@@ -5,8 +5,9 @@
5
5
  * SDK's own options (`RequestOptions`) go in a last argument.
6
6
  */
7
7
  export { type Client, type ClientOptions, createClient, DEFAULT_BASE_URL, } from "./client.js";
8
- export { AuthenticationError, ConflictError, ConnectionError, InsufficientCreditsError, NotFoundError, PermissionError, PlatformError, type PlatformErrorInit, RateLimitError, ServerError, TimeoutError, ValidationError, } from "./errors.js";
8
+ export { AuthenticationError, ConflictError, ConnectionError, InsufficientCreditsError, NotFoundError, PermissionError, PlatformError, type PlatformErrorCode, type PlatformErrorInit, RateLimitError, ServerError, TimeoutError, ValidationError, } from "./errors.js";
9
9
  export type * from "./gen/index.js";
10
+ export type { Page } from "./paging.js";
10
11
  export type { AgentKnowledge, AgentSettings, Agents, } from "./resources/agents.js";
11
12
  export type { AssetListParams, Assets, AssetUploadFields, FolderListParams, Folders, } from "./resources/assets.js";
12
13
  export type { EndUserCreated, EndUserExport, EndUserListParams, EndUsers, } from "./resources/end-users.js";
package/dist/paging.d.ts CHANGED
@@ -1,34 +1,24 @@
1
1
  /**
2
- * Paging as the API pages: every `list()` is an async iterator over every
3
- * item, page after page; `page()` beside it is one page, as the route
4
- * answers it.
2
+ * Paging as the API pages: every list answers one shape, `{data,
3
+ * next_cursor}`, asked for with `limit` and `cursor`, and `next_cursor` is
4
+ * `null` on the last page (`docs/history/wire.md`). So one iterator serves
5
+ * every list: each resource's `list()` is every item, page after page by
6
+ * cursor, and `page()` beside it is one page, as the route answers it.
5
7
  *
6
- * The API has three styles:
7
- * - **Offset** (end users, ephemeral keys, knowledge, and an end user's
8
- * sessions inside the export): the next page starts where this one
9
- * ended, and a page shorter than the limit, or the `total` reached, is
10
- * the last. Newest first, so a row made while iterating shifts the rest
11
- * by one and one item is seen twice — the cost of offsets, which the
12
- * API chose.
13
- * - **`next_cursor`** (generations, assets): the page says where the next
14
- * starts, and the last has none. Nothing is seen twice.
15
- * - **The backwards `next_before`** of a transcript, which only the
16
- * export reads, by hand (`resources/end-users.ts`).
17
- *
18
- * A route that does not page (voices, agents, templates, a folder's
19
- * level) has no iterator: its `list()` answers what the route does.
8
+ * - **Nothing is seen twice, nothing is counted**: the cursor is the
9
+ * store's own position, opaque, and no list says its total.
10
+ * - **A list bounded by nature** — agents, voices, templates, a folder's
11
+ * level — answers the same page with `next_cursor` always `null`, so its
12
+ * `list()` reads once; the day the route pages, it reads on without a
13
+ * change here.
14
+ * - **A list inside a resource** — a session's messages, which
15
+ * `endUsers.export` reads back to the start — pages by its own field's
16
+ * `next_cursor` (`resources/end-users.ts`).
20
17
  */
21
- /** One page of an offset-paged list: its items, and the total when the route says. */
22
- export interface OffsetPage<T> {
23
- items: T[];
24
- total?: number;
25
- }
26
- /** Every item, `limit` at a time from `offset`, until a short page or the total. */
27
- export declare function byOffset<T>(read: (offset: number, limit: number) => Promise<OffsetPage<T>>, limit: number, offset?: number): AsyncGenerator<T, void, undefined>;
28
- /** One page of a cursor-paged list: its items, and where the next one starts. */
29
- export interface CursorPage<T> {
30
- items: T[];
31
- next?: string | null;
18
+ /** One page of any list: its items, and where the next starts — `null` on the last. */
19
+ export interface Page<T> {
20
+ data: T[];
21
+ next_cursor: string | null;
32
22
  }
33
23
  /** Every item, page after page, until a page names no next one. */
34
- export declare function byCursor<T>(read: (cursor: string | undefined) => Promise<CursorPage<T>>): AsyncGenerator<T, void, undefined>;
24
+ export declare function pages<T>(read: (cursor: string | undefined) => Promise<Page<T>>): AsyncGenerator<T, void, undefined>;
package/dist/paging.js CHANGED
@@ -1,41 +1,26 @@
1
1
  /**
2
- * Paging as the API pages: every `list()` is an async iterator over every
3
- * item, page after page; `page()` beside it is one page, as the route
4
- * answers it.
2
+ * Paging as the API pages: every list answers one shape, `{data,
3
+ * next_cursor}`, asked for with `limit` and `cursor`, and `next_cursor` is
4
+ * `null` on the last page (`docs/history/wire.md`). So one iterator serves
5
+ * every list: each resource's `list()` is every item, page after page by
6
+ * cursor, and `page()` beside it is one page, as the route answers it.
5
7
  *
6
- * The API has three styles:
7
- * - **Offset** (end users, ephemeral keys, knowledge, and an end user's
8
- * sessions inside the export): the next page starts where this one
9
- * ended, and a page shorter than the limit, or the `total` reached, is
10
- * the last. Newest first, so a row made while iterating shifts the rest
11
- * by one and one item is seen twice — the cost of offsets, which the
12
- * API chose.
13
- * - **`next_cursor`** (generations, assets): the page says where the next
14
- * starts, and the last has none. Nothing is seen twice.
15
- * - **The backwards `next_before`** of a transcript, which only the
16
- * export reads, by hand (`resources/end-users.ts`).
17
- *
18
- * A route that does not page (voices, agents, templates, a folder's
19
- * level) has no iterator: its `list()` answers what the route does.
8
+ * - **Nothing is seen twice, nothing is counted**: the cursor is the
9
+ * store's own position, opaque, and no list says its total.
10
+ * - **A list bounded by nature** — agents, voices, templates, a folder's
11
+ * level — answers the same page with `next_cursor` always `null`, so its
12
+ * `list()` reads once; the day the route pages, it reads on without a
13
+ * change here.
14
+ * - **A list inside a resource** — a session's messages, which
15
+ * `endUsers.export` reads back to the start — pages by its own field's
16
+ * `next_cursor` (`resources/end-users.ts`).
20
17
  */
21
- /** Every item, `limit` at a time from `offset`, until a short page or the total. */
22
- export async function* byOffset(read, limit, offset = 0) {
23
- for (;;) {
24
- const page = await read(offset, limit);
25
- yield* page.items;
26
- offset += page.items.length;
27
- const last = page.items.length < limit ||
28
- (page.total !== undefined && offset >= page.total);
29
- if (last)
30
- return;
31
- }
32
- }
33
18
  /** Every item, page after page, until a page names no next one. */
34
- export async function* byCursor(read) {
19
+ export async function* pages(read) {
35
20
  let cursor;
36
21
  do {
37
22
  const page = await read(cursor);
38
- yield* page.items;
39
- cursor = page.next ?? undefined;
23
+ yield* page.data;
24
+ cursor = page.next_cursor ?? undefined;
40
25
  } while (cursor);
41
26
  }
@@ -8,9 +8,11 @@
8
8
  * - **`settings`** are the organisation's, for every agent;
9
9
  * **`knowledge.set`** replaces the entries one agent may look up.
10
10
  * - **`create` is a 409 at the plan's maximum agents**; `delete` answers
11
- * the agent as it was, and `null` for one already gone.
11
+ * `true`, and `false` for one already gone.
12
+ * - **`list()` reads one page**: an organisation's agents are few, and the
13
+ * route answers them all with `next_cursor` `null` (`paging.ts`).
12
14
  */
13
- import type { AgentCreateRequest, AgentInfo, AgentList, AgentSettingsInfo, AgentSettingsUpdate, AgentUpdate, KnowledgeAssigned, KnowledgeAssignment, KnowledgeList } from "../gen/index.js";
15
+ import type { AgentCreateRequest, AgentInfo, AgentKnowledgeUpdate, AgentPage, AgentSettingsInfo, AgentSettingsUpdate, AgentSummary, AgentUpdate, KnowledgeAssigned, KnowledgeInfo } from "../gen/index.js";
14
16
  import type { RequestOptions, Transport } from "../transport.js";
15
17
  import { Editable } from "./resource.js";
16
18
  /** `agents.settings`: the organisation's, for every agent. */
@@ -20,15 +22,18 @@ export interface AgentSettings {
20
22
  }
21
23
  /** `agents.knowledge`: the entries one agent may look up. */
22
24
  export interface AgentKnowledge {
23
- list(agentId: string, options?: RequestOptions): Promise<KnowledgeList>;
25
+ /** Every entry it may look up. */
26
+ list(agentId: string, options?: RequestOptions): AsyncGenerator<KnowledgeInfo, void, undefined>;
24
27
  /** Replaces them all with these. */
25
- set(agentId: string, body: KnowledgeAssignment, options?: RequestOptions): Promise<KnowledgeAssigned>;
28
+ set(agentId: string, body: AgentKnowledgeUpdate, options?: RequestOptions): Promise<KnowledgeAssigned>;
26
29
  }
27
30
  export declare class Agents extends Editable<AgentInfo, AgentUpdate> {
28
31
  readonly settings: AgentSettings;
29
32
  readonly knowledge: AgentKnowledge;
30
33
  constructor(api: Transport, base: string);
34
+ /** The page of agents, as the route answers it. */
35
+ page(options?: RequestOptions): Promise<AgentPage>;
31
36
  /** Every agent. */
32
- list(options?: RequestOptions): Promise<AgentList>;
37
+ list(options?: RequestOptions): AsyncGenerator<AgentSummary, void, undefined>;
33
38
  create(body: AgentCreateRequest, options?: RequestOptions): Promise<AgentInfo>;
34
39
  }
@@ -8,7 +8,9 @@
8
8
  * - **`settings`** are the organisation's, for every agent;
9
9
  * **`knowledge.set`** replaces the entries one agent may look up.
10
10
  * - **`create` is a 409 at the plan's maximum agents**; `delete` answers
11
- * the agent as it was, and `null` for one already gone.
11
+ * `true`, and `false` for one already gone.
12
+ * - **`list()` reads one page**: an organisation's agents are few, and the
13
+ * route answers them all with `next_cursor` `null` (`paging.ts`).
12
14
  */
13
15
  import { Editable } from "./resource.js";
14
16
  export class Agents extends Editable {
@@ -19,13 +21,17 @@ export class Agents extends Editable {
19
21
  update: (body, options) => this.send("PUT", "/settings", options, body),
20
22
  };
21
23
  this.knowledge = {
22
- list: (id, options) => this.send("GET", this.at(id, "/knowledge"), options),
24
+ list: (id, options) => this.each(this.at(id, "/knowledge"), {}, options),
23
25
  set: (id, body, options) => this.send("PUT", this.at(id, "/knowledge"), options, body),
24
26
  };
25
27
  }
28
+ /** The page of agents, as the route answers it. */
29
+ page(options) {
30
+ return this.send("GET", "", options);
31
+ }
26
32
  /** Every agent. */
27
33
  list(options) {
28
- return this.send("GET", "", options);
34
+ return this.each("", {}, options);
29
35
  }
30
36
  create(body, options) {
31
37
  return this.send("POST", "", options, body);
@@ -9,17 +9,18 @@
9
9
  * `failed`. A transcription of one still `pending` is a 409.
10
10
  * - **`upload(data, fields)`** is the ticket, the `PUT` and the confirm
11
11
  * in one (`upload.ts`).
12
- * - **`delete` of one already gone is done**: `null`. `deleteMany` and
13
- * `move` answer how many they touched.
12
+ * - **`delete` answers `true`**, and `false` for one already gone: done.
13
+ * `deleteMany` and `move` answer how many they touched; a folder's
14
+ * `delete` how many it moved up a level, and `null` for one gone.
14
15
  * - **`list()` pages by cursor**, 200 a page; a folder's `list()` is one
15
- * level, as the route answers it.
16
+ * level, which the route answers in one page.
16
17
  */
17
- import type { AssetBulkRequest, AssetDeleted, AssetDetail, AssetMoveRequest, AssetPage, AssetSummary, AssetsAffected, AssetUpdate, FolderCreate, FolderDeleted, FolderDetail, FolderLevel, FolderSummary, FolderUpdate, ListAssetsData, ListFoldersData, UploadConfirm, UploadTicket, UploadTicketRequest } from "../gen/index.js";
18
+ import type { AssetBulkRequest, AssetDetail, AssetMoveRequest, AssetPage, AssetSummary, AssetsAffected, AssetUpdate, FolderCreate, FolderDeleted, FolderDetail, FolderPage, FolderSummary, FolderUpdate, ListAssetFoldersData, ListAssetsData, UploadConfirm, UploadTicket, UploadTicketRequest } from "../gen/index.js";
18
19
  import type { RequestOptions, Transport } from "../transport.js";
19
20
  import { type UploadData, type UploadOptions } from "../upload.js";
20
21
  import { Editable, Waitable } from "./resource.js";
21
22
  export type AssetListParams = NonNullable<ListAssetsData["query"]>;
22
- export type FolderListParams = NonNullable<ListFoldersData["query"]>;
23
+ export type FolderListParams = NonNullable<ListAssetFoldersData["query"]>;
23
24
  /** A file's place in the library: the confirm's fields, and its type. */
24
25
  export type AssetUploadFields = Omit<UploadConfirm, "asset_id" | "content_type"> & {
25
26
  /** A Blob's own type by default. */
@@ -30,16 +31,20 @@ export type AssetUploadFields = Omit<UploadConfirm, "asset_id" | "content_type">
30
31
  * counts and ancestors; `update` renames it or moves it under another;
31
32
  * `delete` moves what it held up a level.
32
33
  */
33
- export declare class Folders extends Editable<FolderDetail, FolderUpdate, FolderSummary, FolderDeleted> {
34
- /** One level: the top, or `parent_id`'s children. */
35
- list(params?: FolderListParams, options?: RequestOptions): Promise<FolderLevel>;
34
+ export declare class Folders extends Editable<FolderDetail, FolderUpdate, FolderSummary, FolderDeleted | null> {
35
+ /** One level — the top, or `parent_id`'s children — as the route answers it. */
36
+ page(params?: FolderListParams, options?: RequestOptions): Promise<FolderPage>;
37
+ /** Every folder of one level: the top, or `parent_id`'s children. */
38
+ list(params?: FolderListParams, options?: RequestOptions): AsyncGenerator<FolderSummary, void, undefined>;
39
+ /** Deletes it, moving what it held up a level: how many moved, or `null` when there is none. */
40
+ delete(id: string, options?: RequestOptions): Promise<FolderDeleted | null>;
36
41
  create(body: FolderCreate, options?: RequestOptions): Promise<FolderSummary>;
37
42
  }
38
43
  /**
39
44
  * `update` renames an asset or files it in a folder; `delete` deletes it
40
45
  * and its files.
41
46
  */
42
- export declare class Assets extends Waitable<AssetDetail, AssetUpdate, AssetDetail, AssetDeleted> {
47
+ export declare class Assets extends Waitable<AssetDetail, AssetUpdate> {
43
48
  readonly folders: Folders;
44
49
  constructor(api: Transport, base: string);
45
50
  /** One page, with the `next_cursor` of the next. */
@@ -9,15 +9,15 @@
9
9
  * `failed`. A transcription of one still `pending` is a 409.
10
10
  * - **`upload(data, fields)`** is the ticket, the `PUT` and the confirm
11
11
  * in one (`upload.ts`).
12
- * - **`delete` of one already gone is done**: `null`. `deleteMany` and
13
- * `move` answer how many they touched.
12
+ * - **`delete` answers `true`**, and `false` for one already gone: done.
13
+ * `deleteMany` and `move` answer how many they touched; a folder's
14
+ * `delete` how many it moved up a level, and `null` for one gone.
14
15
  * - **`list()` pages by cursor**, 200 a page; a folder's `list()` is one
15
- * level, as the route answers it.
16
+ * level, which the route answers in one page.
16
17
  */
17
- import { byCursor } from "../paging.js";
18
18
  import { needType, put, } from "../upload.js";
19
19
  import { Editable, Waitable } from "./resource.js";
20
- /** The route's own page bound. */
20
+ /** The route's largest page. */
21
21
  const PAGE = 200;
22
22
  /**
23
23
  * `assets.folders`: the library's tree. `get` answers a folder with its
@@ -25,10 +25,18 @@ const PAGE = 200;
25
25
  * `delete` moves what it held up a level.
26
26
  */
27
27
  export class Folders extends Editable {
28
- /** One level: the top, or `parent_id`'s children. */
29
- list(params = {}, options) {
28
+ /** One level — the top, or `parent_id`'s children — as the route answers it. */
29
+ page(params = {}, options) {
30
30
  return this.send("GET", "", options, params);
31
31
  }
32
+ /** Every folder of one level: the top, or `parent_id`'s children. */
33
+ list(params = {}, options) {
34
+ return this.each("", params, options);
35
+ }
36
+ /** Deletes it, moving what it held up a level: how many moved, or `null` when there is none. */
37
+ delete(id, options) {
38
+ return this.drop(id, options);
39
+ }
32
40
  create(body, options) {
33
41
  return this.send("POST", "", options, body);
34
42
  }
@@ -48,10 +56,7 @@ export class Assets extends Waitable {
48
56
  }
49
57
  /** Every asset the filter matches, page after page (200 a page by default). */
50
58
  list(params = {}, options) {
51
- return byCursor(async (cursor) => {
52
- const page = await this.page({ limit: PAGE, ...params, cursor }, options);
53
- return { items: page.assets, next: page.next_cursor };
54
- });
59
+ return this.each("", params, options, PAGE);
55
60
  }
56
61
  /** A signed URL to `PUT` a file to, with exactly this type. */
57
62
  uploadUrl(body, options) {
@@ -20,19 +20,21 @@
20
20
  * their own, since their conversations and facts are their end user DO's
21
21
  * and an API key cannot read them (`/v1/agent/*` takes only an ephemeral
22
22
  * key): it mints a five-minute key for them, lists their sessions (200 a
23
- * page), reads each transcript backwards (200 messages a page, four
24
- * sessions at a time), reads their facts, and revokes the key in
25
- * `finally` — after an abort or a failure too. The cost: one mint (and
26
- * `limits()` when cold) + ⌈S/200⌉ lists + Σ⌈M/200⌉ transcript pages + one
27
- * facts read + one revoke. **Its ceilings**: the key is ordinary, so at
28
- * the person's live-key cap or the plan's mint rate the export fails as
29
- * the mint does (422, 429) and can be retried once a key lapses; it lives
30
- * five minutes, so a history that takes longer to read fails with a 401;
31
- * its one micro-credit is set aside like any key's, so an organisation
32
- * with no balance cannot export (402); and the facts route answers the
33
- * first 100 until it pages (left-overs #109).
23
+ * page), reads each transcript back to its start by `messages.next_cursor`
24
+ * (200 messages a page, four sessions at a time), reads their facts, and
25
+ * revokes the key in `finally` — after an abort or a failure too. The
26
+ * cost: one mint (and `limits()` when cold) + ⌈S/200⌉ lists + Σ⌈M/200⌉
27
+ * transcript pages + one facts read + one revoke. **Its ceilings**: the
28
+ * key is ordinary, so at the person's live-key cap or the plan's mint rate
29
+ * the export fails as the mint does (422, 429) and can be retried once a
30
+ * key lapses; it lives five minutes, so a history that takes longer to
31
+ * read fails with a 401; its one micro-credit is set aside like any key's,
32
+ * so an organisation with no balance cannot export (402); and the facts
33
+ * route answers the newest 100 in one page until it pages (left-overs
34
+ * #109) — read here by its cursor, so the day it does, the export has
35
+ * them all.
34
36
  */
35
- import type { EndUserCreateRequest, EndUserDeleted, EndUserInfo, EndUserList, EndUserUpdateRequest, FactInfo, ListEndUsersData, SessionDetail } from "../gen/index.js";
37
+ import type { EndUserCreateRequest, EndUserDeleted, EndUserInfo, EndUserPage, EndUserUpdateRequest, FactInfo, ListEndUsersData, SessionDetail } from "../gen/index.js";
36
38
  import { type RequestOptions, type Transport } from "../transport.js";
37
39
  import type { EphemeralKeys } from "./ephemeral-keys.js";
38
40
  import { Editable } from "./resource.js";
@@ -50,12 +52,15 @@ export type EndUserCreated = EndUserInfo & {
50
52
  export interface EndUserExport {
51
53
  /** Their `eu_…` id. */
52
54
  end_user_id: string;
53
- /** Every conversation, newest first, each with every message, oldest first. */
55
+ /**
56
+ * Every conversation, most recent first, each with every message,
57
+ * oldest first, in its one page of `messages` (`next_cursor` `null`).
58
+ */
54
59
  sessions: SessionDetail[];
55
- /** What is remembered about them, oldest first — the first 100 for now. */
60
+ /** What is remembered about them, oldest first — the newest 100 for now. */
56
61
  facts: FactInfo[];
57
62
  }
58
- export declare class EndUsers extends Editable<EndUserInfo, EndUserUpdateRequest, EndUserInfo, EndUserDeleted> {
63
+ export declare class EndUsers extends Editable<EndUserInfo, EndUserUpdateRequest, EndUserInfo, EndUserDeleted | null> {
59
64
  private readonly keys;
60
65
  constructor(api: Transport, base: string, keys: EphemeralKeys);
61
66
  /**
@@ -64,10 +69,12 @@ export declare class EndUsers extends Editable<EndUserInfo, EndUserUpdateRequest
64
69
  * existing `external_id` is answered as ever).
65
70
  */
66
71
  create(body?: EndUserCreateRequest, options?: RequestOptions): Promise<EndUserCreated>;
67
- /** One page, newest first, with the `total` the filter matches. */
68
- page(params?: EndUserListParams, options?: RequestOptions): Promise<EndUserList>;
72
+ /** One page, newest first, with the `next_cursor` of the next. */
73
+ page(params?: EndUserListParams, options?: RequestOptions): Promise<EndUserPage>;
69
74
  /** Every person, newest first, page after page (`limit` a page, 200 by default). */
70
- list(params?: Omit<EndUserListParams, "offset">, options?: RequestOptions): AsyncGenerator<EndUserInfo, void, undefined>;
75
+ list(params?: Omit<EndUserListParams, "cursor">, options?: RequestOptions): AsyncGenerator<EndUserInfo, void, undefined>;
76
+ /** Deletes them and all they had: the counts, or `null` when there is no such person. */
77
+ delete(id: string, options?: RequestOptions): Promise<EndUserDeleted | null>;
71
78
  /** Everything about one person, read with a key of their own (see the header). */
72
79
  export(id: string, options?: RequestOptions): Promise<EndUserExport>;
73
80
  }
@@ -20,28 +20,37 @@
20
20
  * their own, since their conversations and facts are their end user DO's
21
21
  * and an API key cannot read them (`/v1/agent/*` takes only an ephemeral
22
22
  * key): it mints a five-minute key for them, lists their sessions (200 a
23
- * page), reads each transcript backwards (200 messages a page, four
24
- * sessions at a time), reads their facts, and revokes the key in
25
- * `finally` — after an abort or a failure too. The cost: one mint (and
26
- * `limits()` when cold) + ⌈S/200⌉ lists + Σ⌈M/200⌉ transcript pages + one
27
- * facts read + one revoke. **Its ceilings**: the key is ordinary, so at
28
- * the person's live-key cap or the plan's mint rate the export fails as
29
- * the mint does (422, 429) and can be retried once a key lapses; it lives
30
- * five minutes, so a history that takes longer to read fails with a 401;
31
- * its one micro-credit is set aside like any key's, so an organisation
32
- * with no balance cannot export (402); and the facts route answers the
33
- * first 100 until it pages (left-overs #109).
23
+ * page), reads each transcript back to its start by `messages.next_cursor`
24
+ * (200 messages a page, four sessions at a time), reads their facts, and
25
+ * revokes the key in `finally` — after an abort or a failure too. The
26
+ * cost: one mint (and `limits()` when cold) + ⌈S/200⌉ lists + Σ⌈M/200⌉
27
+ * transcript pages + one facts read + one revoke. **Its ceilings**: the
28
+ * key is ordinary, so at the person's live-key cap or the plan's mint rate
29
+ * the export fails as the mint does (422, 429) and can be retried once a
30
+ * key lapses; it lives five minutes, so a history that takes longer to
31
+ * read fails with a 401; its one micro-credit is set aside like any key's,
32
+ * so an organisation with no balance cannot export (402); and the facts
33
+ * route answers the newest 100 in one page until it pages (left-overs
34
+ * #109) — read here by its cursor, so the day it does, the export has
35
+ * them all.
34
36
  */
35
- import { byOffset } from "../paging.js";
37
+ import { pages } from "../paging.js";
36
38
  import { segment, } from "../transport.js";
37
39
  import { Editable } from "./resource.js";
38
- /** The routes' own page bound, for end users, sessions and transcripts alike. */
40
+ /** The routes' largest page, for end users, sessions and transcripts alike. */
39
41
  const PAGE = 200;
40
42
  /** The export key's lifetime and limit: the shortest useful, the least set aside. */
41
43
  const EXPORT_TTL_S = 300;
42
- const EXPORT_CREDIT = "0.000001";
44
+ const EXPORT_CREDIT_MICROS = 1;
43
45
  /** Transcripts read at once. */
44
46
  const EXPORT_PARALLEL = 4;
47
+ /** Every item of `items`, in an array. */
48
+ async function all(items) {
49
+ const out = [];
50
+ for await (const item of items)
51
+ out.push(item);
52
+ return out;
53
+ }
45
54
  /** `work` over every item, `width` at a time, answers in the items' order. */
46
55
  async function inParallel(items, width, work) {
47
56
  const out = new Array(items.length);
@@ -75,22 +84,23 @@ export class EndUsers extends Editable {
75
84
  }, options);
76
85
  return { ...answer.data, created: answer.status === 201 };
77
86
  }
78
- /** One page, newest first, with the `total` the filter matches. */
87
+ /** One page, newest first, with the `next_cursor` of the next. */
79
88
  page(params = {}, options) {
80
89
  return this.send("GET", "", options, params);
81
90
  }
82
91
  /** Every person, newest first, page after page (`limit` a page, 200 by default). */
83
92
  list(params = {}, options) {
84
- return byOffset(async (offset, limit) => {
85
- const page = await this.page({ ...params, offset, limit }, options);
86
- return { items: page.end_users, total: page.total };
87
- }, params.limit ?? PAGE);
93
+ return this.each("", params, options, PAGE);
94
+ }
95
+ /** Deletes them and all they had: the counts, or `null` when there is no such person. */
96
+ delete(id, options) {
97
+ return this.drop(id, options);
88
98
  }
89
99
  /** Everything about one person, read with a key of their own (see the header). */
90
100
  async export(id, options = {}) {
91
101
  const key = await this.keys.create({
92
102
  end_user_id: id,
93
- credit_limit: EXPORT_CREDIT,
103
+ credit_limit_micros: EXPORT_CREDIT_MICROS,
94
104
  expires_in_seconds: EXPORT_TTL_S,
95
105
  label: "export",
96
106
  }, options);
@@ -99,33 +109,27 @@ export class EndUsers extends Editable {
99
109
  const read = (path, query) => this.api
100
110
  .request({ method: "GET", path, query, token: key.key }, options)
101
111
  .then((answer) => answer.data);
102
- const listed = [];
103
- for await (const session of byOffset(async (offset, limit) => ({
104
- items: await read("/v1/agent/sessions", {
105
- offset,
106
- limit,
107
- }),
108
- }), PAGE)) {
109
- listed.push(session);
110
- }
112
+ const listed = await all(pages((cursor) => read("/v1/agent/sessions", { limit: PAGE, cursor })));
111
113
  const sessions = await inParallel(listed, EXPORT_PARALLEL, async (s) => {
112
- const path = `/v1/agent/sessions/${segment(s.id)}`;
113
- const page = (before) => read(path, { limit: PAGE, before });
114
+ const page = (cursor) => read(`/v1/agent/sessions/${segment(s.id)}`, {
115
+ limit: PAGE,
116
+ cursor,
117
+ });
118
+ // The newest messages first; each older page goes before them.
114
119
  const newest = await page();
115
- const pages = [newest.messages];
116
- for (let before = newest.next_before; typeof before === "number";) {
117
- const older = await page(before);
118
- pages.unshift(older.messages);
119
- before = older.next_before;
120
+ const held = [newest.messages.data];
121
+ for (let cursor = newest.messages.next_cursor; cursor;) {
122
+ const older = (await page(cursor)).messages;
123
+ held.unshift(older.data);
124
+ cursor = older.next_cursor;
120
125
  }
121
- return { ...newest, messages: pages.flat(), next_before: null };
126
+ return {
127
+ ...newest,
128
+ messages: { data: held.flat(), next_cursor: null },
129
+ };
122
130
  });
123
- const facts = await read("/v1/agent/facts");
124
- return {
125
- end_user_id: key.end_user_id ?? facts.end_user_id,
126
- sessions,
127
- facts: facts.facts,
128
- };
131
+ const facts = await all(pages((cursor) => read("/v1/agent/facts", { cursor })));
132
+ return { end_user_id: key.end_user_id ?? id, sessions, facts };
129
133
  }
130
134
  finally {
131
135
  // Without the caller's signal: an aborted export still revokes.
@@ -2,8 +2,8 @@
2
2
  * `client.ephemeralKeys`: the keys an end user's device talks to agents
3
3
  * with — `/v1/ephemeral-keys*` and `/v1/token`.
4
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
5
+ * - **A key is prepaid**: its `credit_limit_micros` is set aside from the
6
+ * balance when it is minted (402 when it cannot be), and what it does not spend
7
7
  * comes back when it settles.
8
8
  * - **`create` clamps the lifetime by default** to the platform's maximum,
9
9
  * read from `limits()` — which the API never does: past a cap is a 422
@@ -17,10 +17,10 @@
17
17
  * (`apps/api/src/org/areas/keys.ts` `revokeEphemeral`). Waiting for the
18
18
  * final figures needs the key's read route (left-overs #106).
19
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
20
+ * Its `credits_spent_micros` moves only when the key settles — at the end, or
21
21
  * daily — so a live key reads as unspent (left-overs #105).
22
22
  */
23
- import type { EphemeralKeyCreateRequest, EphemeralKeyCreateResponse, EphemeralKeyInfo, EphemeralKeyLimits, EphemeralTokenSelf, ListEphemeralKeysData } from "../gen/index.js";
23
+ import type { EphemeralKeyCreateRequest, EphemeralKeyCreateResponse, EphemeralKeyInfo, EphemeralKeyLimits, EphemeralKeyPage, EphemeralTokenSelf, ListEphemeralKeysData } from "../gen/index.js";
24
24
  import { type RequestOptions } from "../transport.js";
25
25
  import { Resource } from "./resource.js";
26
26
  export type EphemeralKeyListParams = NonNullable<ListEphemeralKeysData["query"]>;
@@ -43,7 +43,7 @@ export declare class EphemeralKeys extends Resource {
43
43
  /**
44
44
  * Mints a key for one end user — returned **once**, as `key`. Scopes
45
45
  * are exactly `["agent"]` (the default). 402 when the balance cannot set
46
- * `credit_limit` aside, 422 past a cap, 429 past the plan's mints a
46
+ * `credit_limit_micros` aside, 422 past a cap, 429 past the plan's mints a
47
47
  * minute (retried after its `Retry-After`).
48
48
  */
49
49
  create(body: EphemeralKeyCreateRequest, options?: EphemeralKeyCreateOptions): Promise<EphemeralKeyCreateResponse>;
@@ -58,8 +58,8 @@ export declare class EphemeralKeys extends Resource {
58
58
  * when the key is refused — expired, revoked, spent or not a key.
59
59
  */
60
60
  inspect(key: string, options?: RequestOptions): Promise<EphemeralTokenSelf | null>;
61
- /** One page of this organisation's keys: live ones unless `live_only` is `false`. */
62
- page(params?: EphemeralKeyListParams, options?: RequestOptions): Promise<EphemeralKeyInfo[]>;
63
- /** Every key, page after page (`limit` a page, 500 by default). */
64
- list(params?: Omit<EphemeralKeyListParams, "offset">, options?: RequestOptions): AsyncGenerator<EphemeralKeyInfo, void, undefined>;
61
+ /** One page of this organisation's keys, newest first: live ones unless `live_only` is `false`. */
62
+ page(params?: EphemeralKeyListParams, options?: RequestOptions): Promise<EphemeralKeyPage>;
63
+ /** Every key, page after page (`limit` a page, 200 by default). */
64
+ list(params?: Omit<EphemeralKeyListParams, "cursor">, options?: RequestOptions): AsyncGenerator<EphemeralKeyInfo, void, undefined>;
65
65
  }
@@ -2,8 +2,8 @@
2
2
  * `client.ephemeralKeys`: the keys an end user's device talks to agents
3
3
  * with — `/v1/ephemeral-keys*` and `/v1/token`.
4
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
5
+ * - **A key is prepaid**: its `credit_limit_micros` is set aside from the
6
+ * balance when it is minted (402 when it cannot be), and what it does not spend
7
7
  * comes back when it settles.
8
8
  * - **`create` clamps the lifetime by default** to the platform's maximum,
9
9
  * read from `limits()` — which the API never does: past a cap is a 422
@@ -17,16 +17,15 @@
17
17
  * (`apps/api/src/org/areas/keys.ts` `revokeEphemeral`). Waiting for the
18
18
  * final figures needs the key's read route (left-overs #106).
19
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
20
+ * Its `credits_spent_micros` moves only when the key settles — at the end, or
21
21
  * daily — so a live key reads as unspent (left-overs #105).
22
22
  */
23
23
  import { AuthenticationError } from "../errors.js";
24
- import { byOffset } from "../paging.js";
25
24
  import { orNull } from "../transport.js";
26
25
  import { Resource } from "./resource.js";
27
26
  const LIMITS_TTL_MS = 5 * 60_000;
28
- /** The route's own page bound. */
29
- const PAGE = 500;
27
+ /** The route's largest page. */
28
+ const PAGE = 200;
30
29
  /** `promise`, or the signal's reason when it aborts first. */
31
30
  function until(promise, signal) {
32
31
  if (!signal)
@@ -72,7 +71,7 @@ export class EphemeralKeys extends Resource {
72
71
  /**
73
72
  * Mints a key for one end user — returned **once**, as `key`. Scopes
74
73
  * 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
74
+ * `credit_limit_micros` aside, 422 past a cap, 429 past the plan's mints a
76
75
  * minute (retried after its `Retry-After`).
77
76
  */
78
77
  async create(body, options = {}) {
@@ -116,14 +115,12 @@ export class EphemeralKeys extends Resource {
116
115
  }
117
116
  return self;
118
117
  }
119
- /** One page of this organisation's keys: live ones unless `live_only` is `false`. */
118
+ /** One page of this organisation's keys, newest first: live ones unless `live_only` is `false`. */
120
119
  page(params = {}, options) {
121
120
  return this.send("GET", "", options, params);
122
121
  }
123
- /** Every key, page after page (`limit` a page, 500 by default). */
122
+ /** Every key, page after page (`limit` a page, 200 by default). */
124
123
  list(params = {}, options) {
125
- return byOffset(async (offset, limit) => ({
126
- items: await this.page({ ...params, offset, limit }, options),
127
- }), params.limit ?? PAGE);
124
+ return this.each("", params, options, PAGE);
128
125
  }
129
126
  }