palbase 0.6.2 → 0.8.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.
Files changed (81) hide show
  1. package/dist/ai/index.js +2 -14
  2. package/dist/backend/engine.js +2 -2
  3. package/dist/backend/env.js +1 -1
  4. package/dist/backend/index.js +9 -9
  5. package/dist/backend/stack.js +1 -1
  6. package/dist/backend/test.js +2 -2
  7. package/dist/chunks/5VTGMNPL.js +20 -0
  8. package/dist/chunks/{SXH5AIQX.js → BO5KR2EQ.js} +2 -2
  9. package/dist/chunks/{AYY4J3B2.js → BRBCWPR3.js} +1 -1
  10. package/dist/chunks/DB3SIBKB.js +2 -0
  11. package/dist/chunks/EGNEXFTZ.js +2 -0
  12. package/dist/chunks/{VP7V7YRH.js → FZ3MQOVA.js} +2 -2
  13. package/dist/chunks/{A3CDCPXX.js → JSJVV43D.js} +1 -1
  14. package/dist/chunks/K6RRNI23.js +4 -0
  15. package/dist/chunks/{SUX2BJG4.js → KDWDPSL2.js} +1 -1
  16. package/dist/chunks/P4Z5E6ZI.js +2 -0
  17. package/dist/chunks/PDOHT46N.js +2 -0
  18. package/dist/chunks/PGPQGKH2.js +18 -0
  19. package/dist/chunks/{DRTZ33XF.js → RBA6V3UO.js} +2 -2
  20. package/dist/chunks/{KLANM6M6.js → RO5Y5YU4.js} +1 -1
  21. package/dist/chunks/{53FS342F.js → TUGRIOFX.js} +2 -2
  22. package/dist/chunks/WEI32SAJ.js +13 -0
  23. package/dist/chunks/{GVFZM4BI.js → WINBISC2.js} +3 -3
  24. package/dist/chunks/Z5JVQNAZ.js +2 -0
  25. package/dist/cli.js +140 -139
  26. package/dist/refuse/ai.js +1 -1
  27. package/dist/refuse/backend.js +1 -1
  28. package/dist/refuse/server.js +1 -1
  29. package/dist/server/index.js +2 -2
  30. package/dist/server/testing.js +2 -2
  31. package/dist/web/index.js +2 -2
  32. package/dist/web/palbe.js +2 -2
  33. package/dist/web/paywall.js +2 -2
  34. package/dist/web/ssr.js +2 -2
  35. package/dist/web/vite.js +2 -2
  36. package/package.json +2 -2
  37. package/types/ai/src/ai.d.ts +46 -19
  38. package/types/ai/src/chats.d.ts +478 -18
  39. package/types/ai/src/index.d.ts +4 -4
  40. package/types/ai/src/tools.d.ts +161 -29
  41. package/types/backend/src/bootstrap.d.ts +8 -0
  42. package/types/backend/src/container.d.ts +11 -2
  43. package/types/backend/src/database.d.ts +17 -2
  44. package/types/backend/src/engine.d.ts +3 -4
  45. package/types/backend/src/gateways.d.ts +269 -0
  46. package/types/backend/src/index.d.ts +3 -2
  47. package/types/backend/src/openapi.d.ts +3 -6
  48. package/types/backend/src/runtime.d.ts +51 -2
  49. package/types/backend/src/testing/unit.d.ts +3 -1
  50. package/types/internal/src/chat.d.ts +168 -35
  51. package/types/internal/src/modules/ai.d.ts +1826 -545
  52. package/types/internal/src/modules/auth.d.ts +14 -2
  53. package/types/internal/src/modules/events.d.ts +2 -2
  54. package/types/internal/src/modules/machines.d.ts +57 -1
  55. package/types/internal/src/modules/notify.d.ts +6 -6
  56. package/types/internal/src/modules/purchases.d.ts +357 -13
  57. package/types/internal/src/modules/realtime.d.ts +45 -1
  58. package/types/internal/src/modules/stores.d.ts +259 -22
  59. package/types/internal/src/transport.d.ts +5 -0
  60. package/types/server/src/auth/jwt.d.ts +1 -6
  61. package/types/web/src/auth.d.ts +15 -4
  62. package/types/web/src/backends.d.ts +20 -13
  63. package/types/web/src/chat.d.ts +329 -0
  64. package/types/web/src/client.d.ts +28 -3
  65. package/types/web/src/gateways.d.ts +90 -0
  66. package/types/web/src/index.d.ts +5 -4
  67. package/types/web/src/session.d.ts +11 -1
  68. package/types/web/src/socket.d.ts +18 -2
  69. package/types/web/src/voice.d.ts +36 -0
  70. package/dist/chunks/2RG3I7LX.js +0 -6
  71. package/dist/chunks/A76UZBAF.js +0 -2
  72. package/dist/chunks/AD5LBZAB.js +0 -4
  73. package/dist/chunks/DRGYTL6T.js +0 -20
  74. package/dist/chunks/IVEAUFGE.js +0 -13
  75. package/dist/chunks/IYCW6PWG.js +0 -2
  76. package/dist/chunks/KL7FIRII.js +0 -2
  77. package/dist/chunks/U4OZ4HOF.js +0 -2
  78. package/types/backend/src/channels.d.ts +0 -81
  79. package/types/backend/src/rooms.d.ts +0 -178
  80. package/types/web/src/ai.d.ts +0 -224
  81. package/types/web/src/rooms.d.ts +0 -61
