@lumavox/client 0.0.0-stage → 0.1.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.
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Every failure the SDK throws: a `PlatformError`, so one `catch` with
3
+ * `instanceof PlatformError` sees them all, and a subclass per status a
4
+ * caller acts on differently.
5
+ *
6
+ * The API answers its errors in one envelope —
7
+ * `{"error":{"type","message","request_id","details"?}}`
8
+ * (`apps/api/src/http/errors.ts`) — and `type` is the thing to switch on.
9
+ * Two bodies are not that envelope, and are read here too:
10
+ * - **the 400 the document declares** on every route with a body or a path
11
+ * parameter, `{status_code, detail, extra}`;
12
+ * - **Cloudflare's own refusal**, `error code: 1010` as plain text with no
13
+ * `X-Request-Id`: the request never reached the API, so there is no id
14
+ * to report.
15
+ */
16
+ /** What one error says, as the SDK read it. */
17
+ export interface PlatformErrorInit {
18
+ status: number;
19
+ type: string;
20
+ message: string;
21
+ requestId: string | null;
22
+ details?: unknown;
23
+ retryAfter?: number | null;
24
+ cause?: unknown;
25
+ }
26
+ export declare class PlatformError extends Error {
27
+ name: string;
28
+ /** The HTTP status; `0` when no response came back. */
29
+ readonly status: number;
30
+ /** The API's machine-readable category: `validation_error`, `rate_limit_error`, … */
31
+ readonly type: string;
32
+ /**
33
+ * The request's `X-Request-Id` — the one the SDK sent, which the API
34
+ * keeps. `null` only when Cloudflare refused it before the API saw it.
35
+ */
36
+ readonly requestId: string | null;
37
+ /** Field-level problems on a 422; the 400's `extra`; otherwise `undefined`. */
38
+ readonly details: unknown;
39
+ /** Seconds to wait before trying again, from `Retry-After`; `null` without one. */
40
+ readonly retryAfter: number | null;
41
+ constructor(init: PlatformErrorInit);
42
+ }
43
+ /** 400 or 422: the request is malformed or failed validation; `details` says where. */
44
+ export declare class ValidationError extends PlatformError {
45
+ name: string;
46
+ }
47
+ /** 401: no credential, a wrong one, or one that expired or was revoked. */
48
+ export declare class AuthenticationError extends PlatformError {
49
+ name: string;
50
+ }
51
+ /** 402: the balance cannot cover it, or the organisation is suspended or on no plan. */
52
+ export declare class InsufficientCreditsError extends PlatformError {
53
+ name: string;
54
+ }
55
+ /** 403: the key's scopes do not cover this, or the waitlist holds the organisation. */
56
+ export declare class PermissionError extends PlatformError {
57
+ name: string;
58
+ }
59
+ /** 404: nothing by that id in this organisation. */
60
+ export declare class NotFoundError extends PlatformError {
61
+ name: string;
62
+ }
63
+ /** 409: a plan's maximum, or an id already taken. */
64
+ export declare class ConflictError extends PlatformError {
65
+ name: string;
66
+ }
67
+ /** 429: a request window or the plan's mint rate; `retryAfter` says when. */
68
+ export declare class RateLimitError extends PlatformError {
69
+ name: string;
70
+ }
71
+ /** 5xx: the API or something behind it failed. */
72
+ export declare class ServerError extends PlatformError {
73
+ name: string;
74
+ }
75
+ /** No response: DNS, TLS, a reset connection. `cause` is what `fetch` threw. */
76
+ export declare class ConnectionError extends PlatformError {
77
+ name: string;
78
+ }
79
+ /** No response within `timeoutMs`, on the last attempt. */
80
+ export declare class TimeoutError extends PlatformError {
81
+ name: string;
82
+ }
83
+ /** `Retry-After` as seconds: a number of them, or an HTTP date. */
84
+ export declare function retryAfterOf(headers: Headers): number | null;
85
+ /** The error a non-2xx response stands for, from its status, headers and body text. */
86
+ export declare function errorFromResponse(status: number, headers: Headers, text: string, sentRequestId: string): PlatformError;
package/dist/errors.js ADDED
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Every failure the SDK throws: a `PlatformError`, so one `catch` with
3
+ * `instanceof PlatformError` sees them all, and a subclass per status a
4
+ * caller acts on differently.
5
+ *
6
+ * The API answers its errors in one envelope —
7
+ * `{"error":{"type","message","request_id","details"?}}`
8
+ * (`apps/api/src/http/errors.ts`) — and `type` is the thing to switch on.
9
+ * Two bodies are not that envelope, and are read here too:
10
+ * - **the 400 the document declares** on every route with a body or a path
11
+ * parameter, `{status_code, detail, extra}`;
12
+ * - **Cloudflare's own refusal**, `error code: 1010` as plain text with no
13
+ * `X-Request-Id`: the request never reached the API, so there is no id
14
+ * to report.
15
+ */
16
+ export class PlatformError extends Error {
17
+ constructor(init) {
18
+ // `init` is the options too: `Error` reads only its `cause`.
19
+ super(init.message, init);
20
+ // Written out, not `new.target.name`: a customer's minifier renames classes.
21
+ this.name = "PlatformError";
22
+ this.status = init.status;
23
+ this.type = init.type;
24
+ this.requestId = init.requestId;
25
+ this.details = init.details;
26
+ this.retryAfter = init.retryAfter ?? null;
27
+ }
28
+ }
29
+ /** 400 or 422: the request is malformed or failed validation; `details` says where. */
30
+ export class ValidationError extends PlatformError {
31
+ constructor() {
32
+ super(...arguments);
33
+ this.name = "ValidationError";
34
+ }
35
+ }
36
+ /** 401: no credential, a wrong one, or one that expired or was revoked. */
37
+ export class AuthenticationError extends PlatformError {
38
+ constructor() {
39
+ super(...arguments);
40
+ this.name = "AuthenticationError";
41
+ }
42
+ }
43
+ /** 402: the balance cannot cover it, or the organisation is suspended or on no plan. */
44
+ export class InsufficientCreditsError extends PlatformError {
45
+ constructor() {
46
+ super(...arguments);
47
+ this.name = "InsufficientCreditsError";
48
+ }
49
+ }
50
+ /** 403: the key's scopes do not cover this, or the waitlist holds the organisation. */
51
+ export class PermissionError extends PlatformError {
52
+ constructor() {
53
+ super(...arguments);
54
+ this.name = "PermissionError";
55
+ }
56
+ }
57
+ /** 404: nothing by that id in this organisation. */
58
+ export class NotFoundError extends PlatformError {
59
+ constructor() {
60
+ super(...arguments);
61
+ this.name = "NotFoundError";
62
+ }
63
+ }
64
+ /** 409: a plan's maximum, or an id already taken. */
65
+ export class ConflictError extends PlatformError {
66
+ constructor() {
67
+ super(...arguments);
68
+ this.name = "ConflictError";
69
+ }
70
+ }
71
+ /** 429: a request window or the plan's mint rate; `retryAfter` says when. */
72
+ export class RateLimitError extends PlatformError {
73
+ constructor() {
74
+ super(...arguments);
75
+ this.name = "RateLimitError";
76
+ }
77
+ }
78
+ /** 5xx: the API or something behind it failed. */
79
+ export class ServerError extends PlatformError {
80
+ constructor() {
81
+ super(...arguments);
82
+ this.name = "ServerError";
83
+ }
84
+ }
85
+ /** No response: DNS, TLS, a reset connection. `cause` is what `fetch` threw. */
86
+ export class ConnectionError extends PlatformError {
87
+ constructor() {
88
+ super(...arguments);
89
+ this.name = "ConnectionError";
90
+ }
91
+ }
92
+ /** No response within `timeoutMs`, on the last attempt. */
93
+ export class TimeoutError extends PlatformError {
94
+ constructor() {
95
+ super(...arguments);
96
+ this.name = "TimeoutError";
97
+ }
98
+ }
99
+ const BY_STATUS = {
100
+ 400: ValidationError,
101
+ 401: AuthenticationError,
102
+ 402: InsufficientCreditsError,
103
+ 403: PermissionError,
104
+ 404: NotFoundError,
105
+ 409: ConflictError,
106
+ 422: ValidationError,
107
+ 429: RateLimitError,
108
+ };
109
+ const classOf = (status) => BY_STATUS[status] ?? (status >= 500 ? ServerError : PlatformError);
110
+ /** `Retry-After` as seconds: a number of them, or an HTTP date. */
111
+ export function retryAfterOf(headers) {
112
+ const value = headers.get("retry-after")?.trim();
113
+ if (!value)
114
+ return null;
115
+ if (/^\d+(\.\d+)?$/.test(value))
116
+ return Number(value);
117
+ const at = Date.parse(value);
118
+ return Number.isNaN(at) ? null : Math.max(0, (at - Date.now()) / 1000);
119
+ }
120
+ const isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
121
+ /** The error a non-2xx response stands for, from its status, headers and body text. */
122
+ export function errorFromResponse(status, headers, text, sentRequestId) {
123
+ const answered = headers.get("x-request-id");
124
+ const requestId = answered ?? sentRequestId;
125
+ const http = `HTTP ${status}`;
126
+ let body;
127
+ try {
128
+ body = JSON.parse(text);
129
+ }
130
+ catch {
131
+ // swallowed: not JSON — Cloudflare's text, or a proxy's page; read below.
132
+ body = undefined;
133
+ }
134
+ const e = isObject(body) && isObject(body.error) ? body.error : null;
135
+ const code = /^error code: (\d+)/.exec(text.trim())?.[1];
136
+ const snippet = text.trim().slice(0, 200);
137
+ const said = e
138
+ ? {
139
+ type: typeof e.type === "string" ? e.type : "unknown_error",
140
+ message: typeof e.message === "string" ? e.message : http,
141
+ requestId: typeof e.request_id === "string" ? e.request_id : requestId,
142
+ details: e.details ?? undefined,
143
+ }
144
+ : isObject(body) && typeof body.detail === "string"
145
+ ? {
146
+ type: "invalid_request",
147
+ message: body.detail,
148
+ requestId,
149
+ details: body.extra ?? undefined,
150
+ }
151
+ : code && answered === null
152
+ ? {
153
+ type: "cloudflare_error",
154
+ message: `Cloudflare refused the request with error code ${code} ` +
155
+ "before it reached the API.",
156
+ requestId: null,
157
+ }
158
+ : {
159
+ type: "http_error",
160
+ message: snippet ? `${http}: ${snippet}` : http,
161
+ requestId,
162
+ };
163
+ return new (classOf(status))({
164
+ ...said,
165
+ status,
166
+ retryAfter: retryAfterOf(headers),
167
+ });
168
+ }
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Every frame the edge sends, as it sends it: the wire's own field names,
3
+ * snake_case included, so the docs' WebSocket page and these types read
4
+ * the same.
5
+ *
6
+ * **Hand-written, and held twice.** `ServerFrames` is keyed by the event
7
+ * names `protocol.gen.ts` takes from the edge's vocabulary, so an event the
8
+ * edge gains fails `tsc` here until it has a type; and the edge's contract
9
+ * test (`apps/edge/test/sdk.test.ts`) holds every field of every frame the
10
+ * real conversation object sends to these, both ways.
11
+ */
12
+ import type { RelationInfo } from "./gen/index.js";
13
+ import type { ServerEventName } from "./protocol.gen.js";
14
+ interface Frame<T extends ServerEventName> {
15
+ type: T;
16
+ /** Rises by one with every frame on the connection. */
17
+ seq: number;
18
+ }
19
+ /** Inside a turn: the id from its `turn.start`. */
20
+ interface InTurn {
21
+ turn: string;
22
+ }
23
+ /** How audio is encoded: `pcm_s16le`, its rate, and one channel. */
24
+ export interface AudioFormat {
25
+ codec: string;
26
+ sample_rate: number;
27
+ channels: number;
28
+ }
29
+ export interface ReadyFrame extends Frame<"ready"> {
30
+ protocol: string;
31
+ session_id: string;
32
+ agent_id: string;
33
+ end_user_id: string;
34
+ /** Whether the agent replies with audio. */
35
+ speaks: boolean;
36
+ /** The format replies come back in. */
37
+ audio: AudioFormat;
38
+ /** What a recording is assumed to be when `startAudio` names no rate. */
39
+ hearing: AudioFormat;
40
+ emotion: boolean;
41
+ /** The standing relationship; `null` when the agent keeps none. */
42
+ relation: RelationInfo | null;
43
+ /** The agent speaks first: a turn is already running. */
44
+ greeting: boolean;
45
+ idle_seconds: number;
46
+ edge_version: string;
47
+ }
48
+ export interface TurnStartFrame extends Frame<"turn.start">, InTurn {
49
+ session_id: string;
50
+ }
51
+ /** A spoken turn's transcript, before the reply. */
52
+ export interface HeardFrame extends Frame<"heard">, InTurn {
53
+ text: string;
54
+ seconds: number;
55
+ }
56
+ /** The open recording as heard so far: a caption, replaced by the next. */
57
+ export interface HeardPartialFrame extends Frame<"heard.partial"> {
58
+ text: string;
59
+ }
60
+ /** A `transcribeOnly` recording's words: part of no turn. */
61
+ export interface TranscriptFrame extends Frame<"transcript"> {
62
+ text: string;
63
+ seconds: number;
64
+ request_id: string;
65
+ /** The recording's id, which the SDK names every recording with. */
66
+ id?: string;
67
+ }
68
+ export interface MoodFrame extends Frame<"mood">, InTurn {
69
+ mood: string;
70
+ /** 0 to 1, or `null` when the model gave none. */
71
+ intensity: number | null;
72
+ }
73
+ /** A piece of the reply as the model writes it. */
74
+ export interface TextDeltaFrame extends Frame<"text.delta">, InTurn {
75
+ text: string;
76
+ }
77
+ /** A piece of the reply cut for speech; its audio has the same `chunk`. */
78
+ export interface TextChunkFrame extends Frame<"text.chunk">, InTurn {
79
+ chunk: number;
80
+ text: string;
81
+ }
82
+ /** A piece of a thinking summary, when the turn asked for `thoughts`. */
83
+ export interface ThoughtDeltaFrame extends Frame<"thought.delta">, InTurn {
84
+ text: string;
85
+ }
86
+ export interface ToolStartFrame extends Frame<"tool.start">, InTurn {
87
+ name: string;
88
+ }
89
+ export interface ToolEndFrame extends Frame<"tool.end">, InTurn {
90
+ name: string;
91
+ }
92
+ export interface KnowledgeEntry {
93
+ entry_id: string;
94
+ title: string;
95
+ score: number;
96
+ }
97
+ /** The agent looked something up in its knowledge. */
98
+ export interface KnowledgeFrame extends Frame<"knowledge">, InTurn {
99
+ /** `first`, before the reply, or `tool`, when the model asked. */
100
+ via: string;
101
+ /** `found`, `none` or `failed`. */
102
+ outcome: string;
103
+ entries: KnowledgeEntry[];
104
+ }
105
+ /** An audio window opens: the binary frames until `audio.end` are its PCM. */
106
+ export interface AudioStartFrame extends Frame<"audio.start">, InTurn, AudioFormat {
107
+ chunk: number;
108
+ }
109
+ export interface AudioEndFrame extends Frame<"audio.end">, InTurn {
110
+ chunk: number;
111
+ bytes: number;
112
+ audio_s: number;
113
+ }
114
+ /** After `mute`, once the last audio window has closed. */
115
+ export interface MutedFrame extends Frame<"muted">, InTurn {
116
+ }
117
+ /** The relationship moved on this turn: its new fields. */
118
+ export interface RelationFrame extends Frame<"relation">, InTurn, RelationInfo {
119
+ }
120
+ export interface UsageFrame extends Frame<"usage">, InTurn {
121
+ model: string;
122
+ input_tokens: number;
123
+ output_tokens: number;
124
+ thought_tokens: number | null;
125
+ cached_tokens: number | null;
126
+ generation_seconds: number;
127
+ }
128
+ /** Where a turn's time went, in milliseconds from the message; `null` for a step it did not take. */
129
+ export interface TurnTimings {
130
+ heard_ms: number | null;
131
+ knowledge_ms: number | null;
132
+ model_request_ms: number | null;
133
+ model_head_ms: number | null;
134
+ model_first_thought_ms: number | null;
135
+ model_first_text_ms: number | null;
136
+ first_text_sent_ms: number | null;
137
+ first_audio_sent_ms: number | null;
138
+ model_done_ms: number | null;
139
+ total_ms: number;
140
+ model_attempts: number | null;
141
+ /** The Cloudflare location the conversation runs in. */
142
+ colo: string | null;
143
+ }
144
+ /** The turn finished: the whole reply, so a client that ignored every delta is still right. */
145
+ export interface TurnDoneFrame extends Frame<"turn.done">, InTurn {
146
+ session_id: string;
147
+ message: string;
148
+ model: string | null;
149
+ input_tokens: number | null;
150
+ output_tokens: number | null;
151
+ mood: string | null;
152
+ mood_intensity: number | null;
153
+ /** Always `null`: a relationship that moved arrives as its own frame. */
154
+ relation: null;
155
+ /** `true` when the reply was cut short. */
156
+ truncated: true | null;
157
+ chunks_spoken: number;
158
+ heard: string | null;
159
+ heard_seconds: number | null;
160
+ timings: TurnTimings;
161
+ }
162
+ /** The turn ended early; what was written before it stopped still stands. */
163
+ export interface InterruptedFrame extends Frame<"interrupted">, InTurn {
164
+ message: string;
165
+ mood: string | null;
166
+ mood_intensity: number | null;
167
+ detail: string | null;
168
+ }
169
+ /**
170
+ * Something went wrong. It does not end the connection, and inside a turn
171
+ * it does not end the turn. `detail` alone: a frame refused; `stage:
172
+ * "speech"`: the reply could not be spoken; `status`: the turn or a
173
+ * `transcribeOnly` recording failed.
174
+ */
175
+ export interface ErrorFrame extends Frame<"error"> {
176
+ detail: string;
177
+ turn?: string;
178
+ status?: number;
179
+ stage?: string;
180
+ request_id?: string;
181
+ /** The recording it refused or failed. */
182
+ id?: string;
183
+ }
184
+ /** The answer to `ping`, and sent unprompted after 30 s of client silence. */
185
+ export interface PongFrame extends Frame<"pong"> {
186
+ }
187
+ /**
188
+ * Every event, by name. **Exhaustive**: a name in the vocabulary with no
189
+ * entry, an entry the vocabulary does not name, or an entry whose `type`
190
+ * is another's, fails to type-check.
191
+ */
192
+ export type ServerFrames = Exhaustive<{
193
+ ready: ReadyFrame;
194
+ "turn.start": TurnStartFrame;
195
+ heard: HeardFrame;
196
+ "heard.partial": HeardPartialFrame;
197
+ transcript: TranscriptFrame;
198
+ mood: MoodFrame;
199
+ "text.delta": TextDeltaFrame;
200
+ "text.chunk": TextChunkFrame;
201
+ "thought.delta": ThoughtDeltaFrame;
202
+ "tool.start": ToolStartFrame;
203
+ "tool.end": ToolEndFrame;
204
+ knowledge: KnowledgeFrame;
205
+ "audio.start": AudioStartFrame;
206
+ "audio.end": AudioEndFrame;
207
+ muted: MutedFrame;
208
+ relation: RelationFrame;
209
+ usage: UsageFrame;
210
+ "turn.done": TurnDoneFrame;
211
+ interrupted: InterruptedFrame;
212
+ error: ErrorFrame;
213
+ pong: PongFrame;
214
+ }>;
215
+ type Exhaustive<T extends {
216
+ [K in ServerEventName]: {
217
+ type: K;
218
+ };
219
+ } & {
220
+ [K in Exclude<keyof T, ServerEventName>]: never;
221
+ }> = T;
222
+ /** Any frame the edge sends. */
223
+ export type ServerFrame = ServerFrames[ServerEventName];
224
+ export {};
package/dist/events.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export type { ClientOptions, CompactData, CompactError, CompactErrors, CompactionInfo, CompactRequest, CompactResponse, CompactResponses, CreateSessionData, CreateSessionError, CreateSessionErrors, CreateSessionResponse, CreateSessionResponses, DeleteSessionData, DeleteSessionError, DeleteSessionErrors, DeleteSessionResponse, DeleteSessionResponses, ErrorInfo, ErrorResponse, ExtractData, ExtractError, ExtractErrors, ExtractResponse, ExtractResponses, FactInfo, FactList, ForgetFactData, ForgetFactError, ForgetFactErrors, ForgetFactResponse, ForgetFactResponses, ForgetUserFactsData, ForgetUserFactsError, ForgetUserFactsErrors, ForgetUserFactsResponse, ForgetUserFactsResponses, ForgottenFacts, GetSessionDetailData, GetSessionDetailError, GetSessionDetailErrors, GetSessionDetailResponse, GetSessionDetailResponses, KnowledgeCitation, KnowledgeLookupInfo, ListFactsData, ListFactsError, ListFactsErrors, ListFactsResponse, ListFactsResponses, ListRelationsData, ListRelationsError, ListRelationsErrors, ListRelationsResponse, ListRelationsResponses, ListSessionsData, ListSessionsError, ListSessionsErrors, ListSessionsResponse, ListSessionsResponses, MessageInfo, MessageKnowledge, RelationInfo, RelationList, SessionCreateRequest, SessionDetail, SessionInfo } from './types.gen.js';
@@ -0,0 +1,2 @@
1
+ // This file is auto-generated by @hey-api/openapi-ts
2
+ export {};