@base44-preview/sdk 0.8.44-pr.265.8da157e → 0.8.44-pr.267.4549331

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.
@@ -1,499 +0,0 @@
1
- import { Base44Error } from "../utils/axios-client.js";
2
- import { readSseFrames } from "../utils/sse.js";
3
- const KNOWN_EVENT_TYPES = new Set([
4
- "state.changed",
5
- "turn.started",
6
- "turn.finished",
7
- "message.updated",
8
- "error",
9
- "conversation.reset",
10
- "files.changed",
11
- ]);
12
- // A turn in one of these has stopped. `blocked` counts: it is out of credits,
13
- // which resumes on a top-up rather than on anything a caller can await.
14
- const SETTLED_STATUSES = new Set(["idle", "error", "blocked"]);
15
- const RECONNECT_BASE_MS = 1000;
16
- const RECONNECT_MAX_MS = 30000;
17
- // Retry only what a retry can fix. Everything else — a rejected credential, a
18
- // session the server will not serve — would fail identically forever, and
19
- // hammering it is worse than surfacing it.
20
- const isRetryableStatus = (status) => status === 408 || status === 429 || status >= 500;
21
- const asRecord = (value) => value && typeof value === "object" ? value : {};
22
- const asString = (value) => typeof value === "string" && value ? value : undefined;
23
- function toState(raw) {
24
- const source = asRecord(raw);
25
- const state = { status: source.status };
26
- const waiting = asRecord(source.waiting_on);
27
- const waitpointId = asString(waiting.waitpoint_id);
28
- if (waitpointId) {
29
- const toolName = asString(waiting.tool_name);
30
- state.waitingOn = {
31
- kind: waiting.kind,
32
- waitpointId,
33
- ...(toolName ? { toolName } : {}),
34
- };
35
- }
36
- const reason = asString(source.reason);
37
- if (reason)
38
- state.reason = reason;
39
- const turnId = asString(source.turn_id);
40
- if (turnId)
41
- state.turnId = turnId;
42
- const errorSource = asString(source.error_source);
43
- if (errorSource)
44
- state.errorSource = errorSource;
45
- const detail = asString(source.detail);
46
- if (detail)
47
- state.detail = detail;
48
- return state;
49
- }
50
- function toToolCall(raw) {
51
- var _a, _b, _c, _d, _e, _f;
52
- const source = asRecord(raw);
53
- return {
54
- id: (_a = asString(source.id)) !== null && _a !== void 0 ? _a : "",
55
- name: (_b = asString(source.name)) !== null && _b !== void 0 ? _b : "",
56
- status: (_c = asString(source.status)) !== null && _c !== void 0 ? _c : "",
57
- requiresUserInput: Boolean(source.requires_user_input),
58
- waitingOnKind: (_d = source.waiting_on_kind) !== null && _d !== void 0 ? _d : null,
59
- // Kept as the raw string the model produced. It arrives mid-generation, so
60
- // it is routinely incomplete JSON and parsing it here would throw on the
61
- // ticks that matter most.
62
- arguments: (_e = asString(source.arguments)) !== null && _e !== void 0 ? _e : "",
63
- display: (_f = source.display) !== null && _f !== void 0 ? _f : null,
64
- };
65
- }
66
- function toMessage(raw) {
67
- var _a, _b;
68
- const source = asRecord(raw);
69
- const toolCalls = source.tool_calls;
70
- return {
71
- messageId: (_a = asString(source.message_id)) !== null && _a !== void 0 ? _a : "",
72
- role: source.role,
73
- content: (_b = asString(source.content)) !== null && _b !== void 0 ? _b : "",
74
- toolCalls: Array.isArray(toolCalls) ? toolCalls.map(toToolCall) : [],
75
- };
76
- }
77
- /**
78
- * Projects one SSE frame onto a typed event, or drops it.
79
- *
80
- * Unrecognised types are dropped here rather than passed through. The contract
81
- * requires clients to ignore them, so doing it once at the boundary is what lets
82
- * {@link BuildEvent} stay a closed union that narrows on `type` — the alternative
83
- * is an open union whose `data` is `unknown` in every branch.
84
- */
85
- function toBuildEvent(data, frameId) {
86
- var _a, _b;
87
- let parsed;
88
- try {
89
- parsed = JSON.parse(data);
90
- }
91
- catch (_c) {
92
- return null;
93
- }
94
- const source = asRecord(parsed);
95
- const type = asString(source.type);
96
- if (!type || !KNOWN_EVENT_TYPES.has(type))
97
- return null;
98
- const base = { seq: (_b = (_a = asString(source.seq)) !== null && _a !== void 0 ? _a : frameId) !== null && _b !== void 0 ? _b : "" };
99
- const turnId = asString(source.turn_id);
100
- if (type === "message.updated") {
101
- return { ...base, turnId, type, data: toMessage(source.data) };
102
- }
103
- if (type === "conversation.reset" || type === "files.changed") {
104
- return { ...base, turnId, type, data: {} };
105
- }
106
- return {
107
- ...base,
108
- turnId,
109
- type: type,
110
- data: toState(source.data),
111
- };
112
- }
113
- async function streamFailure(response) {
114
- var _a;
115
- const body = await response.text().catch(() => "");
116
- let detail = body;
117
- try {
118
- detail = (_a = asString(asRecord(JSON.parse(body)).detail)) !== null && _a !== void 0 ? _a : body;
119
- }
120
- catch (_b) {
121
- /* a non-JSON error body is still the best message available */
122
- }
123
- return new Base44Error(detail || `Build stream failed with ${response.status}`, response.status, "BUILD_STREAM_FAILED", body, undefined);
124
- }
125
- /** Every route in the family hangs off this. */
126
- const buildPath = (appId) => `/v1/apps/${encodeURIComponent(appId)}/build`;
127
- /**
128
- * The credential, resolved per call.
129
- *
130
- * Empty when there is no getter, which leaves whatever the Axios client already
131
- * carries in place.
132
- */
133
- async function bearer(getToken) {
134
- const token = getToken ? await getToken() : undefined;
135
- return token ? { Authorization: `Bearer ${token}` } : {};
136
- }
137
- function delay(ms, signal) {
138
- return new Promise((resolve) => {
139
- const timer = setTimeout(done, ms);
140
- signal.addEventListener("abort", done, { once: true });
141
- function done() {
142
- clearTimeout(timer);
143
- signal.removeEventListener("abort", done);
144
- resolve();
145
- }
146
- });
147
- }
148
- /**
149
- * The read half of a build session — what a grant can do.
150
- *
151
- * @param deps - Transport, app id and credential.
152
- * @returns The read-only session.
153
- * @internal
154
- */
155
- export function createBuildSessionReader(deps) {
156
- const { axios, appId, getToken } = deps;
157
- const path = buildPath(appId);
158
- const eventsUrl = `${deps.serverUrl.replace(/\/+$/, "")}/api${path}/events`;
159
- const authHeaders = () => bearer(getToken);
160
- const getState = async () => {
161
- const response = await axios.get(`${path}/state`, {
162
- headers: await authHeaders(),
163
- });
164
- return toState(asRecord(response).state);
165
- };
166
- const getTurn = async (turnId) => {
167
- var _a;
168
- const response = asRecord(await axios.get(`${path}/turns/${encodeURIComponent(turnId)}`, {
169
- headers: await authHeaders(),
170
- }));
171
- return {
172
- turnId: (_a = asString(response.turn_id)) !== null && _a !== void 0 ? _a : turnId,
173
- live: Boolean(response.live),
174
- state: toState(response.state),
175
- };
176
- };
177
- const listMessages = async (options = {}) => {
178
- var _a;
179
- const params = {};
180
- if (options.after !== undefined)
181
- params.after = options.after;
182
- if (options.limit !== undefined)
183
- params.limit = options.limit;
184
- const response = asRecord(await axios.get(`${path}/messages`, {
185
- params,
186
- headers: await authHeaders(),
187
- }));
188
- const messages = response.messages;
189
- return {
190
- messages: Array.isArray(messages) ? messages.map(toMessage) : [],
191
- nextAfter: (_a = asString(response.next_after)) !== null && _a !== void 0 ? _a : null,
192
- };
193
- };
194
- const subscribe = (onEvent, options = {}) => {
195
- var _a;
196
- const controller = new AbortController();
197
- let cursor = options.lastEventId;
198
- let stopped = false;
199
- const stop = () => {
200
- if (stopped)
201
- return;
202
- stopped = true;
203
- controller.abort();
204
- };
205
- (_a = options.signal) === null || _a === void 0 ? void 0 : _a.addEventListener("abort", stop, { once: true });
206
- const fail = (error) => {
207
- var _a;
208
- stop();
209
- (_a = options.onError) === null || _a === void 0 ? void 0 : _a.call(options, error);
210
- };
211
- void (async () => {
212
- let attempt = 0;
213
- while (!stopped) {
214
- try {
215
- const headers = {
216
- Accept: "text/event-stream",
217
- ...(await authHeaders()),
218
- };
219
- // Resuming from the cursor is what makes a dropped connection cost
220
- // nothing: the server replays what was missed before any live event.
221
- // Without it every reconnect would replay the whole retained window.
222
- if (cursor)
223
- headers["Last-Event-ID"] = cursor;
224
- const response = await globalThis.fetch(eventsUrl, {
225
- method: "GET",
226
- headers,
227
- signal: controller.signal,
228
- cache: "no-store",
229
- });
230
- if (!response.ok || !response.body)
231
- throw await streamFailure(response);
232
- attempt = 0;
233
- for await (const frame of readSseFrames(response.body)) {
234
- if (stopped)
235
- return;
236
- if (frame.id)
237
- cursor = frame.id;
238
- const event = toBuildEvent(frame.data, frame.id);
239
- if (event)
240
- onEvent(event);
241
- }
242
- // The body ended without an error. A drained deploy looks exactly like
243
- // this, so it is a reconnect rather than a completion — a build has no
244
- // end the transport knows about.
245
- }
246
- catch (error) {
247
- if (stopped)
248
- return;
249
- if (error instanceof Base44Error && !isRetryableStatus(error.status)) {
250
- fail(error);
251
- return;
252
- }
253
- }
254
- if (stopped)
255
- return;
256
- const backoff = Math.min(RECONNECT_MAX_MS, RECONNECT_BASE_MS * 2 ** attempt++);
257
- // Jittered, so a fleet of partner workers reconnecting after one outage
258
- // does not arrive together.
259
- await delay(backoff * (0.5 + Math.random() / 2), controller.signal);
260
- }
261
- })();
262
- return stop;
263
- };
264
- async function* iterate(options = {}) {
265
- const queue = [];
266
- // Held in an object because the generator reads what the callback writes,
267
- // and control-flow narrowing does not follow a closure assignment.
268
- const shared = {
269
- failure: null,
270
- ended: false,
271
- };
272
- let wake = null;
273
- const unsubscribe = subscribe((event) => {
274
- queue.push(event);
275
- wake === null || wake === void 0 ? void 0 : wake();
276
- }, {
277
- ...options,
278
- onError: (error) => {
279
- shared.failure = error;
280
- shared.ended = true;
281
- wake === null || wake === void 0 ? void 0 : wake();
282
- },
283
- });
284
- try {
285
- for (;;) {
286
- while (queue.length)
287
- yield queue.shift();
288
- if (shared.failure)
289
- throw shared.failure;
290
- if (shared.ended)
291
- return;
292
- await new Promise((resolve) => {
293
- wake = resolve;
294
- });
295
- wake = null;
296
- }
297
- }
298
- finally {
299
- unsubscribe();
300
- }
301
- }
302
- const stream = (options) => ({
303
- [Symbol.asyncIterator]: () => iterate(options),
304
- });
305
- async function* iterateText(options) {
306
- var _a;
307
- const sent = new Map();
308
- for await (const event of stream(options)) {
309
- switch (event.type) {
310
- case "message.updated": {
311
- if (event.data.role !== "assistant")
312
- break;
313
- const previous = (_a = sent.get(event.data.messageId)) !== null && _a !== void 0 ? _a : "";
314
- const current = event.data.content;
315
- if (current === previous)
316
- break;
317
- sent.set(event.data.messageId, current);
318
- // Streaming text only ever extends. A snapshot that is not an
319
- // extension means the message was rewritten — a retry, an edit — and a
320
- // chat surface has already made what it printed immutable, so the
321
- // honest rendering is a new paragraph rather than a patch.
322
- yield current.startsWith(previous)
323
- ? current.slice(previous.length)
324
- : `\n\n${current}`;
325
- break;
326
- }
327
- case "turn.finished":
328
- case "error":
329
- return;
330
- case "turn.started":
331
- case "state.changed":
332
- // `blocked` ends it too. No `turn.finished` follows a turn that ran
333
- // out of credits, so waiting for one would hang the loop.
334
- if (event.data.status === "blocked")
335
- return;
336
- break;
337
- default:
338
- break;
339
- }
340
- }
341
- }
342
- const streamText = (options) => ({
343
- [Symbol.asyncIterator]: () => iterateText(options),
344
- });
345
- const waitForTurn = (turnId, options = {}) => new Promise((resolve, reject) => {
346
- var _a;
347
- let settled = false;
348
- const finish = (act) => {
349
- if (settled)
350
- return;
351
- settled = true;
352
- unsubscribe();
353
- act();
354
- };
355
- const unsubscribe = subscribe((event) => {
356
- // Only the events whose payload is a state can settle a turn. Written
357
- // as the positive set so the union narrows to that arm.
358
- if (event.type !== "turn.started" &&
359
- event.type !== "turn.finished" &&
360
- event.type !== "state.changed" &&
361
- event.type !== "error") {
362
- return;
363
- }
364
- if (event.turnId !== turnId && event.data.turnId !== turnId)
365
- return;
366
- if (SETTLED_STATUSES.has(event.data.status)) {
367
- finish(() => resolve(event.data));
368
- }
369
- }, { signal: options.signal, onError: (error) => finish(() => reject(error)) });
370
- (_a = options.signal) === null || _a === void 0 ? void 0 : _a.addEventListener("abort", () => finish(() => {
371
- var _a;
372
- return reject(((_a = options.signal) === null || _a === void 0 ? void 0 : _a.reason) instanceof Error
373
- ? options.signal.reason
374
- : new Error("Waiting for the build turn was aborted"));
375
- }), { once: true });
376
- // The turn may already have ended — between the write returning and this
377
- // call, or before a resumed process got here — and a finished turn emits
378
- // nothing more to wait for. Asked after subscribing, so the transition
379
- // cannot fall between the two.
380
- getTurn(turnId).then((turn) => {
381
- if (!turn.live && SETTLED_STATUSES.has(turn.state.status)) {
382
- finish(() => resolve(turn.state));
383
- }
384
- }, (error) => {
385
- // 404 means the turn is not in the retained window, which a running
386
- // turn also looks like right after it starts. Keep waiting.
387
- if (!(error instanceof Base44Error) || error.status !== 404) {
388
- finish(() => reject(error));
389
- }
390
- });
391
- });
392
- return {
393
- appId,
394
- getState,
395
- listMessages,
396
- getTurn,
397
- waitForTurn,
398
- subscribe,
399
- stream,
400
- streamText,
401
- };
402
- }
403
- /**
404
- * A build session with the writes, for a credential that may start turns.
405
- *
406
- * @param deps - Transport, app id and credential.
407
- * @returns The full session.
408
- * @internal
409
- */
410
- export function createBuildSessionModule(deps) {
411
- const { axios, appId, getToken, serverUrl } = deps;
412
- const path = buildPath(appId);
413
- const reader = createBuildSessionReader(deps);
414
- const authHeaders = () => bearer(getToken);
415
- const writeHeaders = async (idempotencyKey) => ({
416
- ...(await authHeaders()),
417
- ...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}),
418
- });
419
- // The server answers with the path, not a URL. Joining it here means a partner
420
- // can hand the result to a client that is not this SDK without knowing which
421
- // host minted it.
422
- const absolute = (url) => {
423
- if (!url)
424
- return "";
425
- if (/^https?:\/\//i.test(url))
426
- return url;
427
- if (!/^https?:\/\//i.test(serverUrl))
428
- return url;
429
- return new URL(url, serverUrl).toString();
430
- };
431
- return {
432
- ...reader,
433
- async sendMessage(content, options = {}) {
434
- var _a, _b;
435
- const body = { content };
436
- if (options.fileUrls !== undefined)
437
- body.file_urls = options.fileUrls;
438
- const response = asRecord(await axios.post(`${path}/messages`, body, {
439
- headers: await writeHeaders(options.idempotencyKey),
440
- }));
441
- return {
442
- sessionId: (_a = asString(response.session_id)) !== null && _a !== void 0 ? _a : appId,
443
- turnId: (_b = asString(response.turn_id)) !== null && _b !== void 0 ? _b : "",
444
- };
445
- },
446
- async respond(response, options = {}) {
447
- var _a, _b;
448
- const body = {
449
- kind: response.kind,
450
- waitpoint_id: response.waitpointId,
451
- };
452
- if (response.kind === "approval") {
453
- body.approved = response.approved;
454
- }
455
- else if (response.value !== undefined) {
456
- // Omitted rather than sent as null: leaving it out is how you decline,
457
- // and an explicit null would be a second way to say the same thing.
458
- body.value = response.value;
459
- }
460
- const result = asRecord(await axios.post(`${path}/responses`, body, {
461
- headers: await writeHeaders(options.idempotencyKey),
462
- }));
463
- return {
464
- sessionId: (_a = asString(result.session_id)) !== null && _a !== void 0 ? _a : appId,
465
- turnId: (_b = asString(result.turn_id)) !== null && _b !== void 0 ? _b : "",
466
- };
467
- },
468
- async cancel() {
469
- await axios.post(`${path}/cancel`, undefined, {
470
- headers: await authHeaders(),
471
- });
472
- },
473
- async createGrant(options = {}) {
474
- var _a, _b, _c, _d;
475
- const body = {};
476
- if (options.ttlSeconds !== undefined) {
477
- body.token_ttl_seconds = options.ttlSeconds;
478
- }
479
- if (options.subject !== undefined)
480
- body.subject = options.subject;
481
- const response = asRecord(await axios.post(`${path}/grants`, body, {
482
- headers: await authHeaders(),
483
- }));
484
- return {
485
- sessionId: (_a = asString(response.session_id)) !== null && _a !== void 0 ? _a : appId,
486
- grantId: (_b = asString(response.grant_id)) !== null && _b !== void 0 ? _b : "",
487
- token: (_c = asString(response.token)) !== null && _c !== void 0 ? _c : "",
488
- expiresIn: Number(response.expires_in),
489
- expiresAt: (_d = asString(response.expires_at)) !== null && _d !== void 0 ? _d : "",
490
- eventsUrl: absolute(asString(response.events_url)),
491
- };
492
- },
493
- async revokeGrant(grantId) {
494
- await axios.delete(`${path}/grants/${encodeURIComponent(grantId)}`, {
495
- headers: await authHeaders(),
496
- });
497
- },
498
- };
499
- }