@@ -0,0 +1,329 @@
1
+ import { type AgentEvent, type ChatEvent, type GoalEvent, type ToolEvent, type TurnEnd, type TurnEvent, type VoiceEvent } from '../../internal/src/chat.js';
2
+ import { type Transport } from '../../internal/src/transport.js';
3
+ export type { AgentEvent, ChatEvent, GoalEvent, HistoryEvent, HistoryItem, Publication, ThinkingEvent, ToolEvent, TranscriptEvent, TurnEnd, TurnEvent, VoiceEvent } from '../../internal/src/chat.js';
4
+ /** A picture or a file a message carries: an image the model was shown, or a file put in the chat's workspace. */
5
+ export interface ChatAttachment {
6
+ kind: 'image' | 'file' | (string & {});
7
+ /** What the message calls it: a file's name in the workspace. */
8
+ name: string;
9
+ contentType: string;
10
+ size: number;
11
+ /** Its bytes' sha256, hex. */
12
+ sha256: string;
13
+ /** Its place among the message's attachments (its images, then its files): what its bytes are read by. */
14
+ position: number;
15
+ /** Where it was copied from in the project's storage. */
16
+ bucket: string;
17
+ path: string;
18
+ /** A file's place in the chat's workspace (.palbase/attachments/<message id>/<name>); null for a picture. */
19
+ workspacePath: string | null;
20
+ }
21
+ /** A member's (or the server's) message, as the agent got it. */
22
+ export interface ChatMessageView {
23
+ /** The writer's id for it: the same id is the same message. */
24
+ clientId: string;
25
+ id: string | null;
26
+ text: string;
27
+ /** The member who wrote it; null: the project's server. */
28
+ author: string | null;
29
+ authorName: string;
30
+ delivery: string;
31
+ /** pending until the agent has it, delivered once it does, failed when it could not be given. */
32
+ status: 'pending' | 'delivered' | 'failed';
33
+ /** Why it failed: the module's code. */
34
+ error: string | null;
35
+ /** The turn it started or reached. */
36
+ responseId: string | null;
37
+ createdAt: string;
38
+ /** The signed-in user wrote it. */
39
+ mine: boolean;
40
+ /** Its writer's labels for it ({} for none): what an app draws it by. */
41
+ metadata: Record<string, string>;
42
+ /** It is of what a fork began with: the chat it was forked from had it. */
43
+ history: boolean;
44
+ /** Its text is the first of a longer one (a fork's history part carries no more): the fork's model read it whole. */
45
+ truncated: boolean;
46
+ /** text: written to the chat; voice: a line said out loud in its voice — the person's (author, the member) or the voice's (author null). */
47
+ kind: 'text' | 'voice' | (string & {});
48
+ /** A line of kind voice: the voice it was said in, and by whom (user: the person; assistant: the voice). */
49
+ voice: {
50
+ id: string;
51
+ role: 'user' | 'assistant' | (string & {});
52
+ } | null;
53
+ /** Its pictures and files, its images first ([] for none). */
54
+ attachments: ChatAttachment[];
55
+ }
56
+ /** One model step's reasoning, as the agent showed it (thinking: visible): its windows as they came, together. */
57
+ export interface ChatThinking {
58
+ /** The model step it is of; null when the part named none. */
59
+ stepId: string | null;
60
+ runId: string;
61
+ text: string;
62
+ /**
63
+ * The agent shows its reasoning and the turn's provider would not give it: why there is none (text ''), said
64
+ * once, where it would have been — the model, and the provider's word (null otherwise).
65
+ */
66
+ unavailable: boolean;
67
+ model: string | null;
68
+ reason: string | null;
69
+ }
70
+ /** A piece of an answer, in order: its text, split where a tool was called, a subagent handed work, or a step's reasoning began. */
71
+ export type ChatPart = {
72
+ type: 'text';
73
+ text: string;
74
+ } | {
75
+ type: 'tool';
76
+ tool: ToolEvent;
77
+ } | {
78
+ type: 'agent';
79
+ agent: AgentEvent;
80
+ } | {
81
+ type: 'thinking';
82
+ thinking: ChatThinking;
83
+ };
84
+ /** One answer of the agent's (or of one of its subagents). */
85
+ export interface ChatResponse {
86
+ id: string;
87
+ status: TurnEvent['status'] | null;
88
+ waiting: TurnEvent['waiting'];
89
+ /** How its turn ended, in the chat's words — completed, failed, stopped, budget_exceeded; null while it has not (or a subagent's). */
90
+ end: TurnEnd | (string & {}) | null;
91
+ /** Why, when it did not complete and was not stopped: its code, its words, whether asking again may go otherwise. */
92
+ error: TurnEvent['error'];
93
+ /** The whole answer so far. */
94
+ text: string;
95
+ parts: ChatPart[];
96
+ /** A subagent's: its run, its name, and the response that handed it the work. */
97
+ runId: string | null;
98
+ agent: string | null;
99
+ parentId: string | null;
100
+ /** The member whose turn it is (a subagent's: its turn's); null: the project's server's, or the engine's. */
101
+ startedBy: string | null;
102
+ /** The signed-in user's own turn. */
103
+ mine: boolean;
104
+ /** The model's reasoning, step by step, when the agent shows it — also in parts, where each step's began. */
105
+ thinking: ChatThinking[];
106
+ /** A turn the chat's goal opened (a subagent's: its turn's): the goal, the turn's role and its number. */
107
+ goal: {
108
+ goalId: string;
109
+ role: string;
110
+ seq: number;
111
+ } | null;
112
+ /** It is of what a fork began with: the agent's answer in the chat it was forked from. */
113
+ history: boolean;
114
+ /** Its text is the first of a longer one (a fork's history part carries no more). */
115
+ truncated: boolean;
116
+ }
117
+ /** One tool call of the chat, its whole answer — the stream shows its end (its last 16 KiB) — its secret shapes masked. */
118
+ export interface ChatToolCall {
119
+ callId: string;
120
+ name: string;
121
+ status: string;
122
+ runId: string;
123
+ responseId: string;
124
+ /** What the agent called it with; null when its arguments, masked, are no JSON. */
125
+ input: unknown;
126
+ /** Its answer's text, whole; null: none yet. */
127
+ output: string | null;
128
+ outputBytes: number;
129
+ imageCount: number;
130
+ isError: boolean;
131
+ }
132
+ /** One part of a chat's stream as a page of its past holds it (palbase/ai's chats.parts, or the module's own page). */
133
+ export interface ChatStreamPart {
134
+ type: string;
135
+ id?: number;
136
+ data: Record<string, unknown>;
137
+ }
138
+ /** A page of a chat's past, the oldest it holds first; nextCursor reads the page before it (none: the chat's start is in this one). */
139
+ export interface ChatPartsPage {
140
+ parts: ChatStreamPart[];
141
+ nextCursor?: string;
142
+ }
143
+ /**
144
+ * Which page of a chat's past is read: before a part (before: its journal's sequence — the oldest the channel gave),
145
+ * where a page's nextCursor left off (cursor), or from the chat's newest end (neither).
146
+ */
147
+ export interface ChatPartsQuery {
148
+ before?: number;
149
+ cursor?: string;
150
+ limit?: number;
151
+ signal?: AbortSignal;
152
+ }
153
+ /** How a chat reads its past past the day its channel keeps: the module, with the member's own session — or the app's own way. */
154
+ export interface ChatHistoryReader {
155
+ parts(chatId: string, query: ChatPartsQuery): Promise<ChatPartsPage>;
156
+ }
157
+ /** The chat's goal as its words told it: set, changed, its status, and each word in order. */
158
+ export interface ChatGoal {
159
+ id: string;
160
+ objective: string;
161
+ rubric: string;
162
+ /** active, paused, blocked, budget_limited, achieved — or cleared. */
163
+ status: string;
164
+ /** Why it last left active, as a stable code (empty while active). */
165
+ stopCode: string;
166
+ stopReason: string;
167
+ /** What its next turn is, as it was last said: work, or verify. */
168
+ phase: string;
169
+ maxTurns: number | null;
170
+ maxCostUsd: number | null;
171
+ /** Its words as they came: set, changed, each turn begun, judged and checked, its status, cleared. */
172
+ events: GoalEvent[];
173
+ }
174
+ /** The chat's voice as its parts told it: whose it is, what becomes of what is asked of it, live or ended — and why. */
175
+ export interface ChatVoice {
176
+ id: string;
177
+ /** Its member; null: the server's own. */
178
+ userId: string | null;
179
+ /** The signed-in user's own. */
180
+ mine: boolean;
181
+ delegation: VoiceEvent['delegation'];
182
+ status: 'started' | 'closed' | (string & {});
183
+ /** On closed: how long it lasted, and why it ended ('' while live). */
184
+ seconds: number | null;
185
+ closeReason: string;
186
+ }
187
+ /**
188
+ * One thing of the conversation, in order: a message (a line said in the chat's voice too: kind voice), an answer, a
189
+ * word of the chat's goal, or the chat's voice beginning or ending.
190
+ */
191
+ export type ChatItem = {
192
+ type: 'message';
193
+ message: ChatMessageView;
194
+ } | {
195
+ type: 'response';
196
+ response: ChatResponse;
197
+ } | {
198
+ type: 'goal';
199
+ goal: GoalEvent;
200
+ } | {
201
+ type: 'voice';
202
+ voice: VoiceEvent;
203
+ };
204
+ export interface ChatState {
205
+ /** The conversation in order: the members' messages and the agent's answers (a subagent's is in its parent's parts). */
206
+ items: ChatItem[];
207
+ messages: ChatMessageView[];
208
+ /** Every answer, the subagents' too, as they began. */
209
+ responses: ChatResponse[];
210
+ /** The last turn's state. */
211
+ turn: TurnEvent | null;
212
+ /**
213
+ * What waits for the signed-in user: the tool calls of their own turns
214
+ * waiting for their yes, and the client tool calls of their own turns
215
+ * waiting for their app's answer — each said back through the gateway's
216
+ * own messages, its backend deciding. Another member's turn is theirs.
217
+ */
218
+ waiting: {
219
+ approvals: ToolEvent[];
220
+ outputs: ToolEvent[];
221
+ };
222
+ /** The chat's goal, once one was set in it; null: none. */
223
+ goal: ChatGoal | null;
224
+ /** A fork's: the chat it was forked from, and the message it was cut before; null for a chat of its own. */
225
+ forkedFrom: {
226
+ chatId: string;
227
+ beforeMessage: string;
228
+ } | null;
229
+ /** The chat's voice — the last that began in it, live or ended; null: none yet. */
230
+ voice: ChatVoice | null;
231
+ }
232
+ /** A gateway's chat: its state now, and told each time it changes. */
233
+ export interface GatewayChat extends AsyncIterable<ChatState> {
234
+ /** The chat as the channel holds it now: the same object until it changes. */
235
+ readonly state: ChatState;
236
+ /** Told each time the state changes (at most once a microtask); returns the unsubscribe. */
237
+ subscribe(listener: (state: ChatState) => void): () => void;
238
+ /** More of the chat's past is there to read with loadOlder (true until a page reaches the chat's start). */
239
+ readonly hasOlder: boolean;
240
+ /**
241
+ * Reads the page of the chat's past before what it holds — the channel keeps a day — and folds it in: the first
242
+ * page is the one before the oldest part the channel gave (before: its sequence; none: the chat's newest end),
243
+ * each after it the page its last named. With the member's own session (the project lets its app's users reach
244
+ * their chats: ai.settings clients) unless load gives the page another way — the app's backend asking palbase/ai's
245
+ * chats.parts. Resolves with hasOlder. The chat's id is the join's chatId, or its messages' (chatId gives it).
246
+ */
247
+ loadOlder(options?: {
248
+ limit?: number;
249
+ chatId?: string;
250
+ load?: (query: ChatPartsQuery) => Promise<ChatPartsPage>;
251
+ signal?: AbortSignal;
252
+ }): Promise<boolean>;
253
+ }
254
+ /**
255
+ * A chat folded from its channel's parts. It holds no connection: a part is
256
+ * handed to take() as the channel gives it — once, in order — and the state
257
+ * is built from them; me is the signed-in user, read as the state is built.
258
+ */
259
+ export declare class ChatLog implements GatewayChat {
260
+ private readonly me;
261
+ private readonly reader;
262
+ private readonly messages;
263
+ private readonly responses;
264
+ private readonly order;
265
+ private turn;
266
+ private goal;
267
+ /** The goal's words taken, by their sequence. */
268
+ private readonly goalWords;
269
+ /** The voices' starts and ends taken, each once by its voice: an end is told under no sequence (vclosed:<voice>). */
270
+ private readonly voiceWords;
271
+ /** The chat's voice: the last that began in it, live or ended. */
272
+ private voice;
273
+ /** A fork's history, by its part's place: whenever a part comes, what it holds goes before the rest of the chat. */
274
+ private readonly history;
275
+ private forkedFrom;
276
+ private readonly calls;
277
+ /**
278
+ * Every part the channel gave, in order, and the pages of the past read before them (loadOlder), the oldest first:
279
+ * a page read is folded by folding them all again, the past first — a part both told (the channel's day and the
280
+ * page's newest end meet) is then the channel's, as it came after.
281
+ */
282
+ private readonly taken;
283
+ private older;
284
+ /** The oldest part the channel gave, by its journal's sequence: the first page of the past is read before it. */
285
+ private oldestLive;
286
+ private olderCursor;
287
+ private olderLeft;
288
+ private loading;
289
+ private chatId;
290
+ private readonly listeners;
291
+ /** The iterations waiting for a change: woken when the gateway is left. */
292
+ private readonly enders;
293
+ private snapshot;
294
+ private told;
295
+ private ended;
296
+ constructor(me?: () => string | undefined, reader?: ChatHistoryReader, chatId?: string);
297
+ get hasOlder(): boolean;
298
+ loadOlder(options?: Parameters<GatewayChat['loadOlder']>[0]): Promise<boolean>;
299
+ private readOlder;
300
+ private idFor;
301
+ private refold;
302
+ get state(): ChatState;
303
+ subscribe(listener: (state: ChatState) => void): () => void;
304
+ /** The state now, then each time it changes, until the gateway is left. */
305
+ [Symbol.asyncIterator](): AsyncGenerator<ChatState, void, undefined>;
306
+ /** The gateway was left: an iteration ends, and no part is taken any more. */
307
+ end(): void;
308
+ /** One payload of the channel's "chat" event; one that is no part this runtime reads is let be. */
309
+ takePayload(payload: unknown): void;
310
+ take(e: ChatEvent): void;
311
+ private fold;
312
+ private goalSaid;
313
+ private heard;
314
+ private said;
315
+ private place;
316
+ private response;
317
+ private changed;
318
+ private build;
319
+ }
320
+ /**
321
+ * A chat's member's reads of it beyond its channel, with their own session — the ai module's calls written here, so
322
+ * a browser's bundle carries nothing more of the module. The project lets its app's users reach their chats
323
+ * (ai.settings clients), or the module refuses them (403 clients_disabled).
324
+ */
325
+ export interface ChatReads extends ChatHistoryReader {
326
+ toolCall(chatId: string, callId: string, signal?: AbortSignal): Promise<ChatToolCall>;
327
+ workspaceFile(chatId: string, path: string, manifest?: string, signal?: AbortSignal): Promise<Blob>;
328
+ }
329
+ export declare function chatReader(transport: Transport): ChatReads;
@@ -5,7 +5,7 @@ import { type RefreshMode, type SessionStore } from './session.js';
5
5
  import { RealtimeSocket } from './socket.js';
6
6
  import { Analytics, type AnalyticsOptions } from './analytics.js';
7
7
  import { Purchases } from './purchases.js';
8
- import { AI } from './ai.js';
8
+ import { type ChatToolCall } from './chat.js';
9
9
  export interface ClientOptions {
10
10
  /** The project's address: https://<ref>.<domain> */
11
11
  url: string;
@@ -46,6 +46,14 @@ export interface ClientOptions {
46
46
  * which app asks, it does not prove it. Its id, not its slug: a UUID.
47
47
  */
48
48
  appId?: string;
49
+ /**
50
+ * The language the app shows itself in, said with every request as
51
+ * Accept-Language — a tag ("tr", "pt-BR"), or tags in order of preference
52
+ * — what auth's emails and pages, a flag's and a paywall's rules and a
53
+ * backend's translations read. Without it the browser says its own.
54
+ * pb.setLanguage changes it.
55
+ */
56
+ language?: string | readonly string[];
49
57
  }
50
58
  /**
51
59
  * The app's client. A backend's generated Palbe module (palbase apps:sync)
@@ -63,7 +71,24 @@ export interface PalbaseClient {
63
71
  analytics: Analytics;
64
72
  /** What the signed-in customer has (live, onCustomerInfo), the paywall's catalog, and buying on the web. */
65
73
  purchases: Purchases;
66
- /** The project's agents, for the signed-in user (when the project lets its users talk to them): their chats, followed as typed parts. */
67
- ai: AI;
74
+ /** The language every request says from now on (ClientOptions.language); null for the browser's own. */
75
+ setLanguage(language: string | readonly string[] | null): void;
76
+ /**
77
+ * One tool call of a chat the signed-in user is a member of, its whole answer — the one a tool part names
78
+ * (call_id): the chat's channel shows a call's answer by its end (omittedBytes what it left out). Read with their
79
+ * own session (the project lets its app's users reach their chats: ai.settings clients); another chat's call is
80
+ * not found.
81
+ */
82
+ chatToolCall(chatId: string, callId: string, options?: {
83
+ signal?: AbortSignal;
84
+ }): Promise<ChatToolCall>;
85
+ /**
86
+ * A file of a chat's workspace, its bytes: from the tree the workspace is at now, or from the one a publish named
87
+ * (manifest: an ai.workspace.published event's manifest_id) — a build's product as that publish left it. Up to
88
+ * 64 MiB (file_too_large past it); a path its tree does not name is file_not_found.
89
+ */
90
+ chatWorkspaceFile(chatId: string, path: string, manifest?: string, options?: {
91
+ signal?: AbortSignal;
92
+ }): Promise<Blob>;
68
93
  }
69
94
  export declare function createClient(opts: ClientOptions): PalbaseClient;
@@ -0,0 +1,90 @@
1
+ import { type ChatHistoryReader, type GatewayChat } from './chat.js';
2
+ import type { RealtimeSocket } from './socket.js';
3
+ /** One event of a gateway, as a generated module's union has it. */
4
+ export type GatewayEvent = {
5
+ event: string;
6
+ payload: unknown;
7
+ };
8
+ /**
9
+ * A gateway, as the generator writes one. `send`, `reply` and `event` carry
10
+ * types alone — their values are never read: `gateway.send('say', { … })` is
11
+ * checked against the message's shape and resolves with its ack's, and a
12
+ * handler's event against the gateway's events.
13
+ */
14
+ export interface GatewayDescriptor<E extends GatewayEvent, S, R = Record<keyof S, unknown>, P extends readonly string[] = readonly string[], C extends boolean = false> {
15
+ /** The backend's class: `ChatGateway`. */
16
+ name?: string;
17
+ /** The channel pattern the backend declared: `chat:{chatId}`. */
18
+ pattern: string;
19
+ /** It keeps what it sends: a join is sent what it keeps, and a dropped socket misses nothing. */
20
+ durable?: boolean;
21
+ /** Its server pipes a chat into it: the handle holds the chat's state (`handle.chat`). */
22
+ chat?: C;
23
+ /** The `{param}` names, in the pattern's order: a join takes exactly these. */
24
+ params: P;
25
+ /** The declared event names: what a handle listens to. */
26
+ events: readonly string[];
27
+ /** The declared message names: what a handle sends. */
28
+ messages: readonly string[];
29
+ send: S;
30
+ reply?: R;
31
+ event: E;
32
+ }
33
+ /** How a gateway is joined. */
34
+ export interface JoinOptions {
35
+ /** A durable gateway's: sent what it keeps first (true, the default), or only what comes after the join (false). */
36
+ history?: boolean;
37
+ }
38
+ /** What `on` returns: called, it stops that callback (`off` does the same). */
39
+ export type GatewaySubscription = () => void;
40
+ /** A live gateway. Hold it for as long as the view is on screen, and `leave` it when the view goes: an occupied gateway is one its backend is still running for. */
41
+ export interface GatewayHandle<E extends GatewayEvent, S, R = Record<keyof S, unknown>> {
42
+ /** The channel this handle is on: `chat:42`. */
43
+ readonly topic: string;
44
+ /** A durable gateway's number of the last event the handle was sent: what a dropped socket joins again after. */
45
+ readonly seq: number | undefined;
46
+ /** Every event the gateway declares, as a union to switch over. */
47
+ on(handler: (event: E) => void): GatewaySubscription;
48
+ /** One event by its name, its payload typed. */
49
+ on<K extends E['event']>(event: K, handler: (payload: Extract<E, {
50
+ event: K;
51
+ }>['payload']) => void): GatewaySubscription;
52
+ /** Stops one callback. */
53
+ off(subscription: GatewaySubscription): void;
54
+ /**
55
+ * Sends one declared message and waits for the gateway's backend to take
56
+ * it: resolves with its ack (what its @Subscribe returned), rejects with a
57
+ * GatewayError — its reason — when it did not.
58
+ */
59
+ send<K extends keyof S & string>(message: K, payload: S[K]): Promise<K extends keyof R ? R[K] : unknown>;
60
+ /** Drops every callback of this handle and lets go of the gateway; the channel is left with its last handle. */
61
+ leave(): void;
62
+ }
63
+ /** A chat gateway's handle: the chat its server piped into the channel, as its state. */
64
+ export interface ChatGatewayHandle<E extends GatewayEvent, S, R = Record<keyof S, unknown>> extends GatewayHandle<E, S, R> {
65
+ /** The chat from its start (the channel keeps it), and each change as the platform writes it. */
66
+ readonly chat: GatewayChat;
67
+ }
68
+ /** What a join resolves with: a chat gateway's handle holds its chat. */
69
+ export type JoinedGateway<E extends GatewayEvent, S, R, C> = [C] extends [true] ? ChatGatewayHandle<E, S, R> : GatewayHandle<E, S, R>;
70
+ /**
71
+ * Why a gateway did not do what was asked. `reason` is the server's: for a join,
72
+ * `unauthorized` (the backend did not let the user in) or `unavailable` (it
73
+ * could not be asked: try again); for a message, the reason the backend gave,
74
+ * `forbidden` when it gave none or the user may only listen, `rate_limited`,
75
+ * `unavailable`. When no answer came it is `no reply` or `socket closed` —
76
+ * the message may have been taken — and `not joined` when nothing was sent:
77
+ * the handle left, its socket closed (a sign-out, another user's sign-in), or
78
+ * the socket dropped and is being opened again.
79
+ */
80
+ export declare class GatewayError extends Error {
81
+ readonly reason: string;
82
+ constructor(message: string, reason: string);
83
+ }
84
+ /**
85
+ * Joins `gateway` on `socket`: resolves once the backend let the user in,
86
+ * rejects with a GatewayError when it did not. me is the signed-in user: a
87
+ * chat's turns and messages that are theirs; reader is how a chat gateway's
88
+ * chat reads its past and its calls with the user's session.
89
+ */
90
+ export declare function joinGateway<E extends GatewayEvent, S, R, P extends readonly string[], C extends boolean = false>(socket: RealtimeSocket, gateway: GatewayDescriptor<E, S, R, P, C>, params: Record<P[number], string>, options?: JoinOptions, me?: () => string | undefined, reader?: ChatHistoryReader): Promise<JoinedGateway<E, S, R, C>>;
@@ -1,9 +1,10 @@
1
1
  export { createClient, type ClientOptions, type PalbaseClient } from './client.js';
2
2
  export type { Auth, CredentialsLike, SignInResult, User } from './auth.js';
3
- export { localStore, memoryStore, SessionChangedError, type AuthEvent, type RefreshMode, type Session, type SessionStore } from './session.js';
4
- export { Channel, RealtimeSocket, type PushReply } from './socket.js';
3
+ export { localStore, memoryStore, SessionChangedError, type AuthEvent, type RefreshMode, type Session, type SessionEndReason, type SessionStore } from './session.js';
4
+ export { Channel, RealtimeSocket, type ChannelOptions, type PushReply } from './socket.js';
5
5
  export { Analytics, type AnalyticsOptions } from './analytics.js';
6
- export { AI, ChatSession, type AgentEvent, type Chat, type ChatEvent, type ChatItem, type ChatMessageView, type ChatOptions, type ChatPart, type ChatResponse, type ChatState, type ChatVoice, type ClientToolHandlers, type ToolEvent, type ToolResult, type TurnEvent, type VoiceEvent, } from './ai.js';
6
+ export type { AgentEvent, ChatAttachment, ChatEvent, ChatGoal, ChatItem, ChatMessageView, ChatPart, ChatPartsPage, ChatPartsQuery, ChatResponse, ChatState, ChatStreamPart, ChatThinking, ChatToolCall, ChatVoice, GatewayChat, GoalEvent, HistoryEvent, HistoryItem, Publication, ThinkingEvent, ToolEvent, TranscriptEvent, TurnEnd, TurnEvent, VoiceEvent, } from './chat.js';
7
+ export { connectVoice, type StartedVoice, type VoiceConnection, type VoiceOptions } from './voice.js';
7
8
  export { Purchases, type CustomerInfo, type CustomerInfoListener, type MergeGrant, type Offerings } from './purchases.js';
8
9
  export type { Paywall, PaywallRenderOptions } from './paywall/placement.js';
9
10
  export type { PaywallAttribute } from './paywall/attributes.js';
@@ -11,5 +12,5 @@ export type { PaywallPackage, PaywallView } from './paywall/render.js';
11
12
  export { PalbaseError } from '../../internal/src/transport.js';
12
13
  export { BackendError, isBackendError, type BackendErrorKind, type FieldError } from './backend-error.js';
13
14
  export type { BackendBase, CallOptions, UploadOptions, UploadProgress } from './backends.js';
14
- export { RoomError, type RoomDescriptor, type RoomEvent, type RoomHandle, type RoomSubscription } from './rooms.js';
15
+ export { GatewayError, type ChatGatewayHandle, type GatewayDescriptor, type GatewayEvent, type GatewayHandle, type GatewaySubscription, type JoinedGateway, type JoinOptions } from './gateways.js';
15
16
  export type { RetryPolicy } from '../../internal/src/retry.js';
@@ -17,6 +17,14 @@ export interface SessionStore {
17
17
  watch?(onChange: () => void): () => void;
18
18
  }
19
19
  export type AuthEvent = 'SIGNED_IN' | 'TOKEN_REFRESHED' | 'USER_UPDATED' | 'SIGNED_OUT' | 'SESSION_EXPIRED';
20
+ /**
21
+ * Why a session ended without the app signing out (SESSION_EXPIRED): it ran out (past its lifetime, or idle too
22
+ * long), it was revoked (signed out everywhere, revoked by the project, its token refused), the account was deleted
23
+ * (by the project, or by the user on another device), or the account was suspended.
24
+ */
25
+ export type SessionEndReason = 'expired' | 'revoked' | 'userDeleted' | 'userBanned';
26
+ /** What a refused refresh says: auth's reason when it gives one, else what its code means. */
27
+ export declare function sessionEndReasonOf(refusal: unknown): SessionEndReason;
20
28
  export interface TokenPair {
21
29
  access_token: string;
22
30
  refresh_token: string;
@@ -57,6 +65,8 @@ export declare class SessionManager {
57
65
  private current;
58
66
  private loaded;
59
67
  private inflight;
68
+ /** Why the last session ended, set before SESSION_EXPIRED is told; null once someone signs in or out. */
69
+ endReason: SessionEndReason | null;
60
70
  private listeners;
61
71
  private store;
62
72
  private refresher;
@@ -82,7 +92,7 @@ export declare class SessionManager {
82
92
  * tab: a sign-out signs the user out of every tab. It is left alone only
83
93
  * when it holds another user's (someone else signed in since).
84
94
  */
85
- clear(event: 'SIGNED_OUT' | 'SESSION_EXPIRED'): Promise<void>;
95
+ clear(event: 'SIGNED_OUT' | 'SESSION_EXPIRED', reason?: SessionEndReason): Promise<void>;
86
96
  /** The access token to send now; undefined when no one is signed in. */
87
97
  accessToken(): Promise<string | undefined>;
88
98
  /** One refresh at a time — in this tab, and across tabs when the store is shared. */
@@ -12,6 +12,14 @@ export interface SocketOptions {
12
12
  replyTimeoutMs?: number;
13
13
  }
14
14
  export type ChannelState = 'closed' | 'joining' | 'joined' | 'refused';
15
+ export interface ChannelOptions {
16
+ /**
17
+ * On a durable channel, the number to be sent its log after: what was missed
18
+ * since (0: all it keeps). Left out, the channel is heard from its join on.
19
+ * Either way it is joined again after the last number it was sent.
20
+ */
21
+ after?: number;
22
+ }
15
23
  /**
16
24
  * What became of a push: the server's answer — `ok` with its response, or
17
25
  * `error` with `{ reason }` — or, when none came, `timeout` (`no reply`: the
@@ -26,13 +34,20 @@ export declare class Channel {
26
34
  state: ChannelState;
27
35
  /** Why the server refused the last join, when it did. */
28
36
  refusal?: Record<string, unknown>;
37
+ /**
38
+ * A durable channel's number: of the last broadcast it was sent (each carries
39
+ * its `seq`), or of the one its join started after. A socket that drops joins
40
+ * it again after it, and is sent what it missed in order, once. Undefined on
41
+ * a channel that keeps no log.
42
+ */
43
+ logSeq?: number;
29
44
  joinRef: string | null;
30
45
  private listeners;
31
46
  private closers;
32
47
  private joinWaiters;
33
48
  readonly name: string;
34
49
  private socket;
35
- constructor(name: string, socket: RealtimeSocket);
50
+ constructor(name: string, socket: RealtimeSocket, opts?: ChannelOptions);
36
51
  get topic(): string;
37
52
  /** Resolves once joined; rejects with the server's reason when refused. */
38
53
  join(): Promise<void>;
@@ -69,7 +84,8 @@ export declare class RealtimeSocket {
69
84
  reconnects: number;
70
85
  private opts;
71
86
  constructor(opts: SocketOptions);
72
- channel(name: string): Channel;
87
+ /** The channel by its name; opts are the first caller's (a channel already made keeps its own). */
88
+ channel(name: string, opts?: ChannelOptions): Channel;
73
89
  /** Closes the socket; every channel is closed (the app joins again — as the next user, if one signs in). */
74
90
  disconnect(): void;
75
91
  /** A new token for every joined channel (the session was refreshed). */
@@ -0,0 +1,36 @@
1
+ /** What the app's backend answers the offer with: the voice it started, and the provider's answer (chats.voice.start). */
2
+ export interface StartedVoice {
3
+ id: string;
4
+ sdp: string;
5
+ }
6
+ export interface VoiceOptions {
7
+ /** The round trip to the app's own backend: the offer (an SDP) in, the voice and its answer out. */
8
+ start(sdp: string): Promise<StartedVoice>;
9
+ /** Where the voice plays; left out, an audio element of its own (autoplay), not in the page. */
10
+ audio?: HTMLAudioElement;
11
+ /** A microphone the app opened already — left open on close; left out, one is asked for (getUserMedia) and closed with the voice. */
12
+ microphone?: MediaStream;
13
+ /** How long the offer's network candidates are gathered at most before it is sent (default 10 s). */
14
+ iceTimeoutMs?: number;
15
+ /** Ends the connecting: whatever was opened is closed. */
16
+ signal?: AbortSignal;
17
+ /** The provider's word that something went wrong in the voice (its events channel carries errors alone). */
18
+ onError?(message: string): void;
19
+ /** The connection's state each time it changes: connected, disconnected, failed, closed. */
20
+ onState?(state: RTCPeerConnectionState): void;
21
+ }
22
+ /** A voice connected: the peer connection, where it plays, and its microphone — muted, or closed. */
23
+ export interface VoiceConnection {
24
+ /** The voice the backend started: what it reads, tells and closes it by. */
25
+ readonly id: string;
26
+ readonly peer: RTCPeerConnection;
27
+ readonly audio: HTMLAudioElement;
28
+ readonly microphone: MediaStream;
29
+ /** The microphone is heard (false) or not (true). */
30
+ readonly muted: boolean;
31
+ mute(on: boolean): void;
32
+ /** Ends it here: the connection, and the microphone it opened. The backend closes the voice itself (chats.voice.close). */
33
+ close(): void;
34
+ }
35
+ /** Connects the browser to a chat's voice: the microphone in, the voice out, the offer through the app's own backend. */
36
+ export declare function connectVoice(options: VoiceOptions): Promise<VoiceConnection>;
@@ -1,6 +0,0 @@
1
- /*! palbase 0.6.2 — Palbase SDK License (see LICENSE) */
2
- import{a as C,b as E}from"./IYCW6PWG.js";async function*S(e,t={}){let n=e.getReader(),o=new TextDecoder,r="",a="",i=[],g,w=()=>{n.cancel().catch(()=>{})};t.signal?.addEventListener("abort",w,{once:!0});try{if(t.signal?.aborted)return;for(;;){let{done:v,value:A}=await n.read();if(v||t.signal?.aborted)return;t.onBytes?.(),r+=o.decode(A,{stream:!0});for(let y=r.indexOf(`
3
- `);y>=0;y=r.indexOf(`
4
- `)){let s=r.slice(0,r[y-1]==="\r"?y-1:y);r=r.slice(y+1),s===""?(a!==""&&(yield g===void 0?{event:a,data:i.join(`
5
- `)}:{event:a,data:i.join(`
6
- `),id:g}),a="",i=[],g=void 0):s.startsWith("event:")?a=k(s,6):s.startsWith("data:")?i.push(k(s,5)):s.startsWith("id:")&&(g=k(s,3))}}}finally{t.signal?.removeEventListener("abort",w);try{await n.cancel()}catch{}}}function k(e,t){let n=e.slice(t);return n.startsWith(" ")?n.slice(1):n}function P(e){let t=0;for(let n=0;n<e.length;n++){let o=e.charCodeAt(n);o<128?t+=1:o<2048?t+=2:o>=55296&&o<=56319&&n+1<e.length&&(e.charCodeAt(n+1)&64512)===56320?(t+=4,n++):t+=3}return t}function $(e,t){if(t<=0)return 0;let n=0;for(let o=0;o<e.length;o++){let r=e.charCodeAt(o),a=3,i=1;if(r<128?a=1:r<2048?a=2:r>=55296&&r<=56319&&o+1<e.length&&(e.charCodeAt(o+1)&64512)===56320&&(a=4,i=2),n+=a,n>=t)return o+i;o+=i-1}return e.length}function j(e,t){return e.slice($(e,t))}var M={too_many_streams:()=>"too many chat streams are open at once (4 a user, 32 a project): close one \u2014 chat.close(), or leave a stream's loop \u2014 then follow again",too_many_chats:()=>"this user is in as many chats as they may be (100): delete or leave one before starting another",too_many_endpoints:()=>"the project holds as many tool endpoints as it may (32): delete one (ai.endpoints.delete) before putting another",too_many_messages:e=>`this user has sent as many messages this minute as the project allows${e.retryAfter!==void 0?`: write again in ${e.retryAfter} s`:""}`,history_full:()=>"the project keeps as much chat history as it may: delete chats to write again",not_your_turn:()=>"this turn is another member's: only the member who started it (or the project's server) answers its tools and decides its approvals",image_not_found:()=>"a picture given is not a file of the project's storage: upload it first (storage.from(bucket).upload(path, file)) and give that bucket and path",storage_unavailable:e=>`the project's storage did not answer when the model was to read a picture${e.retryAfter!==void 0?`: try again in ${e.retryAfter} s`:"; try again shortly"}`},O=new WeakSet;function R(e){if(!(e instanceof E)||O.has(e))return e;let t=M[e.code];if(!t)return e;let n=new E({op:e.op,status:e.status,code:e.code,message:`${t(e)} (${e.op}: ${e.status} ${e.code})`,requestId:e.requestId,retryAfter:e.retryAfter,body:e.body,cause:e});return O.add(n),n}function V(e){return e.catch(t=>{throw R(t)})}var m=class extends Error{constructor(t){super(t),this.name="StreamCut"}};function Y(e,t={}){let n=t.resumeFor??12e4,o=t.idleFor??45e3,r=t.signal,a=t.after,i=t.response,g=new Map;i!==void 0&&g.set(i,t.offset??0);let w=!1,v=()=>{let s={};return a!==void 0&&(s.after=a),i!==void 0&&(s.response=i,s.offset=g.get(i)??0),s};function A(s,_,h){let f=h!==void 0&&/^\d+$/.test(h)?Number(h):void 0;if(f!==void 0&&a!==void 0&&f<=a)return;let c;try{c=C(JSON.parse(_))}catch{throw new m(`a ${s} frame is not JSON`)}if(typeof c!="object"||c===null)throw new m(`a ${s} frame is not an object`);let p;switch(s){case"message":p={type:"message",seq:f??0,message:c};break;case"turn":{let d={...c,type:"turn",seq:f??0};d.waiting??=null,d.error??=null,(d.status==="running"||d.status==="waiting")&&(i=d.response_id),p=d;break}case"text":{let d=String(c.response_id),l=Number(c.offset),u=String(c.text??"");if(!Number.isInteger(l)||l<0)throw new m("a text frame names no offset");i=d;let b=g.get(d)??0,x=l+P(u);if(x<=b)return;let T=l>=b?u:j(u,b-l);g.set(d,x),p={type:"text",response_id:d,offset:Math.max(l,b),text:T};break}case"reset":{let d=String(c.response_id),l=Number(c.offset),u=g.get(d)??0;if(!Number.isInteger(l)||l<0||l>=u)return;g.set(d,l),p={type:"reset",response_id:d,offset:l};break}case"tool":case"agent":case"voice":p={...c,type:s,seq:f??0};break;default:break}return f!==void 0&&(a=f),p}async function*y(){let s=Date.now()+n,_;for(let h=0;;h++){if(r?.aborted)throw r.reason;let f=new AbortController,c=()=>f.abort(r?.reason);r?.addEventListener("abort",c,{once:!0});let p,d=()=>{clearTimeout(p),p=setTimeout(()=>f.abort(new m(`no byte for ${o} ms`)),o)},l;try{let u=await e(v(),f.signal);if(!u.body)throw new m("the stream has no body");d();let b=S(u.body,{signal:f.signal,onBytes:()=>{d(),s=Date.now()+n,h=0}});for await(let x of b){let T=A(x.event,x.data,x.id);T&&(yield T)}_=new m("the stream ended")}catch(u){if(r?.aborted)throw r.reason;if(u instanceof E){if(u.status>0&&u.status<500&&u.status!==429||u.code==="too_many_streams")throw R(u);u.retryAfter!==void 0&&(l=u.retryAfter*1e3)}_=f.signal.aborted&&f.signal.reason instanceof m?f.signal.reason:u}finally{clearTimeout(p),r?.removeEventListener("abort",c),f.abort()}if(Date.now()+(l??0)>s)throw _;await L(l??Math.min(250*2**Math.min(h,4),4e3),r)}}return{get position(){return v()},[Symbol.asyncIterator](){if(w)throw new TypeError("a chat stream is read once: follow the chat again from its position for another reader");return w=!0,y()}}}function L(e,t){return new Promise((n,o)=>{if(t?.aborted)return o(t.reason);let a=setTimeout(()=>{t?.removeEventListener("abort",i),n()},e),i=()=>{clearTimeout(a),o(t?.reason)};t?.addEventListener("abort",i,{once:!0})})}var N=new Set(["image/png","image/jpeg","image/webp","image/gif"]);function H(e){return Array.isArray(e)&&e.length>0&&e.every(q)?e.map(F):q(e)?[F(e)]:e==null?[{type:"text",text:""}]:typeof e=="string"?[{type:"text",text:e}]:[{type:"text",text:JSON.stringify(e)??String(e)}]}function q(e){if(typeof e!="object"||e===null)return!1;let t=e;return t.type==="text"?typeof t.text=="string":t.type==="image"?(typeof t.mediaType=="string"||typeof t.media_type=="string")&&(typeof t.data=="string"||t.data instanceof Uint8Array):t.type===void 0&&typeof t.mediaType=="string"&&t.bytes instanceof Uint8Array}function F(e){if("type"in e&&e.type==="text")return{type:"text",text:e.text};let t="bytes"in e||"mediaType"in e?e.mediaType:e.media_type;if(!N.has(t))throw new TypeError(`a tool's picture is image/png, image/jpeg, image/webp or image/gif (got ${JSON.stringify(t)})`);let n="bytes"in e?e.bytes:e.data;return{type:"image",media_type:t,data:typeof n=="string"?n:B(n)}}function B(e){let t="";for(let n=0;n<e.length;n+=32768)t+=String.fromCharCode(...e.subarray(n,n+32768));return btoa(t)}export{S as a,P as b,$ as c,j as d,R as e,V as f,Y as g,H as h};