@idapt/browser-app-sdk 0.1.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/react.cjs ADDED
@@ -0,0 +1,4167 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/react/context.tsx
7
+
8
+ // ../sdk/src/errors.ts
9
+ var IdaptError = class extends Error {
10
+ constructor(init) {
11
+ super(init.message, init.cause ? { cause: init.cause } : void 0);
12
+ this.name = "IdaptError";
13
+ this.code = init.code;
14
+ this.type = init.type;
15
+ this.subCode = init.subCode;
16
+ this.status = init.status ?? 0;
17
+ this.body = init.body;
18
+ }
19
+ };
20
+ var NetworkError = class extends IdaptError {
21
+ constructor(init) {
22
+ super({ ...init, code: "network_error" });
23
+ this.name = "NetworkError";
24
+ }
25
+ };
26
+ var AuthError = class extends IdaptError {
27
+ constructor(init) {
28
+ super({ ...init, code: init.code ?? "auth_expired" });
29
+ this.name = "AuthError";
30
+ }
31
+ };
32
+ var PermissionError = class extends IdaptError {
33
+ constructor(init) {
34
+ super({ ...init, code: "permission_denied" });
35
+ this.name = "PermissionError";
36
+ }
37
+ };
38
+ var NotFoundError = class extends IdaptError {
39
+ constructor(init) {
40
+ super({ ...init, code: "not_found" });
41
+ this.name = "NotFoundError";
42
+ }
43
+ };
44
+ var ConflictError = class extends IdaptError {
45
+ constructor(init) {
46
+ super({ ...init, code: "conflict" });
47
+ this.name = "ConflictError";
48
+ }
49
+ };
50
+ var RateLimitError = class extends IdaptError {
51
+ constructor(init) {
52
+ super({ ...init, code: "rate_limited" });
53
+ this.name = "RateLimitError";
54
+ this.retryAfter = init.retryAfter;
55
+ }
56
+ };
57
+ var InvalidRequestError = class extends IdaptError {
58
+ constructor(init) {
59
+ super({ ...init, code: "invalid_request" });
60
+ this.name = "InvalidRequestError";
61
+ }
62
+ };
63
+ var ServiceUnavailableError = class extends IdaptError {
64
+ constructor(init) {
65
+ super({ ...init, code: "service_unavailable" });
66
+ /** Always `true` — a 503 means "retry later", the request was fine. */
67
+ this.retryable = true;
68
+ this.name = "ServiceUnavailableError";
69
+ this.retryAfter = init.retryAfter;
70
+ }
71
+ };
72
+ var ServerError = class extends IdaptError {
73
+ constructor(init) {
74
+ super({ ...init, code: "server_error" });
75
+ this.name = "ServerError";
76
+ }
77
+ };
78
+ function errorFromStatus(status, message, body) {
79
+ const { type, subCode } = extractErrorFields(body);
80
+ const base = { message, status, body, type, subCode };
81
+ if (status === 401) return new AuthError(base);
82
+ if (status === 403) return new PermissionError(base);
83
+ if (status === 404) return new NotFoundError(base);
84
+ if (status === 409) return new ConflictError(base);
85
+ if (status === 422) {
86
+ return new InvalidRequestError(base);
87
+ }
88
+ if (status === 429) {
89
+ const retryAfter = extractRetryAfter(body);
90
+ return new RateLimitError({ ...base, retryAfter });
91
+ }
92
+ if (status === 503) {
93
+ const retryAfter = extractRetryAfter(body);
94
+ return new ServiceUnavailableError({ ...base, retryAfter });
95
+ }
96
+ if (status >= 500) return new ServerError(base);
97
+ return new IdaptError({ ...base, code: "unknown" });
98
+ }
99
+ function extractErrorFields(body) {
100
+ if (!body || typeof body !== "object" || !("error" in body)) return {};
101
+ const err = body.error;
102
+ if (!err || typeof err !== "object") return {};
103
+ const out = {};
104
+ const t = err.type;
105
+ if (typeof t === "string") out.type = t;
106
+ const c = err.code;
107
+ if (typeof c === "string") out.subCode = c;
108
+ return out;
109
+ }
110
+ function extractRetryAfter(body) {
111
+ if (body && typeof body === "object" && "retry_after" in body) {
112
+ const v = body.retry_after;
113
+ if (typeof v === "number") return v;
114
+ }
115
+ return void 0;
116
+ }
117
+
118
+ // ../sdk/src/http.ts
119
+ async function request(ctx, req) {
120
+ const res = await send(ctx, req);
121
+ return handleResponse(res, {
122
+ expectJson: req.expectJson !== false,
123
+ expectBlob: req.expectBlob === true
124
+ });
125
+ }
126
+ async function requestRaw(ctx, req) {
127
+ const res = await send(ctx, req);
128
+ if (!res.ok) {
129
+ await handleResponse(res, {
130
+ expectJson: req.expectJson !== false,
131
+ expectBlob: false
132
+ });
133
+ }
134
+ return res;
135
+ }
136
+ async function send(ctx, req) {
137
+ const doFetch = ctx.fetch ?? globalThis.fetch;
138
+ if (!doFetch) {
139
+ throw new NetworkError({
140
+ message: "No fetch implementation available"
141
+ });
142
+ }
143
+ const url = buildUrl(ctx.apiUrl, req.path, req.query);
144
+ const callerSetsAuth = hasAuthorizationHeader(req.headers);
145
+ const mintedBearer = !!ctx.getToken && !callerSetsAuth;
146
+ const cookieMode = !mintedBearer && ctx.auth === "cookie";
147
+ const body = buildBody(req);
148
+ const doSend = async (authHeader) => {
149
+ const headers = {
150
+ ...authHeader ? { Authorization: authHeader } : {},
151
+ // Context defaults (e.g. User-Agent), then per-request overrides win.
152
+ ...ctx.headers ?? {},
153
+ ...req.headers ?? {}
154
+ };
155
+ if (body.contentType) headers["Content-Type"] ?? (headers["Content-Type"] = body.contentType);
156
+ try {
157
+ return await doFetch(url, {
158
+ method: req.method,
159
+ headers,
160
+ body: body.value,
161
+ signal: req.signal,
162
+ // Send the httpOnly app-key cookie ONLY in cookie mode (same-origin on
163
+ // the app subdomain). Bearer / minted-bearer omit it to avoid leaking
164
+ // ambient cookies — the minted JWT is the sole credential there.
165
+ ...cookieMode ? { credentials: "include" } : {}
166
+ });
167
+ } catch (err) {
168
+ if (err instanceof Error && err.name === "AbortError") throw err;
169
+ throw new NetworkError({
170
+ message: err instanceof Error ? err.message : "Network failure",
171
+ cause: err
172
+ });
173
+ }
174
+ };
175
+ if (mintedBearer && ctx.getToken) {
176
+ let res = await doSend(`Bearer ${await ctx.getToken()}`);
177
+ if (res.status === 401 && ctx.onUnauthorized) {
178
+ ctx.onUnauthorized();
179
+ res = await doSend(`Bearer ${await ctx.getToken()}`);
180
+ }
181
+ return res;
182
+ }
183
+ return doSend(cookieMode ? null : `Bearer ${ctx.key}`);
184
+ }
185
+ function hasAuthorizationHeader(headers) {
186
+ if (!headers) return false;
187
+ return Object.keys(headers).some((k) => k.toLowerCase() === "authorization");
188
+ }
189
+ function buildBody(req) {
190
+ if (req.bodyRaw !== void 0) return { value: req.bodyRaw };
191
+ if (req.body !== void 0) {
192
+ return {
193
+ value: JSON.stringify(req.body),
194
+ contentType: "application/json"
195
+ };
196
+ }
197
+ return { value: void 0 };
198
+ }
199
+ async function handleResponse(res, mode) {
200
+ const ct = res.headers.get("content-type") ?? "";
201
+ const isJson = ct.includes("application/json");
202
+ if (!res.ok) {
203
+ let parsed;
204
+ let message = `API error: ${res.status}`;
205
+ try {
206
+ if (isJson) {
207
+ parsed = await res.json();
208
+ if (parsed && typeof parsed === "object" && "error" in parsed && parsed.error && typeof parsed.error === "object" && "message" in parsed.error && typeof parsed.error.message === "string") {
209
+ message = parsed.error.message;
210
+ }
211
+ } else {
212
+ parsed = await res.text();
213
+ }
214
+ } catch {
215
+ }
216
+ throw errorFromStatus(res.status, message, parsed);
217
+ }
218
+ if (res.status === 204) {
219
+ return void 0;
220
+ }
221
+ try {
222
+ if (mode.expectBlob) return await res.blob();
223
+ if (mode.expectJson && isJson) return await res.json();
224
+ return await res.text();
225
+ } catch (err) {
226
+ throw new IdaptError({
227
+ code: "server_error",
228
+ message: "Invalid response body",
229
+ status: res.status,
230
+ cause: err
231
+ });
232
+ }
233
+ }
234
+ function buildUrl(apiUrl, path, query) {
235
+ const base = apiUrl.replace(/\/+$/, "");
236
+ const cleanPath = path.startsWith("/") ? path : `/${path}`;
237
+ const qs = buildQuery(query);
238
+ return base + cleanPath + qs;
239
+ }
240
+ function buildQuery(query) {
241
+ if (!query) return "";
242
+ const entries = [];
243
+ for (const [k, v] of Object.entries(query)) {
244
+ if (v === null || v === void 0) continue;
245
+ if (typeof v !== "string" && typeof v !== "number" && typeof v !== "boolean") {
246
+ continue;
247
+ }
248
+ entries.push([k, String(v)]);
249
+ }
250
+ if (entries.length === 0) return "";
251
+ const sp = new URLSearchParams(entries);
252
+ return `?${sp.toString()}`;
253
+ }
254
+
255
+ // ../sdk/src/api/_files-core.ts
256
+ async function listFiles(ctx, query = {}, opts = {}) {
257
+ const res = await request(ctx, {
258
+ method: "GET",
259
+ path: "/api/v1/drive/files",
260
+ query,
261
+ signal: opts.signal
262
+ });
263
+ return res.data ?? [];
264
+ }
265
+ async function getFileText(ctx, id, opts = {}) {
266
+ return request(ctx, {
267
+ method: "GET",
268
+ path: `/api/v1/drive/files/${id}`,
269
+ signal: opts.signal,
270
+ expectJson: false
271
+ });
272
+ }
273
+ async function getFileBlob(ctx, id, opts = {}) {
274
+ return request(ctx, {
275
+ method: "GET",
276
+ path: `/api/v1/drive/files/${id}`,
277
+ signal: opts.signal,
278
+ expectBlob: true
279
+ });
280
+ }
281
+ async function uploadFile(ctx, input, opts = {}) {
282
+ const form = new FormData();
283
+ const filename = input.name ?? (input.file instanceof globalThis.File ? input.file.name : "upload");
284
+ form.set("file", input.file, filename);
285
+ if (input.name) form.set("name", input.name);
286
+ if (input.parent_id) form.set("parent_id", input.parent_id);
287
+ if (input.workspace_id) form.set("workspace_id", input.workspace_id);
288
+ if (input.skip_if_exists) form.set("skip_if_exists", "true");
289
+ const res = await request(ctx, {
290
+ method: "POST",
291
+ path: "/api/v1/drive/files",
292
+ bodyRaw: form,
293
+ signal: opts.signal
294
+ });
295
+ return res.data;
296
+ }
297
+ async function patchFile(ctx, id, input, opts = {}) {
298
+ const body = { ...input };
299
+ if (opts.expectedUpdatedAt) body.expected_updated_at = opts.expectedUpdatedAt;
300
+ const res = await request(ctx, {
301
+ method: "PATCH",
302
+ path: `/api/v1/drive/files/${id}`,
303
+ body,
304
+ signal: opts.signal
305
+ });
306
+ return res.data;
307
+ }
308
+ async function deleteFile(ctx, id, opts = {}) {
309
+ return request(ctx, {
310
+ method: "DELETE",
311
+ path: `/api/v1/drive/files/${id}`,
312
+ signal: opts.signal
313
+ });
314
+ }
315
+ async function restoreFile(ctx, id, opts = {}) {
316
+ const res = await request(ctx, {
317
+ method: "POST",
318
+ path: `/api/v1/drive/files/${id}/restore`,
319
+ signal: opts.signal
320
+ });
321
+ return res.data;
322
+ }
323
+ async function permanentDeleteFile(ctx, id, opts = {}) {
324
+ return request(ctx, {
325
+ method: "DELETE",
326
+ path: `/api/v1/drive/files/${id}/permanent-delete`,
327
+ signal: opts.signal
328
+ });
329
+ }
330
+ async function createFolder(ctx, input, opts = {}) {
331
+ const res = await request(ctx, {
332
+ method: "POST",
333
+ path: "/api/v1/drive/files/folders",
334
+ body: input,
335
+ signal: opts.signal
336
+ });
337
+ return res.data;
338
+ }
339
+ async function moveFile(ctx, id, parentId, opts = {}) {
340
+ const res = await request(ctx, {
341
+ method: "POST",
342
+ path: `/api/v1/drive/files/${id}/move`,
343
+ body: { parent_id: parentId },
344
+ signal: opts.signal
345
+ });
346
+ return res.data;
347
+ }
348
+ async function runFile(ctx, id, input = {}, opts = {}) {
349
+ const res = await request(ctx, {
350
+ method: "POST",
351
+ path: `/api/v1/drive/files/${id}/run`,
352
+ body: input,
353
+ signal: opts.signal
354
+ });
355
+ return res.data;
356
+ }
357
+
358
+ // ../sdk/src/api/agents.ts
359
+ var AgentsApi = class {
360
+ constructor(ctx) {
361
+ this.ctx = ctx;
362
+ }
363
+ async list(query = {}, opts = {}) {
364
+ const res = await request(this.ctx, {
365
+ method: "GET",
366
+ path: "/api/v1/agents",
367
+ query,
368
+ signal: opts.signal
369
+ });
370
+ return res.data;
371
+ }
372
+ async create(input, opts = {}) {
373
+ const res = await request(this.ctx, {
374
+ method: "POST",
375
+ path: "/api/v1/agents",
376
+ body: input,
377
+ signal: opts.signal
378
+ });
379
+ return res.data;
380
+ }
381
+ async get(id, opts = {}) {
382
+ const res = await request(this.ctx, {
383
+ method: "GET",
384
+ path: `/api/v1/agents/${id}`,
385
+ signal: opts.signal
386
+ });
387
+ return res.data;
388
+ }
389
+ async update(id, input, opts = {}) {
390
+ const res = await request(this.ctx, {
391
+ method: "PATCH",
392
+ path: `/api/v1/agents/${id}`,
393
+ body: input,
394
+ signal: opts.signal
395
+ });
396
+ return res.data;
397
+ }
398
+ async delete(id, opts = {}) {
399
+ return request(this.ctx, {
400
+ method: "DELETE",
401
+ path: `/api/v1/agents/${id}`,
402
+ signal: opts.signal
403
+ });
404
+ }
405
+ async archive(id, opts = {}) {
406
+ const res = await request(this.ctx, {
407
+ method: "POST",
408
+ path: `/api/v1/agents/${id}/archive`,
409
+ signal: opts.signal
410
+ });
411
+ return res.data;
412
+ }
413
+ async unarchive(id, opts = {}) {
414
+ const res = await request(this.ctx, {
415
+ method: "POST",
416
+ path: `/api/v1/agents/${id}/unarchive`,
417
+ signal: opts.signal
418
+ });
419
+ return res.data;
420
+ }
421
+ async restore(id, opts = {}) {
422
+ const res = await request(this.ctx, {
423
+ method: "POST",
424
+ path: `/api/v1/agents/${id}/restore`,
425
+ signal: opts.signal
426
+ });
427
+ return res.data;
428
+ }
429
+ async permanentDelete(id, opts = {}) {
430
+ return request(this.ctx, {
431
+ method: "DELETE",
432
+ path: `/api/v1/agents/${id}/permanent-delete`,
433
+ signal: opts.signal
434
+ });
435
+ }
436
+ async move(id, input, opts = {}) {
437
+ const res = await request(this.ctx, {
438
+ method: "POST",
439
+ path: `/api/v1/agents/${id}/move`,
440
+ body: input,
441
+ signal: opts.signal
442
+ });
443
+ return res.data;
444
+ }
445
+ async copyToWorkspace(id, input, opts = {}) {
446
+ const res = await request(this.ctx, {
447
+ method: "POST",
448
+ path: `/api/v1/agents/${id}/copy-to-workspace`,
449
+ body: input,
450
+ signal: opts.signal
451
+ });
452
+ return res.data;
453
+ }
454
+ };
455
+
456
+ // ../sdk/src/api/api-keys.ts
457
+ var ApiKeysApi = class {
458
+ constructor(ctx) {
459
+ this.ctx = ctx;
460
+ }
461
+ async list(query = {}, opts = {}) {
462
+ const res = await request(this.ctx, {
463
+ method: "GET",
464
+ path: "/api/v1/api-keys",
465
+ query,
466
+ signal: opts.signal
467
+ });
468
+ return res.data;
469
+ }
470
+ /**
471
+ * Mint a new API key. The returned object includes a one-shot `key` field
472
+ * with the plaintext — store it immediately, there is no way to retrieve
473
+ * it later.
474
+ */
475
+ async create(input, opts = {}) {
476
+ const res = await request(this.ctx, {
477
+ method: "POST",
478
+ path: "/api/v1/api-keys",
479
+ body: input,
480
+ signal: opts.signal
481
+ });
482
+ return res.data;
483
+ }
484
+ async update(id, input, opts = {}) {
485
+ const res = await request(this.ctx, {
486
+ method: "PATCH",
487
+ path: `/api/v1/api-keys/${id}`,
488
+ body: input,
489
+ signal: opts.signal
490
+ });
491
+ return res.data;
492
+ }
493
+ /** Revoke (delete) a key. Subsequent calls with that key 401. */
494
+ async delete(id, opts = {}) {
495
+ return request(this.ctx, {
496
+ method: "DELETE",
497
+ path: `/api/v1/api-keys/${id}`,
498
+ signal: opts.signal
499
+ });
500
+ }
501
+ /**
502
+ * Rotate a key and return the replacement plaintext once. API-key
503
+ * management remains first-party session-only on the server, so calls made
504
+ * with a raw `uk_` bearer key receive 403.
505
+ */
506
+ async rotate(id, input, opts = {}) {
507
+ const res = await request(this.ctx, {
508
+ method: "POST",
509
+ path: `/api/v1/api-keys/${id}/rotate`,
510
+ body: input,
511
+ signal: opts.signal
512
+ });
513
+ return res.data;
514
+ }
515
+ };
516
+
517
+ // ../sdk/src/api/audio.ts
518
+ var AudioApi = class {
519
+ constructor(ctx) {
520
+ this.ctx = ctx;
521
+ }
522
+ /** Text-to-speech. Responds HTTP 201 with the written file ref under `data`. */
523
+ async speak(input, opts = {}) {
524
+ const res = await request(this.ctx, {
525
+ method: "POST",
526
+ path: "/api/v1/audio/speech",
527
+ body: input,
528
+ signal: opts.signal
529
+ });
530
+ return res.data;
531
+ }
532
+ async transcribe(input, opts = {}) {
533
+ const form = new FormData();
534
+ const filename = input.filename ?? (input.file instanceof File ? input.file.name : "audio");
535
+ form.set("file", input.file, filename);
536
+ if (input.model) form.set("model", input.model);
537
+ if (input.language) form.set("language", input.language);
538
+ const res = await request(this.ctx, {
539
+ method: "POST",
540
+ path: "/api/v1/audio/transcriptions",
541
+ bodyRaw: form,
542
+ signal: opts.signal
543
+ });
544
+ return res.data;
545
+ }
546
+ async *streamSpeech(input, opts = {}) {
547
+ const res = await requestRaw(this.ctx, {
548
+ method: "POST",
549
+ path: "/api/v1/audio/speech/stream",
550
+ body: input,
551
+ signal: opts.signal,
552
+ expectJson: false
553
+ });
554
+ yield* parseSpeechStream(res, opts.signal);
555
+ }
556
+ async *streamTranscribe(input, opts = {}) {
557
+ const form = new FormData();
558
+ const filename = input.filename ?? (input.file instanceof File ? input.file.name : "audio");
559
+ form.set("file", input.file, filename);
560
+ if (input.model) form.set("model", input.model);
561
+ if (input.language) form.set("language", input.language);
562
+ const res = await requestRaw(this.ctx, {
563
+ method: "POST",
564
+ path: "/api/v1/audio/transcriptions/stream",
565
+ bodyRaw: form,
566
+ signal: opts.signal,
567
+ expectJson: false
568
+ });
569
+ yield* parseTranscriptionStream(res, opts.signal);
570
+ }
571
+ };
572
+ async function* parseSpeechStream(response, signal) {
573
+ for await (const frame of parseSse(response, signal)) {
574
+ const payload = safeJson(frame.data);
575
+ if (frame.event === "chunk") {
576
+ const audio = payload?.audio;
577
+ if (typeof audio === "string") yield { type: "chunk", audio };
578
+ } else if (frame.event === "done") {
579
+ const event = { type: "done" };
580
+ const totalBytes = numberOrUndefined(payload?.totalBytes);
581
+ const durationMs = numberOrUndefined(payload?.durationMs);
582
+ const charCount = numberOrUndefined(payload?.charCount);
583
+ if (totalBytes !== void 0) event.total_bytes = totalBytes;
584
+ if (durationMs !== void 0) event.duration_ms = durationMs;
585
+ if (charCount !== void 0) event.char_count = charCount;
586
+ if (typeof payload?.cached === "boolean") event.cached = payload.cached;
587
+ yield event;
588
+ } else if (frame.event === "error") {
589
+ yield errorEvent(payload);
590
+ }
591
+ }
592
+ }
593
+ async function* parseTranscriptionStream(response, signal) {
594
+ for await (const frame of parseSse(response, signal)) {
595
+ const payload = safeJson(frame.data);
596
+ if (frame.event === "partial") {
597
+ const text = payload?.text;
598
+ if (typeof text === "string") yield { type: "partial", text };
599
+ } else if (frame.event === "final") {
600
+ const text = payload?.text;
601
+ if (typeof text === "string") yield { type: "final", text };
602
+ } else if (frame.event === "error") {
603
+ yield errorEvent(payload);
604
+ }
605
+ }
606
+ }
607
+ async function* parseSse(response, signal) {
608
+ if (!response.body) return;
609
+ const reader = response.body.getReader();
610
+ const decoder = new TextDecoder();
611
+ let buffer = "";
612
+ try {
613
+ while (true) {
614
+ if (signal?.aborted) return;
615
+ const { done, value } = await reader.read();
616
+ if (done) break;
617
+ buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
618
+ while (true) {
619
+ const boundary = buffer.indexOf("\n\n");
620
+ if (boundary === -1) break;
621
+ const block = buffer.slice(0, boundary);
622
+ buffer = buffer.slice(boundary + 2);
623
+ const parsed = parseSseBlock(block);
624
+ if (parsed) yield parsed;
625
+ }
626
+ }
627
+ } finally {
628
+ try {
629
+ reader.releaseLock();
630
+ } catch {
631
+ }
632
+ }
633
+ }
634
+ function parseSseBlock(block) {
635
+ let event = "message";
636
+ let data = "";
637
+ for (const line of block.split("\n")) {
638
+ if (!line) continue;
639
+ if (line.startsWith("event: ")) event = line.slice(7).trim();
640
+ else if (line.startsWith("event:")) event = line.slice(6).trim();
641
+ else if (line.startsWith("data: ")) data += line.slice(6);
642
+ else if (line.startsWith("data:")) data += line.slice(5);
643
+ }
644
+ if (!data && event === "message") return null;
645
+ return { event, data };
646
+ }
647
+ function safeJson(raw) {
648
+ try {
649
+ return JSON.parse(raw);
650
+ } catch {
651
+ return null;
652
+ }
653
+ }
654
+ function numberOrUndefined(value) {
655
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
656
+ }
657
+ function errorEvent(payload) {
658
+ const event = {
659
+ type: "error"
660
+ };
661
+ const status = numberOrUndefined(payload?.status);
662
+ const retryAfter = numberOrUndefined(payload?.retryAfter);
663
+ if (status !== void 0) event.status = status;
664
+ if (retryAfter !== void 0) event.retry_after = retryAfter;
665
+ return event;
666
+ }
667
+
668
+ // ../sdk/src/api/blobs.ts
669
+ var enc = encodeURIComponent;
670
+ var BlobsApi = class {
671
+ constructor(ctx) {
672
+ this.ctx = ctx;
673
+ }
674
+ /** List objects in a namespace (prefix-filterable). */
675
+ async list(namespace, opts = {}) {
676
+ const q = new URLSearchParams();
677
+ if (opts.prefix) q.set("prefix", opts.prefix);
678
+ if (opts.cursor) q.set("cursor", opts.cursor);
679
+ if (opts.limit) q.set("limit", String(opts.limit));
680
+ const qs = q.toString() ? `?${q.toString()}` : "";
681
+ const res = await request(this.ctx, {
682
+ method: "GET",
683
+ path: `/api/v1/blobs/${enc(namespace)}${qs}`,
684
+ signal: opts.signal
685
+ });
686
+ return res.data;
687
+ }
688
+ /**
689
+ * Upload (upsert) an object's bytes. Returns its metadata.
690
+ *
691
+ * Sent as `multipart/form-data` (the bytes ride a `file` part, with an
692
+ * optional `content_type` field) — the same upload convention as
693
+ * `client.files.upload`, so it flows through the one multipart transport and
694
+ * is reachable identically via `client.call("blobs put", …)`. The SDK leaves
695
+ * `Content-Type` unset so the runtime picks the multipart boundary.
696
+ */
697
+ async put(namespace, key, content, contentType, opts = {}) {
698
+ const ct = contentType ?? (content instanceof Blob && content.type ? content.type : "application/octet-stream");
699
+ const blob = content instanceof Blob ? content : new Blob([content], { type: ct });
700
+ const form = new FormData();
701
+ form.set("file", blob, key);
702
+ form.set("content_type", ct);
703
+ const res = await request(this.ctx, {
704
+ method: "POST",
705
+ path: `/api/v1/blobs/${enc(namespace)}/${enc(key)}`,
706
+ // No explicit Content-Type — the runtime sets the multipart boundary.
707
+ bodyRaw: form,
708
+ signal: opts.signal
709
+ });
710
+ return res.data;
711
+ }
712
+ /**
713
+ * Download an object's raw bytes (preserving its stored Content-Type).
714
+ *
715
+ * `GET /blobs/:ns/:key` returns JSON metadata + a signed URL by default (the
716
+ * agent/CLI form); `?download=true` is the raw-bytes escape hatch this facade
717
+ * uses. For the JSON reference instead, call `client.call("blobs get", …)`.
718
+ */
719
+ async get(namespace, key, opts = {}) {
720
+ const res = await requestRaw(this.ctx, {
721
+ method: "GET",
722
+ path: `/api/v1/blobs/${enc(namespace)}/${enc(key)}?download=true`,
723
+ signal: opts.signal,
724
+ expectJson: false
725
+ });
726
+ const blob = await res.blob();
727
+ return {
728
+ blob,
729
+ contentType: res.headers.get("content-type") ?? "application/octet-stream"
730
+ };
731
+ }
732
+ /** Read an object's metadata (no bytes). */
733
+ async head(namespace, key, opts = {}) {
734
+ const res = await request(this.ctx, {
735
+ method: "GET",
736
+ path: `/api/v1/blobs/${enc(namespace)}/${enc(key)}/meta`,
737
+ signal: opts.signal
738
+ });
739
+ return res.data;
740
+ }
741
+ /** Delete an object (idempotent). */
742
+ async delete(namespace, key, opts = {}) {
743
+ return request(this.ctx, {
744
+ method: "DELETE",
745
+ path: `/api/v1/blobs/${enc(namespace)}/${enc(key)}`,
746
+ signal: opts.signal
747
+ });
748
+ }
749
+ /** Mint a time-limited direct-download URL for an object. */
750
+ async createSignedUrl(namespace, key, expiresIn, opts = {}) {
751
+ const res = await request(this.ctx, {
752
+ method: "POST",
753
+ path: `/api/v1/blobs/${enc(namespace)}/${enc(key)}/signed-url`,
754
+ body: { expires_in: expiresIn },
755
+ signal: opts.signal
756
+ });
757
+ return res.data;
758
+ }
759
+ /** List the blob namespaces in the workspace. */
760
+ async listNamespaces(opts = {}) {
761
+ const res = await request(this.ctx, {
762
+ method: "GET",
763
+ path: "/api/v1/blobs",
764
+ signal: opts.signal
765
+ });
766
+ return res.data.map((n) => n.namespace);
767
+ }
768
+ };
769
+ function bindPath(binding, args) {
770
+ let path = binding.path;
771
+ const rest = { ...args };
772
+ for (const param of binding.pathParams) {
773
+ const value = rest[param];
774
+ if (value === void 0 || value === null || value === "") {
775
+ throw new IdaptError({
776
+ code: "invalid_request",
777
+ message: `missing required path argument \`${param}\` for \`${binding.command}\``
778
+ });
779
+ }
780
+ delete rest[param];
781
+ path = path.replace(`:${param}`, encodeURIComponent(String(value)));
782
+ }
783
+ return { path: `/api/v1${path}`, rest };
784
+ }
785
+ function buildMultipart(rest) {
786
+ const form = new FormData();
787
+ for (const [k, v] of Object.entries(rest)) {
788
+ if (v === void 0 || v === null) continue;
789
+ if (v instanceof Blob) {
790
+ const filename = v instanceof File ? v.name : k === "files" ? "files" : "file";
791
+ form.set(k, v, filename);
792
+ } else if (Array.isArray(v) && v.every((x) => x instanceof Blob)) {
793
+ for (const part of v) {
794
+ const filename = part instanceof File ? part.name : k;
795
+ form.append(k, part, filename);
796
+ }
797
+ } else if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
798
+ form.set(k, String(v));
799
+ } else {
800
+ form.set(k, JSON.stringify(v));
801
+ }
802
+ }
803
+ return form;
804
+ }
805
+ function buildRequest(binding, path, rest, signal) {
806
+ const base = { method: binding.method, path, signal };
807
+ switch (binding.argLocation) {
808
+ case "query":
809
+ return { ...base, query: rest };
810
+ case "body":
811
+ return { ...base, body: rest };
812
+ case "multipart":
813
+ return { ...base, bodyRaw: buildMultipart(rest) };
814
+ default:
815
+ return base;
816
+ }
817
+ }
818
+ async function* parseSse2(response, signal) {
819
+ if (!response.body) return;
820
+ const reader = response.body.getReader();
821
+ const decoder = new TextDecoder();
822
+ let buffer = "";
823
+ try {
824
+ while (true) {
825
+ if (signal?.aborted) return;
826
+ const { done, value } = await reader.read();
827
+ if (done) break;
828
+ buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
829
+ while (true) {
830
+ const boundary = buffer.indexOf("\n\n");
831
+ if (boundary === -1) break;
832
+ const block = buffer.slice(0, boundary);
833
+ buffer = buffer.slice(boundary + 2);
834
+ const frame = parseSseBlock2(block);
835
+ if (frame) yield frame;
836
+ }
837
+ }
838
+ } finally {
839
+ try {
840
+ reader.releaseLock();
841
+ } catch {
842
+ }
843
+ }
844
+ }
845
+ function parseSseBlock2(block) {
846
+ let event = "message";
847
+ let data = "";
848
+ for (const line of block.split("\n")) {
849
+ if (!line) continue;
850
+ if (line.startsWith("event: ")) event = line.slice(7).trim();
851
+ else if (line.startsWith("event:")) event = line.slice(6).trim();
852
+ else if (line.startsWith("data: ")) data += line.slice(6);
853
+ else if (line.startsWith("data:")) data += line.slice(5);
854
+ }
855
+ if (!data && event === "message") return null;
856
+ return { event, data };
857
+ }
858
+ function isSseBinding(binding) {
859
+ return !!binding.binaryContentTypes?.some(
860
+ (t) => t.toLowerCase().includes("text/event-stream")
861
+ );
862
+ }
863
+ async function executeCommand(binding, args = {}, ctx, opts = {}) {
864
+ const { path, rest } = bindPath(binding, args);
865
+ const req = buildRequest(binding, path, rest, opts.signal);
866
+ {
867
+ if (isSseBinding(binding) && !opts.bufferBinary) {
868
+ const res = await requestRaw(ctx, { ...req, expectJson: false });
869
+ return parseSse2(res, opts.signal);
870
+ }
871
+ return await request(ctx, { ...req, expectBlob: true });
872
+ }
873
+ }
874
+
875
+ // ../sdk/src/generated/command-bindings.generated.ts
876
+ var COMMAND_BINDINGS = {
877
+ "chat stream": {
878
+ "command": "chat stream",
879
+ "method": "POST",
880
+ "path": "/chats/:id/messages/stream",
881
+ "pathParams": [
882
+ "id"
883
+ ],
884
+ "argLocation": "body",
885
+ "binaryContentTypes": [
886
+ "text/event-stream"
887
+ ]
888
+ }};
889
+
890
+ // ../sdk/src/api/chats.ts
891
+ var ChatsApi = class {
892
+ constructor(ctx) {
893
+ this.ctx = ctx;
894
+ }
895
+ // ---------------------------------------------------------------------------
896
+ // CRUD
897
+ // ---------------------------------------------------------------------------
898
+ async list(query = {}, opts = {}) {
899
+ const res = await request(this.ctx, {
900
+ method: "GET",
901
+ path: "/api/v1/chats",
902
+ query,
903
+ signal: opts.signal
904
+ });
905
+ return res.data;
906
+ }
907
+ async create(input, opts = {}) {
908
+ const res = await request(this.ctx, {
909
+ method: "POST",
910
+ path: "/api/v1/chats",
911
+ body: input,
912
+ signal: opts.signal
913
+ });
914
+ return res.data;
915
+ }
916
+ async get(id, opts = {}) {
917
+ const res = await request(this.ctx, {
918
+ method: "GET",
919
+ path: `/api/v1/chats/${id}`,
920
+ signal: opts.signal
921
+ });
922
+ return res.data;
923
+ }
924
+ async update(id, input, opts = {}) {
925
+ const res = await request(this.ctx, {
926
+ method: "PATCH",
927
+ path: `/api/v1/chats/${id}`,
928
+ body: input,
929
+ signal: opts.signal
930
+ });
931
+ return res.data;
932
+ }
933
+ async delete(id, opts = {}) {
934
+ return request(this.ctx, {
935
+ method: "DELETE",
936
+ path: `/api/v1/chats/${id}`,
937
+ signal: opts.signal
938
+ });
939
+ }
940
+ async archive(id, opts = {}) {
941
+ const res = await request(this.ctx, {
942
+ method: "POST",
943
+ path: `/api/v1/chats/${id}/archive`,
944
+ signal: opts.signal
945
+ });
946
+ return res.data;
947
+ }
948
+ async unarchive(id, opts = {}) {
949
+ const res = await request(this.ctx, {
950
+ method: "POST",
951
+ path: `/api/v1/chats/${id}/unarchive`,
952
+ signal: opts.signal
953
+ });
954
+ return res.data;
955
+ }
956
+ async restore(id, opts = {}) {
957
+ const res = await request(this.ctx, {
958
+ method: "POST",
959
+ path: `/api/v1/chats/${id}/restore`,
960
+ signal: opts.signal
961
+ });
962
+ return res.data;
963
+ }
964
+ async permanentDelete(id, opts = {}) {
965
+ return request(this.ctx, {
966
+ method: "DELETE",
967
+ path: `/api/v1/chats/${id}/permanent-delete`,
968
+ signal: opts.signal
969
+ });
970
+ }
971
+ // ---------------------------------------------------------------------------
972
+ // Sub-resources
973
+ // ---------------------------------------------------------------------------
974
+ /** `GET /chats/:id/cost` — token usage + spend snapshot. */
975
+ async cost(id, opts = {}) {
976
+ const res = await request(this.ctx, {
977
+ method: "GET",
978
+ path: `/api/v1/chats/${id}/cost`,
979
+ signal: opts.signal
980
+ });
981
+ return res.data;
982
+ }
983
+ /** `GET /chats/:id/message-costs` - per-message and per-run cost details. */
984
+ async messageCosts(id, opts = {}) {
985
+ const res = await request(this.ctx, {
986
+ method: "GET",
987
+ path: `/api/v1/chats/${id}/message-costs`,
988
+ signal: opts.signal
989
+ });
990
+ return res.data;
991
+ }
992
+ /** `POST /chats/:id/stop` - request cancellation of the active run. */
993
+ async stop(id, input = {}, opts = {}) {
994
+ const res = await request(this.ctx, {
995
+ method: "POST",
996
+ path: `/api/v1/chats/${id}/stop`,
997
+ body: input,
998
+ signal: opts.signal
999
+ });
1000
+ return res.data;
1001
+ }
1002
+ /** `GET /chats/:id/export` - stream a transcript as a Blob. */
1003
+ async export(id, query = {}, opts = {}) {
1004
+ return request(this.ctx, {
1005
+ method: "GET",
1006
+ path: `/api/v1/chats/${id}/export`,
1007
+ query,
1008
+ expectBlob: true,
1009
+ signal: opts.signal
1010
+ });
1011
+ }
1012
+ async copyToAgent(id, input, opts = {}) {
1013
+ const res = await request(this.ctx, {
1014
+ method: "POST",
1015
+ path: `/api/v1/chats/${id}/copy-to-agent`,
1016
+ body: input,
1017
+ signal: opts.signal
1018
+ });
1019
+ return res.data;
1020
+ }
1021
+ async copyToWorkspace(id, input, opts = {}) {
1022
+ const res = await request(this.ctx, {
1023
+ method: "POST",
1024
+ path: `/api/v1/chats/${id}/copy-to-workspace`,
1025
+ body: input,
1026
+ signal: opts.signal
1027
+ });
1028
+ return res.data;
1029
+ }
1030
+ async forkToWorkspace(id, opts = {}) {
1031
+ const res = await request(this.ctx, {
1032
+ method: "POST",
1033
+ path: `/api/v1/chats/${id}/fork-to-workspace`,
1034
+ signal: opts.signal
1035
+ });
1036
+ return res.data;
1037
+ }
1038
+ /** `GET /chats/:id/messages` — paginated history. */
1039
+ async listMessages(id, query = {}, opts = {}) {
1040
+ const res = await request(this.ctx, {
1041
+ method: "GET",
1042
+ path: `/api/v1/chats/${id}/messages`,
1043
+ query,
1044
+ signal: opts.signal
1045
+ });
1046
+ return res.data;
1047
+ }
1048
+ /**
1049
+ * `POST /chats/:id/messages` — send a user message + (optionally) wait
1050
+ * for the assistant reply synchronously. Responds HTTP 201 with a
1051
+ * `oneOf`: a completed `{status, chat_id, model_id, user_message_id,
1052
+ * message}` or a pending `{status, pending_token, chat_id}`. The pending
1053
+ * handle is `pending_token` — an opaque workflow handle, NOT a
1054
+ * resourceId. See `SendMessageResult`.
1055
+ */
1056
+ async sendMessage(id, input, opts = {}) {
1057
+ return request(this.ctx, {
1058
+ method: "POST",
1059
+ path: `/api/v1/chats/${id}/messages`,
1060
+ body: input,
1061
+ signal: opts.signal
1062
+ });
1063
+ }
1064
+ /**
1065
+ * `POST /chats/:id/messages/stream` — send a user message and stream the
1066
+ * assistant reply token-by-token as Server-Sent Events. The real-time twin
1067
+ * of {@link sendMessage}: same body, but instead of one finished JSON reply
1068
+ * you get an `AsyncIterable` of raw SSE frames as the model generates.
1069
+ *
1070
+ * Each yielded `{ event, data }` has `data` as a JSON string — parse it per
1071
+ * the frame's `event`:
1072
+ * - `token` → `{ chat_id, message_id, text, reasoning? }` (`text` is the
1073
+ * FULL accumulated snapshot — render by REPLACING, not
1074
+ * appending)
1075
+ * - `message` → `{ chat_id, message_id, role }` (assistant row finalized)
1076
+ * - `done` → `{ chat_id, message_id, status }` (terminal; loop ends)
1077
+ * - `heartbeat` → keep-alive ping, ignore.
1078
+ *
1079
+ * for await (const frame of client.chats.stream(chatId, { content })) {
1080
+ * if (frame.event === "token") {
1081
+ * const { text } = JSON.parse(frame.data);
1082
+ * render(text);
1083
+ * }
1084
+ * }
1085
+ *
1086
+ * Delegates to the generic spec-driven executor, which turns the
1087
+ * `text/event-stream` binary binding into the async iterable. Equivalent to
1088
+ * `client.call("chat stream", input)`.
1089
+ */
1090
+ stream(id, input, opts = {}) {
1091
+ return executeCommand(
1092
+ COMMAND_BINDINGS["chat stream"],
1093
+ { id, ...input },
1094
+ this.ctx,
1095
+ opts
1096
+ );
1097
+ }
1098
+ /** `GET /chats/:id/runs` — model execution runs tied to this chat. */
1099
+ async listRuns(id, query = {}, opts = {}) {
1100
+ const res = await request(this.ctx, {
1101
+ method: "GET",
1102
+ path: `/api/v1/chats/${id}/runs`,
1103
+ query,
1104
+ signal: opts.signal
1105
+ });
1106
+ return res.data;
1107
+ }
1108
+ /**
1109
+ * `POST /chats/:id/messages/:message_id/reprompt` — regenerate the
1110
+ * assistant response to an existing user message. The original AI reply
1111
+ * is preserved; a sibling assistant message is created so branching
1112
+ * stays intact.
1113
+ *
1114
+ * Before: [User A] → [AI B]
1115
+ * After: [User A] ─┬─ [AI B]
1116
+ * └─ [AI C] ← C.prevMessageId = A
1117
+ *
1118
+ * With `wait=true` (default) the server blocks until the new sibling is
1119
+ * available; with `wait=false` returns `{ status: "pending", ... }` and
1120
+ * the new message can be polled via `listMessages`.
1121
+ */
1122
+ async repromptMessage(chatId, messageId, input = {}, opts = {}) {
1123
+ const res = await request(this.ctx, {
1124
+ method: "POST",
1125
+ path: `/api/v1/chats/${chatId}/messages/${messageId}/reprompt`,
1126
+ body: input,
1127
+ signal: opts.signal
1128
+ });
1129
+ return res.data;
1130
+ }
1131
+ };
1132
+
1133
+ // ../sdk/src/api/code.ts
1134
+ var CodeRunsApi = class {
1135
+ constructor(ctx) {
1136
+ this.ctx = ctx;
1137
+ }
1138
+ /**
1139
+ * Execute a code file. Returns the full `ExecutionRun` row — `id`,
1140
+ * `status`, `stdout`, `stderr`, `exit_code`, and the run timestamps —
1141
+ * the same shape as `GET /code-runs/:id`.
1142
+ */
1143
+ async run(input, opts = {}) {
1144
+ const res = await request(this.ctx, {
1145
+ method: "POST",
1146
+ path: "/api/v1/code-runs",
1147
+ body: input,
1148
+ signal: opts.signal
1149
+ });
1150
+ return res.data;
1151
+ }
1152
+ /** List recent execution runs. */
1153
+ async list(query = {}, opts = {}) {
1154
+ const res = await request(this.ctx, {
1155
+ method: "GET",
1156
+ path: "/api/v1/code-runs",
1157
+ query,
1158
+ signal: opts.signal
1159
+ });
1160
+ return res.data;
1161
+ }
1162
+ /** Get a single execution run by id. */
1163
+ async get(id, opts = {}) {
1164
+ const res = await request(this.ctx, {
1165
+ method: "GET",
1166
+ path: `/api/v1/code-runs/${id}`,
1167
+ signal: opts.signal
1168
+ });
1169
+ return res.data;
1170
+ }
1171
+ };
1172
+
1173
+ // ../sdk/src/api/computers.ts
1174
+ var ComputersApi = class {
1175
+ constructor(ctx) {
1176
+ this.ctx = ctx;
1177
+ }
1178
+ // ---------------------------------------------------------------------------
1179
+ // CRUD
1180
+ // ---------------------------------------------------------------------------
1181
+ async list(query, opts = {}) {
1182
+ const res = await request(this.ctx, {
1183
+ method: "GET",
1184
+ path: "/api/v1/computers",
1185
+ query,
1186
+ signal: opts.signal
1187
+ });
1188
+ return res.data;
1189
+ }
1190
+ async create(input, opts = {}) {
1191
+ const res = await request(this.ctx, {
1192
+ method: "POST",
1193
+ path: "/api/v1/computers/pair-tokens",
1194
+ body: input,
1195
+ signal: opts.signal
1196
+ });
1197
+ return res.data;
1198
+ }
1199
+ async get(id, opts = {}) {
1200
+ const res = await request(this.ctx, {
1201
+ method: "GET",
1202
+ path: `/api/v1/computers/${id}`,
1203
+ signal: opts.signal
1204
+ });
1205
+ return res.data;
1206
+ }
1207
+ async update(id, input, opts = {}) {
1208
+ const res = await request(this.ctx, {
1209
+ method: "PATCH",
1210
+ path: `/api/v1/computers/${id}`,
1211
+ body: input,
1212
+ signal: opts.signal
1213
+ });
1214
+ return res.data;
1215
+ }
1216
+ async delete(id, opts = {}) {
1217
+ return request(this.ctx, {
1218
+ method: "DELETE",
1219
+ path: `/api/v1/computers/${id}`,
1220
+ signal: opts.signal
1221
+ });
1222
+ }
1223
+ // ---------------------------------------------------------------------------
1224
+ // Lifecycle
1225
+ // ---------------------------------------------------------------------------
1226
+ async archive(id, opts = {}) {
1227
+ const res = await request(this.ctx, {
1228
+ method: "POST",
1229
+ path: `/api/v1/computers/${id}/archive`,
1230
+ signal: opts.signal
1231
+ });
1232
+ return res.data;
1233
+ }
1234
+ async unarchive(id, opts = {}) {
1235
+ const res = await request(this.ctx, {
1236
+ method: "POST",
1237
+ path: `/api/v1/computers/${id}/unarchive`,
1238
+ signal: opts.signal
1239
+ });
1240
+ return res.data;
1241
+ }
1242
+ /** Wake a hibernated cloud computer. Errors with 409 from non-hibernated states. */
1243
+ async start(id, opts = {}) {
1244
+ const res = await request(this.ctx, {
1245
+ method: "POST",
1246
+ path: `/api/v1/computers/${id}/start`,
1247
+ signal: opts.signal
1248
+ });
1249
+ return res.data;
1250
+ }
1251
+ /** Stop a running cloud computer; preserves provider-local disk state. */
1252
+ async stop(id, opts = {}) {
1253
+ const res = await request(this.ctx, {
1254
+ method: "POST",
1255
+ path: `/api/v1/computers/${id}/stop`,
1256
+ signal: opts.signal
1257
+ });
1258
+ return res.data;
1259
+ }
1260
+ /** Hibernate a running cloud computer; deallocates compute, snapshots state. */
1261
+ async hibernate(id, opts = {}) {
1262
+ const res = await request(this.ctx, {
1263
+ method: "POST",
1264
+ path: `/api/v1/computers/${id}/hibernate`,
1265
+ signal: opts.signal
1266
+ });
1267
+ return res.data;
1268
+ }
1269
+ /**
1270
+ * Daemon connectivity probe; returns a success flag + duration.
1271
+ * Rate-limited per computer. `serverInfo` is a structured
1272
+ * `{version, uptime_seconds}` object (was a free-form blob).
1273
+ */
1274
+ async testConnection(id, opts = {}) {
1275
+ const res = await request(this.ctx, {
1276
+ method: "POST",
1277
+ path: `/api/v1/computers/${id}/test-connection`,
1278
+ signal: opts.signal
1279
+ });
1280
+ return res.data;
1281
+ }
1282
+ // ---------------------------------------------------------------------------
1283
+ // Exec & tmux
1284
+ // ---------------------------------------------------------------------------
1285
+ async exec(id, input, opts = {}) {
1286
+ const res = await request(this.ctx, {
1287
+ method: "POST",
1288
+ path: `/api/v1/computers/${id}/exec`,
1289
+ body: input,
1290
+ signal: opts.signal
1291
+ });
1292
+ return res.data;
1293
+ }
1294
+ /**
1295
+ * tmux dispatcher. Each `op` returns a different shape — see the route
1296
+ * docs for the per-op payload. Common case: `op: "run"` starts a window,
1297
+ * `op: "capture"` reads its output.
1298
+ */
1299
+ async tmux(id, input, opts = {}) {
1300
+ const res = await request(
1301
+ this.ctx,
1302
+ {
1303
+ method: "POST",
1304
+ path: `/api/v1/computers/${id}/tmux`,
1305
+ body: input,
1306
+ signal: opts.signal
1307
+ }
1308
+ );
1309
+ return res.data;
1310
+ }
1311
+ /** Convenience over `tmux({ op: "list" })`. */
1312
+ async listTmuxWindows(id, opts = {}) {
1313
+ const result = await this.tmux(id, { op: "list" }, opts);
1314
+ return result.windows ?? [];
1315
+ }
1316
+ // ---------------------------------------------------------------------------
1317
+ // SFTP
1318
+ // ---------------------------------------------------------------------------
1319
+ /**
1320
+ * Unified SFTP dispatcher. Returns op-shaped payload. For streaming
1321
+ * uploads/downloads use `sftpUpload` / `sftpDownload`.
1322
+ */
1323
+ async sftp(id, input, opts = {}) {
1324
+ const res = await request(
1325
+ this.ctx,
1326
+ {
1327
+ method: "POST",
1328
+ path: `/api/v1/computers/${id}/sftp`,
1329
+ body: input,
1330
+ signal: opts.signal
1331
+ }
1332
+ );
1333
+ return res.data;
1334
+ }
1335
+ /** Convenience: `sftp({op:"list"})` returning the entries array directly. */
1336
+ async sftpList(id, path, opts = {}) {
1337
+ const result = await this.sftp(id, { op: "list", path }, opts);
1338
+ return result;
1339
+ }
1340
+ /**
1341
+ * Multipart upload of a file/folder member. Falls back to single-file mode
1342
+ * when `relative_path` is omitted.
1343
+ */
1344
+ async sftpUpload(id, input, opts = {}) {
1345
+ const form = new FormData();
1346
+ form.append("file", input.file);
1347
+ form.append("path", input.path);
1348
+ if (input.relative_path) form.append("relative_path", input.relative_path);
1349
+ if (input.conflict_mode) form.append("conflict_mode", input.conflict_mode);
1350
+ const res = await request(
1351
+ this.ctx,
1352
+ {
1353
+ method: "POST",
1354
+ path: `/api/v1/computers/${id}/sftp/upload`,
1355
+ bodyRaw: form,
1356
+ signal: opts.signal
1357
+ }
1358
+ );
1359
+ return res.data;
1360
+ }
1361
+ /**
1362
+ * Stream a remote file (or a folder as `tar.gz`) as a `Blob`. The server
1363
+ * sends the real MIME type on `Content-Type`.
1364
+ */
1365
+ async sftpDownload(id, remotePath, opts = {}) {
1366
+ return request(this.ctx, {
1367
+ method: "GET",
1368
+ path: `/api/v1/computers/${id}/sftp/download`,
1369
+ query: { path: remotePath },
1370
+ expectBlob: true,
1371
+ signal: opts.signal
1372
+ });
1373
+ }
1374
+ // ---------------------------------------------------------------------------
1375
+ // Ports (managed only)
1376
+ // ---------------------------------------------------------------------------
1377
+ async listPorts(id, query = {}, opts = {}) {
1378
+ const res = await request(this.ctx, {
1379
+ method: "GET",
1380
+ path: `/api/v1/computers/${id}/ports`,
1381
+ query: query.refresh ? { refresh: "true" } : void 0,
1382
+ signal: opts.signal
1383
+ });
1384
+ return res.data;
1385
+ }
1386
+ async updatePort(id, input, opts = {}) {
1387
+ const res = await request(this.ctx, {
1388
+ method: "PATCH",
1389
+ path: `/api/v1/computers/${id}/ports`,
1390
+ body: input,
1391
+ signal: opts.signal
1392
+ });
1393
+ return res.data;
1394
+ }
1395
+ // ---------------------------------------------------------------------------
1396
+ // Unix users
1397
+ // ---------------------------------------------------------------------------
1398
+ /**
1399
+ * List the computer's Unix users. The v1 contract returns a list envelope
1400
+ * — `data` is the user array, with `current_user`, `has_root_access` and
1401
+ * `has_sudo_access` as sibling top-level keys. This convenience method
1402
+ * returns just the array; use `listUsersWithMeta` for the access flags.
1403
+ */
1404
+ async listUsers(id, opts = {}) {
1405
+ return (await this.listUsersWithMeta(id, opts)).data;
1406
+ }
1407
+ /**
1408
+ * Like `listUsers` but returns the full v1 list envelope, including the
1409
+ * `current_user` daemon user and the `has_root_access` / `has_sudo_access`
1410
+ * capability flags.
1411
+ */
1412
+ async listUsersWithMeta(id, opts = {}) {
1413
+ return request(this.ctx, {
1414
+ method: "GET",
1415
+ path: `/api/v1/computers/${id}/users`,
1416
+ signal: opts.signal
1417
+ });
1418
+ }
1419
+ /** Fetch one Unix user by username (`GET /v1/computers/{id}/users/{username}`). */
1420
+ async getUser(id, username, opts = {}) {
1421
+ const res = await request(this.ctx, {
1422
+ method: "GET",
1423
+ path: `/api/v1/computers/${id}/users/${username}`,
1424
+ signal: opts.signal
1425
+ });
1426
+ return res.data;
1427
+ }
1428
+ /**
1429
+ * Create a Unix user. The user fields are returned directly under `data`.
1430
+ * Responds 201 on creation, 200 when the user already existed.
1431
+ */
1432
+ async createUser(id, input, opts = {}) {
1433
+ const res = await request(this.ctx, {
1434
+ method: "POST",
1435
+ path: `/api/v1/computers/${id}/users`,
1436
+ body: input,
1437
+ signal: opts.signal
1438
+ });
1439
+ return res.data;
1440
+ }
1441
+ /** Update a Unix user. The user fields are returned directly under `data`. */
1442
+ async updateUser(id, username, input, opts = {}) {
1443
+ const res = await request(this.ctx, {
1444
+ method: "PATCH",
1445
+ path: `/api/v1/computers/${id}/users/${username}`,
1446
+ body: input,
1447
+ signal: opts.signal
1448
+ });
1449
+ return res.data;
1450
+ }
1451
+ async deleteUser(id, username, opts = {}) {
1452
+ return request(this.ctx, {
1453
+ method: "DELETE",
1454
+ path: `/api/v1/computers/${id}/users/${username}`,
1455
+ signal: opts.signal
1456
+ });
1457
+ }
1458
+ // ---------------------------------------------------------------------------
1459
+ // User env vars
1460
+ // ---------------------------------------------------------------------------
1461
+ async listUserEnvVars(id, username, opts = {}) {
1462
+ const res = await request(this.ctx, {
1463
+ method: "GET",
1464
+ path: `/api/v1/computers/${id}/users/${username}/env`,
1465
+ signal: opts.signal
1466
+ });
1467
+ return res.data;
1468
+ }
1469
+ async createUserEnvVar(id, username, input, opts = {}) {
1470
+ const res = await request(this.ctx, {
1471
+ method: "POST",
1472
+ path: `/api/v1/computers/${id}/users/${username}/env`,
1473
+ body: input,
1474
+ signal: opts.signal
1475
+ });
1476
+ return res.data;
1477
+ }
1478
+ /**
1479
+ * Remove an env-var binding from a user. A binding is addressed by its
1480
+ * env-var `name` (the route segment is `{name}`, not a UUID).
1481
+ */
1482
+ async deleteUserEnvVar(id, username, name, opts = {}) {
1483
+ return request(this.ctx, {
1484
+ method: "DELETE",
1485
+ path: `/api/v1/computers/${id}/users/${username}/env/${name}`,
1486
+ signal: opts.signal
1487
+ });
1488
+ }
1489
+ /** Bootstrap `~/.idapt-env` + `.bashrc` sourcing on a user. Idempotent. */
1490
+ async setupUserEnv(id, username, opts = {}) {
1491
+ const res = await request(
1492
+ this.ctx,
1493
+ {
1494
+ method: "POST",
1495
+ path: `/api/v1/computers/${id}/users/${username}/env/setup`,
1496
+ signal: opts.signal
1497
+ }
1498
+ );
1499
+ return res.data;
1500
+ }
1501
+ async checkUserEnvSetup(id, username, opts = {}) {
1502
+ const res = await request(
1503
+ this.ctx,
1504
+ {
1505
+ method: "GET",
1506
+ path: `/api/v1/computers/${id}/users/${username}/env/setup`,
1507
+ signal: opts.signal
1508
+ }
1509
+ );
1510
+ return res.data;
1511
+ }
1512
+ /** Rewrite the computer's `~/.idapt-env` from the DB-recorded bindings. */
1513
+ async syncUserEnv(id, username, opts = {}) {
1514
+ const res = await request(
1515
+ this.ctx,
1516
+ {
1517
+ method: "POST",
1518
+ path: `/api/v1/computers/${id}/users/${username}/env/sync`,
1519
+ signal: opts.signal
1520
+ }
1521
+ );
1522
+ return res.data;
1523
+ }
1524
+ async checkUserEnvSync(id, username, opts = {}) {
1525
+ const res = await request(
1526
+ this.ctx,
1527
+ {
1528
+ method: "GET",
1529
+ path: `/api/v1/computers/${id}/users/${username}/env/sync`,
1530
+ signal: opts.signal
1531
+ }
1532
+ );
1533
+ return res.data;
1534
+ }
1535
+ // ---------------------------------------------------------------------------
1536
+ // Workspace links (share LOCAL INFERENCE into other workspaces)
1537
+ // ---------------------------------------------------------------------------
1538
+ /**
1539
+ * List the workspaces a computer's LOCAL INFERENCE is shared into. The home
1540
+ * workspace is never listed (it already has full access). The route returns
1541
+ * the links under a `links` key (not a standard list envelope).
1542
+ */
1543
+ async listWorkspaceLinks(id, opts = {}) {
1544
+ const res = await request(this.ctx, {
1545
+ method: "GET",
1546
+ path: `/api/v1/computers/${id}/workspace-links`,
1547
+ signal: opts.signal
1548
+ });
1549
+ return res.data.links;
1550
+ }
1551
+ /**
1552
+ * Share a computer's LOCAL INFERENCE into another workspace (never shell /
1553
+ * files / tunnels). `workspaceId` is the target workspace's `ws_…`
1554
+ * resourceId or slug. Requires EDIT on the computer's home workspace and
1555
+ * membership of the target. Rejects the home workspace and duplicates.
1556
+ */
1557
+ async linkWorkspace(id, workspaceId, opts = {}) {
1558
+ const res = await request(
1559
+ this.ctx,
1560
+ {
1561
+ method: "POST",
1562
+ path: `/api/v1/computers/${id}/workspace-links`,
1563
+ body: { workspace_id: workspaceId },
1564
+ signal: opts.signal
1565
+ }
1566
+ );
1567
+ return res.data.link;
1568
+ }
1569
+ /**
1570
+ * Stop sharing a computer's local inference into a workspace. `workspaceId`
1571
+ * is the target workspace's `ws_…` resourceId. Idempotent — unlinking a
1572
+ * non-existent link still succeeds.
1573
+ */
1574
+ async unlinkWorkspace(id, workspaceId, opts = {}) {
1575
+ const res = await request(this.ctx, {
1576
+ method: "DELETE",
1577
+ path: `/api/v1/computers/${id}/workspace-links/${workspaceId}`,
1578
+ signal: opts.signal
1579
+ });
1580
+ return res.data;
1581
+ }
1582
+ // ---------------------------------------------------------------------------
1583
+ // Pair-token redemption (daemon bootstrap)
1584
+ // ---------------------------------------------------------------------------
1585
+ /**
1586
+ * Redeem a one-time pair token for a long-lived computer identity. The
1587
+ * token itself is the auth — we swap the bearer for the token on this
1588
+ * call only (same pattern as `triggers.fire`).
1589
+ *
1590
+ * Used by `idapt up --token` to bootstrap a daemon. The request body is
1591
+ * snake_case; the response is wrapped in the standard `{data:{…}}`
1592
+ * envelope. `computer_id` is a resourceId.
1593
+ */
1594
+ async pair(input, opts = {}) {
1595
+ const localCtx = {
1596
+ apiUrl: this.ctx.apiUrl,
1597
+ key: input.token,
1598
+ fetch: this.ctx.fetch
1599
+ };
1600
+ const res = await request(localCtx, {
1601
+ method: "POST",
1602
+ path: "/api/v1/computers/pair",
1603
+ body: input,
1604
+ signal: opts.signal
1605
+ });
1606
+ return res.data;
1607
+ }
1608
+ };
1609
+
1610
+ // ../sdk/src/api/datastore.ts
1611
+ var enc2 = encodeURIComponent;
1612
+ var DatastoreApi = class {
1613
+ constructor(ctx) {
1614
+ this.ctx = ctx;
1615
+ }
1616
+ /** List keys in a namespace (prefix-filterable). */
1617
+ async list(namespace, opts = {}) {
1618
+ const q = new URLSearchParams();
1619
+ if (opts.prefix) q.set("prefix", opts.prefix);
1620
+ if (opts.cursor) q.set("cursor", opts.cursor);
1621
+ if (opts.limit) q.set("limit", String(opts.limit));
1622
+ const qs = q.toString() ? `?${q.toString()}` : "";
1623
+ const res = await request(this.ctx, {
1624
+ method: "GET",
1625
+ path: `/api/v1/datastore/${enc2(namespace)}${qs}`,
1626
+ signal: opts.signal
1627
+ });
1628
+ return res.data;
1629
+ }
1630
+ /** Read a KV entry. */
1631
+ async get(namespace, key, opts = {}) {
1632
+ const res = await request(this.ctx, {
1633
+ method: "GET",
1634
+ path: `/api/v1/datastore/${enc2(namespace)}/${enc2(key)}`,
1635
+ signal: opts.signal
1636
+ });
1637
+ return res.data;
1638
+ }
1639
+ /** Upsert a KV entry (optionally with a TTL). */
1640
+ async set(namespace, key, value, opts = {}) {
1641
+ const res = await request(this.ctx, {
1642
+ method: "POST",
1643
+ path: `/api/v1/datastore/${enc2(namespace)}/${enc2(key)}`,
1644
+ body: { value, ttl_seconds: opts.ttlSeconds },
1645
+ signal: opts.signal
1646
+ });
1647
+ return res.data;
1648
+ }
1649
+ /** Delete a KV entry (idempotent). */
1650
+ async delete(namespace, key, opts = {}) {
1651
+ return request(this.ctx, {
1652
+ method: "DELETE",
1653
+ path: `/api/v1/datastore/${enc2(namespace)}/${enc2(key)}`,
1654
+ signal: opts.signal
1655
+ });
1656
+ }
1657
+ /** Atomically add `by` (default 1) to a numeric counter. */
1658
+ async increment(namespace, key, by = 1, opts = {}) {
1659
+ const res = await request(
1660
+ this.ctx,
1661
+ {
1662
+ method: "POST",
1663
+ path: `/api/v1/datastore/${enc2(namespace)}/${enc2(key)}/increment`,
1664
+ body: { by },
1665
+ signal: opts.signal
1666
+ }
1667
+ );
1668
+ return res.data;
1669
+ }
1670
+ /** List the namespaces in the workspace. */
1671
+ async listNamespaces(opts = {}) {
1672
+ const res = await request(this.ctx, {
1673
+ method: "GET",
1674
+ path: "/api/v1/datastore",
1675
+ signal: opts.signal
1676
+ });
1677
+ return res.data.map((n) => n.namespace);
1678
+ }
1679
+ /** Read several keys at once. */
1680
+ async batchGet(namespace, keys, opts = {}) {
1681
+ const res = await request(
1682
+ this.ctx,
1683
+ {
1684
+ method: "POST",
1685
+ path: `/api/v1/datastore/${enc2(namespace)}`,
1686
+ body: { op: "get", keys },
1687
+ signal: opts.signal
1688
+ }
1689
+ );
1690
+ return res.data.entries ?? [];
1691
+ }
1692
+ /** Upsert several entries at once. */
1693
+ async batchSet(namespace, entries, opts = {}) {
1694
+ const res = await request(this.ctx, {
1695
+ method: "POST",
1696
+ path: `/api/v1/datastore/${enc2(namespace)}`,
1697
+ body: {
1698
+ op: "set",
1699
+ entries: entries.map((e) => ({
1700
+ key: e.key,
1701
+ value: e.value,
1702
+ ttl_seconds: e.ttlSeconds
1703
+ }))
1704
+ },
1705
+ signal: opts.signal
1706
+ });
1707
+ return res.data.count ?? 0;
1708
+ }
1709
+ /** Delete several keys at once; returns the count removed. */
1710
+ async batchDelete(namespace, keys, opts = {}) {
1711
+ const res = await request(this.ctx, {
1712
+ method: "POST",
1713
+ path: `/api/v1/datastore/${enc2(namespace)}`,
1714
+ body: { op: "delete", keys },
1715
+ signal: opts.signal
1716
+ });
1717
+ return res.data.count ?? 0;
1718
+ }
1719
+ };
1720
+
1721
+ // ../sdk/src/api/docs.ts
1722
+ var DocsApi = class {
1723
+ constructor(ctx) {
1724
+ this.ctx = ctx;
1725
+ }
1726
+ /**
1727
+ * Fetch the OpenAPI spec as a plain object. Shape is `OpenAPI.Document`
1728
+ * from the standard spec — typed as `Record<string, unknown>` here so
1729
+ * consumers can narrow as needed without pulling in an openapi-types
1730
+ * dependency.
1731
+ */
1732
+ async get(opts = {}) {
1733
+ return request(this.ctx, {
1734
+ method: "GET",
1735
+ path: "/api/v1/docs",
1736
+ signal: opts.signal
1737
+ });
1738
+ }
1739
+ };
1740
+
1741
+ // ../sdk/src/api/files.ts
1742
+ var FilesApi = class {
1743
+ constructor(ctx) {
1744
+ this.ctx = ctx;
1745
+ }
1746
+ // ---------------------------------------------------------------------------
1747
+ // CRUD
1748
+ // ---------------------------------------------------------------------------
1749
+ list(query = {}, opts = {}) {
1750
+ return listFiles(this.ctx, query, opts);
1751
+ }
1752
+ /** Multipart upload a Blob/File. Returns the partial file record. */
1753
+ upload(input, opts = {}) {
1754
+ return uploadFile(this.ctx, input, opts);
1755
+ }
1756
+ /**
1757
+ * Fetch a file's raw text content. Use for text-like files (source code,
1758
+ * Markdown, JSON strings). For binary content (images, PDFs, zips) use
1759
+ * `getBlob()` instead — `getText()` will UTF-8 decode and corrupt bytes.
1760
+ */
1761
+ getText(id, opts = {}) {
1762
+ return getFileText(this.ctx, id, opts);
1763
+ }
1764
+ /**
1765
+ * Fetch a file's raw bytes as a Blob. The server sends the real MIME
1766
+ * type on `Content-Type`; the returned Blob's `.type` reflects it.
1767
+ */
1768
+ getBlob(id, opts = {}) {
1769
+ return getFileBlob(this.ctx, id, opts);
1770
+ }
1771
+ /**
1772
+ * Patch file metadata or content. Supports `expectedUpdatedAt` for
1773
+ * optimistic concurrency — pass it when you know the version you read.
1774
+ */
1775
+ patch(id, input, opts = {}) {
1776
+ return patchFile(this.ctx, id, input, opts);
1777
+ }
1778
+ delete(id, opts = {}) {
1779
+ return deleteFile(this.ctx, id, opts);
1780
+ }
1781
+ restore(id, opts = {}) {
1782
+ return restoreFile(this.ctx, id, opts);
1783
+ }
1784
+ permanentDelete(id, opts = {}) {
1785
+ return permanentDeleteFile(this.ctx, id, opts);
1786
+ }
1787
+ // ---------------------------------------------------------------------------
1788
+ // Folder / move / run
1789
+ // ---------------------------------------------------------------------------
1790
+ /**
1791
+ * Idempotent create-or-get a folder. Returns the existing folder (with
1792
+ * `200`) or the new one (`201`) — consumers don't need to distinguish.
1793
+ */
1794
+ createFolder(input, opts = {}) {
1795
+ return createFolder(this.ctx, input, opts);
1796
+ }
1797
+ move(id, parentId, opts = {}) {
1798
+ return moveFile(this.ctx, id, parentId, opts);
1799
+ }
1800
+ /**
1801
+ * Execute a code file (js/ts/py/sh) in a sandboxed Lambda. Returns the
1802
+ * full `ExecutionRun` record (status, stdout/stderr, exit code, timestamps).
1803
+ */
1804
+ run(id, input = {}, opts = {}) {
1805
+ return runFile(this.ctx, id, input, opts);
1806
+ }
1807
+ };
1808
+
1809
+ // ../sdk/src/api/guide.ts
1810
+ var GuideApi = class {
1811
+ constructor(ctx) {
1812
+ this.ctx = ctx;
1813
+ }
1814
+ /**
1815
+ * Fetch the SKILL.md document. Returns `{ content, format }` — the route
1816
+ * responds with a JSON envelope, not raw text.
1817
+ */
1818
+ async get(opts = {}) {
1819
+ const res = await request(this.ctx, {
1820
+ method: "GET",
1821
+ path: "/api/v1/guide",
1822
+ signal: opts.signal
1823
+ });
1824
+ return res.data;
1825
+ }
1826
+ };
1827
+
1828
+ // ../sdk/src/api/images.ts
1829
+ var ImagesApi = class {
1830
+ constructor(ctx) {
1831
+ this.ctx = ctx;
1832
+ }
1833
+ async listModels(opts = {}) {
1834
+ const res = await request(this.ctx, {
1835
+ method: "GET",
1836
+ path: "/api/v1/images/models",
1837
+ signal: opts.signal
1838
+ });
1839
+ return res.data;
1840
+ }
1841
+ /** Generate an image. Responds HTTP 201 with the written file ref under `data`. */
1842
+ async generate(input, opts = {}) {
1843
+ const res = await request(this.ctx, {
1844
+ method: "POST",
1845
+ path: "/api/v1/images/generations",
1846
+ body: input,
1847
+ signal: opts.signal
1848
+ });
1849
+ return res.data;
1850
+ }
1851
+ };
1852
+
1853
+ // ../sdk/src/api/models.ts
1854
+ var ModelsApi = class {
1855
+ constructor(ctx) {
1856
+ this.ctx = ctx;
1857
+ }
1858
+ async list(opts = {}) {
1859
+ const res = await request(this.ctx, {
1860
+ method: "GET",
1861
+ path: "/api/v1/models",
1862
+ signal: opts.signal
1863
+ });
1864
+ return res.data;
1865
+ }
1866
+ };
1867
+
1868
+ // ../sdk/src/api/notifications.ts
1869
+ var NotificationsApi = class {
1870
+ constructor(ctx) {
1871
+ this.ctx = ctx;
1872
+ }
1873
+ // ---------------------------------------------------------------------------
1874
+ // Per-row verbs
1875
+ // ---------------------------------------------------------------------------
1876
+ /**
1877
+ * List notification rows. Returns the `data` array directly; the
1878
+ * unfiltered unread tally lives at the TOP LEVEL of the wire envelope
1879
+ * (`unread_count`, a sibling of `data`/`pagination`) — use `listWithMeta`
1880
+ * when you need it.
1881
+ */
1882
+ async list(query = {}, opts = {}) {
1883
+ const res = await this.listWithMeta(query, opts);
1884
+ return res.data;
1885
+ }
1886
+ /**
1887
+ * Like `list()` but returns the full envelope so callers can read the
1888
+ * top-level `unread_count` (e.g. to drive an unread badge).
1889
+ */
1890
+ async listWithMeta(query = {}, opts = {}) {
1891
+ return request(
1892
+ this.ctx,
1893
+ {
1894
+ method: "GET",
1895
+ path: "/api/v1/notifications",
1896
+ query,
1897
+ signal: opts.signal
1898
+ }
1899
+ );
1900
+ }
1901
+ async get(id, opts = {}) {
1902
+ const res = await request(this.ctx, {
1903
+ method: "GET",
1904
+ path: `/api/v1/notifications/${id}`,
1905
+ signal: opts.signal
1906
+ });
1907
+ return res.data;
1908
+ }
1909
+ /**
1910
+ * PATCH a single recipient row. The body accepts only the `is_read` and
1911
+ * `archived` booleans; at least one must be supplied (422 otherwise).
1912
+ * Returns the full updated `Notification`.
1913
+ */
1914
+ async update(id, input, opts = {}) {
1915
+ const res = await request(this.ctx, {
1916
+ method: "PATCH",
1917
+ path: `/api/v1/notifications/${id}`,
1918
+ body: input,
1919
+ signal: opts.signal
1920
+ });
1921
+ return res.data;
1922
+ }
1923
+ /** Shorthand for `update(id, { is_read: true })`. */
1924
+ markRead(id, opts = {}) {
1925
+ return this.update(id, { is_read: true }, opts);
1926
+ }
1927
+ /** Shorthand for `update(id, { archived: true })`. */
1928
+ archive(id, opts = {}) {
1929
+ return this.update(id, { archived: true }, opts);
1930
+ }
1931
+ /** Shorthand for `update(id, { archived: false })`. */
1932
+ unarchive(id, opts = {}) {
1933
+ return this.update(id, { archived: false }, opts);
1934
+ }
1935
+ async delete(id, opts = {}) {
1936
+ return request(this.ctx, {
1937
+ method: "DELETE",
1938
+ path: `/api/v1/notifications/${id}`,
1939
+ signal: opts.signal
1940
+ });
1941
+ }
1942
+ // ---------------------------------------------------------------------------
1943
+ // Bulk
1944
+ // ---------------------------------------------------------------------------
1945
+ /**
1946
+ * Mark every unread, non-archived, non-deleted notification as read.
1947
+ * Resolves once the bulk update lands — the v1 response is an empty
1948
+ * `{ data: {} }` envelope.
1949
+ */
1950
+ async readAll(opts = {}) {
1951
+ await request(this.ctx, {
1952
+ method: "POST",
1953
+ path: "/api/v1/notifications/read-all",
1954
+ signal: opts.signal
1955
+ });
1956
+ }
1957
+ // ---------------------------------------------------------------------------
1958
+ // Send (admin / owner)
1959
+ // ---------------------------------------------------------------------------
1960
+ /**
1961
+ * Send a notification to workspace members. The caller must be an admin/owner
1962
+ * of the workspace; `recipient_ids` (profile resourceIds) and the broadcast
1963
+ * targets are mutually exclusive — pass one via `audience`.
1964
+ */
1965
+ async send(input, opts = {}) {
1966
+ const body = {
1967
+ workspace_id: input.workspace_id,
1968
+ title: input.title,
1969
+ message: input.message,
1970
+ channels: input.channels,
1971
+ urgency: input.urgency,
1972
+ dedup_key: input.dedup_key,
1973
+ data: input.data,
1974
+ deep_link: input.deep_link
1975
+ };
1976
+ if (Array.isArray(input.audience)) {
1977
+ body.recipient_ids = input.audience;
1978
+ } else {
1979
+ body.target = input.audience;
1980
+ }
1981
+ const res = await request(
1982
+ this.ctx,
1983
+ {
1984
+ method: "POST",
1985
+ path: "/api/v1/notifications",
1986
+ body,
1987
+ signal: opts.signal
1988
+ }
1989
+ );
1990
+ return res.data;
1991
+ }
1992
+ // ---------------------------------------------------------------------------
1993
+ // Config + preferences
1994
+ // ---------------------------------------------------------------------------
1995
+ /**
1996
+ * Fetch the per-user toast/sound/quiet-hours/digest config. The response
1997
+ * is flat — the config object IS `data`, with no inner `{config}` wrapper.
1998
+ */
1999
+ async getConfig(opts = {}) {
2000
+ const res = await request(this.ctx, {
2001
+ method: "GET",
2002
+ path: "/api/v1/notifications/config",
2003
+ signal: opts.signal
2004
+ });
2005
+ return res.data;
2006
+ }
2007
+ /** PATCH a subset of the config. Returns the full flat config. */
2008
+ async updateConfig(input, opts = {}) {
2009
+ const res = await request(this.ctx, {
2010
+ method: "PATCH",
2011
+ path: "/api/v1/notifications/config",
2012
+ body: input,
2013
+ signal: opts.signal
2014
+ });
2015
+ return res.data;
2016
+ }
2017
+ /**
2018
+ * Fetch the (type, subtype?, channel) preference matrix. The response is
2019
+ * flat — `data` IS the preference array, with no inner `{preferences}`
2020
+ * wrapper.
2021
+ */
2022
+ async getPreferences(opts = {}) {
2023
+ const res = await request(
2024
+ this.ctx,
2025
+ {
2026
+ method: "GET",
2027
+ path: "/api/v1/notifications/preferences",
2028
+ signal: opts.signal
2029
+ }
2030
+ );
2031
+ return res.data;
2032
+ }
2033
+ /**
2034
+ * Bulk-update preferences. Each entry sets one (type, subtype?, channel)
2035
+ * row. Empty `updates[]` → 422. Returns the full flat preference array.
2036
+ */
2037
+ async updatePreferences(updates, opts = {}) {
2038
+ const res = await request(
2039
+ this.ctx,
2040
+ {
2041
+ method: "PATCH",
2042
+ path: "/api/v1/notifications/preferences",
2043
+ body: { updates },
2044
+ signal: opts.signal
2045
+ }
2046
+ );
2047
+ return res.data;
2048
+ }
2049
+ };
2050
+
2051
+ // ../sdk/src/api/provider-endpoints.ts
2052
+ var ProviderEndpointsApi = class {
2053
+ constructor(ctx) {
2054
+ this.ctx = ctx;
2055
+ }
2056
+ async list(opts = {}) {
2057
+ const res = await request(this.ctx, {
2058
+ method: "GET",
2059
+ path: "/api/v1/ai-gateway/provider-endpoints",
2060
+ signal: opts.signal
2061
+ });
2062
+ return res.data;
2063
+ }
2064
+ async presets(opts = {}) {
2065
+ const res = await request(this.ctx, {
2066
+ method: "GET",
2067
+ path: "/api/v1/ai-gateway/provider-endpoints/presets",
2068
+ signal: opts.signal
2069
+ });
2070
+ return res.data;
2071
+ }
2072
+ async create(input, opts = {}) {
2073
+ const res = await request(this.ctx, {
2074
+ method: "POST",
2075
+ path: "/api/v1/ai-gateway/provider-endpoints",
2076
+ body: input,
2077
+ signal: opts.signal
2078
+ });
2079
+ return res.data;
2080
+ }
2081
+ async update(id, input, opts = {}) {
2082
+ const res = await request(this.ctx, {
2083
+ method: "PATCH",
2084
+ path: `/api/v1/ai-gateway/provider-endpoints/${id}`,
2085
+ body: input,
2086
+ signal: opts.signal
2087
+ });
2088
+ return res.data;
2089
+ }
2090
+ async delete(id, opts = {}) {
2091
+ return request(this.ctx, {
2092
+ method: "DELETE",
2093
+ path: `/api/v1/ai-gateway/provider-endpoints/${id}`,
2094
+ signal: opts.signal
2095
+ });
2096
+ }
2097
+ async test(id, opts = {}) {
2098
+ const res = await request(
2099
+ this.ctx,
2100
+ {
2101
+ method: "POST",
2102
+ path: `/api/v1/ai-gateway/provider-endpoints/${id}/test`,
2103
+ signal: opts.signal
2104
+ }
2105
+ );
2106
+ return res.data;
2107
+ }
2108
+ };
2109
+
2110
+ // ../sdk/src/api/realtime.ts
2111
+ var enc3 = encodeURIComponent;
2112
+ var RECONNECT_MIN_MS = 1e3;
2113
+ var RECONNECT_MAX_MS = 3e4;
2114
+ var RealtimeApi = class {
2115
+ constructor(ctx) {
2116
+ this.ctx = ctx;
2117
+ }
2118
+ /** Send an ephemeral message to a channel's current subscribers. */
2119
+ async broadcast(channel, message, opts = {}) {
2120
+ const res = await request(this.ctx, {
2121
+ method: "POST",
2122
+ path: `/api/v1/realtime/${enc3(channel)}/broadcast`,
2123
+ body: { message },
2124
+ signal: opts.signal
2125
+ });
2126
+ return res.data;
2127
+ }
2128
+ /** Record/refresh the caller's presence on a channel (TTL heartbeat). */
2129
+ async heartbeat(channel, opts = {}) {
2130
+ const res = await request(this.ctx, {
2131
+ method: "POST",
2132
+ path: `/api/v1/realtime/${enc3(channel)}/presence`,
2133
+ body: { meta: opts.meta, ttl_seconds: opts.ttlSeconds },
2134
+ signal: opts.signal
2135
+ });
2136
+ return res.data;
2137
+ }
2138
+ /** List who is currently present on a channel. */
2139
+ async presence(channel, opts = {}) {
2140
+ const res = await request(this.ctx, {
2141
+ method: "GET",
2142
+ path: `/api/v1/realtime/${enc3(channel)}/presence`,
2143
+ signal: opts.signal
2144
+ });
2145
+ return res.data;
2146
+ }
2147
+ /**
2148
+ * Open an SSE stream for one or more channels' durable changes + ephemeral
2149
+ * broadcasts. `onEvent` fires for every change/broadcast frame; heartbeat and
2150
+ * transport-only signals are NOT surfaced (they keep the socket warm). Returns
2151
+ * an unsubscribe function — call it (or abort `opts.signal`) to close.
2152
+ *
2153
+ * The stream auto-reconnects with capped exponential backoff and resumes from
2154
+ * the last seen change id (so durable changes are not missed across a drop).
2155
+ */
2156
+ subscribe(channels, onEvent, opts = {}) {
2157
+ if (channels.length === 0) {
2158
+ throw new Error("realtime.subscribe requires at least one channel");
2159
+ }
2160
+ const controller = new AbortController();
2161
+ if (opts.signal) {
2162
+ if (opts.signal.aborted) controller.abort();
2163
+ else opts.signal.addEventListener("abort", () => controller.abort());
2164
+ }
2165
+ let lastEventId = opts.lastEventId;
2166
+ let backoff = RECONNECT_MIN_MS;
2167
+ let closed = false;
2168
+ const run = async () => {
2169
+ while (!closed && !controller.signal.aborted) {
2170
+ try {
2171
+ const headers = {};
2172
+ if (lastEventId) headers["Last-Event-ID"] = lastEventId;
2173
+ const res = await requestRaw(this.ctx, {
2174
+ method: "GET",
2175
+ path: "/api/v1/realtime/subscribe",
2176
+ query: { channels: channels.join(",") },
2177
+ headers,
2178
+ signal: controller.signal,
2179
+ expectJson: false
2180
+ });
2181
+ backoff = RECONNECT_MIN_MS;
2182
+ for await (const frame of parseSseStream(res, controller.signal)) {
2183
+ if (frame.id) lastEventId = frame.id;
2184
+ const event = toRealtimeEvent(frame);
2185
+ if (event) onEvent(event);
2186
+ }
2187
+ } catch (err) {
2188
+ if (controller.signal.aborted || closed) return;
2189
+ opts.onError?.(err instanceof Error ? err : new Error(String(err)));
2190
+ }
2191
+ if (closed || controller.signal.aborted) return;
2192
+ await delay(backoff, controller.signal);
2193
+ backoff = Math.min(backoff * 2, RECONNECT_MAX_MS);
2194
+ }
2195
+ };
2196
+ void run();
2197
+ return () => {
2198
+ closed = true;
2199
+ controller.abort();
2200
+ };
2201
+ }
2202
+ /**
2203
+ * A per-channel handle bundling subscribe + broadcast + presence + heartbeat
2204
+ * for one channel, so callers don't repeat the key:
2205
+ *
2206
+ * const ch = client.realtime.channel("table:col_123");
2207
+ * const stop = ch.subscribe((e) => render(e));
2208
+ * await ch.broadcast({ cursor: { x, y } });
2209
+ */
2210
+ channel(key) {
2211
+ return {
2212
+ key,
2213
+ subscribe: (onEvent, o) => this.subscribe([key], onEvent, o),
2214
+ broadcast: (message, o) => this.broadcast(key, message, o),
2215
+ presence: (o) => this.presence(key, o),
2216
+ heartbeat: (o) => this.heartbeat(key, o)
2217
+ };
2218
+ }
2219
+ };
2220
+ async function* parseSseStream(response, signal) {
2221
+ if (!response.body) return;
2222
+ const reader = response.body.getReader();
2223
+ const decoder = new TextDecoder();
2224
+ let buffer = "";
2225
+ try {
2226
+ while (true) {
2227
+ if (signal?.aborted) return;
2228
+ const { done, value } = await reader.read();
2229
+ if (done) break;
2230
+ buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
2231
+ while (true) {
2232
+ const boundary = buffer.indexOf("\n\n");
2233
+ if (boundary === -1) break;
2234
+ const block = buffer.slice(0, boundary);
2235
+ buffer = buffer.slice(boundary + 2);
2236
+ const parsed = parseSseBlock3(block);
2237
+ if (parsed) yield parsed;
2238
+ }
2239
+ }
2240
+ } finally {
2241
+ try {
2242
+ reader.releaseLock();
2243
+ } catch {
2244
+ }
2245
+ }
2246
+ }
2247
+ function parseSseBlock3(block) {
2248
+ let id;
2249
+ let data = "";
2250
+ let isHeartbeat = false;
2251
+ for (const line of block.split("\n")) {
2252
+ if (!line || line.startsWith(":")) continue;
2253
+ if (line.startsWith("id:")) id = line.slice(3).trim();
2254
+ else if (line.startsWith("event:")) {
2255
+ if (line.slice(6).trim() === "heartbeat") isHeartbeat = true;
2256
+ } else if (line.startsWith("data:")) data += line.slice(5).trimStart();
2257
+ }
2258
+ if (isHeartbeat) return null;
2259
+ if (!data) return null;
2260
+ return id ? { id, data } : { data };
2261
+ }
2262
+ function toRealtimeEvent(frame) {
2263
+ let parsed;
2264
+ try {
2265
+ parsed = JSON.parse(frame.data);
2266
+ } catch {
2267
+ return null;
2268
+ }
2269
+ if (!parsed || typeof parsed !== "object") return null;
2270
+ const env = parsed;
2271
+ if (typeof env.channel !== "string") return null;
2272
+ const channel = env.channel;
2273
+ if (channel === "rt:bcast") {
2274
+ const inner = env.message;
2275
+ if (inner && typeof inner === "object" && typeof inner.channel === "string") {
2276
+ return { channel: inner.channel, type: "broadcast", data: inner.message };
2277
+ }
2278
+ return null;
2279
+ }
2280
+ if (channel === "events") {
2281
+ const inner = env.message;
2282
+ const sigChannel = inner && typeof inner.channel === "string" ? inner.channel : channel;
2283
+ const type = inner?.message && typeof inner.message.type === "string" ? inner.message.type : "signal";
2284
+ return { channel: sigChannel, type, data: null };
2285
+ }
2286
+ const msg = env.message;
2287
+ if (msg && typeof msg === "object") {
2288
+ return {
2289
+ channel,
2290
+ type: typeof msg.type === "string" ? msg.type : "change",
2291
+ data: msg.data ?? null,
2292
+ ...frame.id ? { id: frame.id } : {}
2293
+ };
2294
+ }
2295
+ return null;
2296
+ }
2297
+ function delay(ms, signal) {
2298
+ return new Promise((resolve) => {
2299
+ const t = setTimeout(resolve, ms);
2300
+ signal.addEventListener(
2301
+ "abort",
2302
+ () => {
2303
+ clearTimeout(t);
2304
+ resolve();
2305
+ },
2306
+ { once: true }
2307
+ );
2308
+ });
2309
+ }
2310
+
2311
+ // ../sdk/src/api/search.ts
2312
+ var SearchApi = class {
2313
+ constructor(ctx) {
2314
+ this.ctx = ctx;
2315
+ }
2316
+ async search(input, opts = {}) {
2317
+ const res = await request(this.ctx, {
2318
+ method: "GET",
2319
+ path: "/api/v1/search",
2320
+ query: {
2321
+ q: input.q,
2322
+ limit: input.limit,
2323
+ cursor: input.cursor,
2324
+ source: input.source,
2325
+ type: input.type,
2326
+ workspace_id: input.workspace_id,
2327
+ chat_mode: input.chat_mode
2328
+ },
2329
+ signal: opts.signal
2330
+ });
2331
+ return res.data;
2332
+ }
2333
+ };
2334
+
2335
+ // ../sdk/src/api/secrets.ts
2336
+ var SecretsApi = class {
2337
+ constructor(ctx) {
2338
+ this.ctx = ctx;
2339
+ }
2340
+ async list(opts = {}) {
2341
+ const query = opts.workspaceId ? `?workspace_id=${encodeURIComponent(opts.workspaceId)}` : "";
2342
+ const res = await request(this.ctx, {
2343
+ method: "GET",
2344
+ path: `/api/v1/secrets${query}`,
2345
+ signal: opts.signal
2346
+ });
2347
+ return res.data;
2348
+ }
2349
+ async get(secretId, opts = {}) {
2350
+ const res = await request(this.ctx, {
2351
+ method: "GET",
2352
+ path: `/api/v1/secrets/${secretId}`,
2353
+ signal: opts.signal
2354
+ });
2355
+ return res.data;
2356
+ }
2357
+ async create(input, opts = {}) {
2358
+ const res = await request(this.ctx, {
2359
+ method: "POST",
2360
+ path: "/api/v1/secrets",
2361
+ body: input,
2362
+ signal: opts.signal
2363
+ });
2364
+ return res.data;
2365
+ }
2366
+ async update(secretId, input, opts = {}) {
2367
+ const res = await request(this.ctx, {
2368
+ method: "PATCH",
2369
+ path: `/api/v1/secrets/${secretId}`,
2370
+ body: input,
2371
+ signal: opts.signal
2372
+ });
2373
+ return res.data;
2374
+ }
2375
+ async delete(secretId, opts = {}) {
2376
+ return request(this.ctx, {
2377
+ method: "DELETE",
2378
+ path: `/api/v1/secrets/${secretId}`,
2379
+ signal: opts.signal
2380
+ });
2381
+ }
2382
+ /**
2383
+ * Reveal a secret's plaintext value. Session-authenticated callers only (not
2384
+ * API keys), audited, and rate-limited. Returns the decrypted `value`.
2385
+ */
2386
+ async reveal(secretId, opts = {}) {
2387
+ const res = await request(this.ctx, {
2388
+ method: "POST",
2389
+ path: `/api/v1/secrets/${secretId}/reveal`,
2390
+ signal: opts.signal
2391
+ });
2392
+ return res.data;
2393
+ }
2394
+ };
2395
+
2396
+ // ../sdk/src/api/settings.ts
2397
+ var SettingsApi = class {
2398
+ constructor(ctx) {
2399
+ this.ctx = ctx;
2400
+ }
2401
+ async get(opts = {}) {
2402
+ const res = await request(this.ctx, {
2403
+ method: "GET",
2404
+ path: "/api/v1/settings",
2405
+ signal: opts.signal
2406
+ });
2407
+ return res.data;
2408
+ }
2409
+ async update(input, opts = {}) {
2410
+ const res = await request(this.ctx, {
2411
+ method: "PATCH",
2412
+ path: "/api/v1/settings",
2413
+ body: input,
2414
+ signal: opts.signal
2415
+ });
2416
+ return res.data;
2417
+ }
2418
+ };
2419
+
2420
+ // ../sdk/src/api/sharing.ts
2421
+ var SharingApi = class {
2422
+ constructor(ctx) {
2423
+ this.ctx = ctx;
2424
+ }
2425
+ // ---------------------------------------------------------------------------
2426
+ // /v1/shares — share rows on a single resource
2427
+ // ---------------------------------------------------------------------------
2428
+ async list(query, opts = {}) {
2429
+ const res = await request(this.ctx, {
2430
+ method: "GET",
2431
+ path: "/api/v1/shares",
2432
+ query,
2433
+ signal: opts.signal
2434
+ });
2435
+ return res.data;
2436
+ }
2437
+ async add(input, opts = {}) {
2438
+ const res = await request(this.ctx, {
2439
+ method: "POST",
2440
+ path: "/api/v1/shares",
2441
+ body: input,
2442
+ signal: opts.signal
2443
+ });
2444
+ return res.data;
2445
+ }
2446
+ /**
2447
+ * Update a share's permission. The keying triple
2448
+ * (`resource_type`, `resource_id`, `grantee_actor_id`) goes on the QUERY
2449
+ * string; the request BODY carries only `{ permission }`.
2450
+ */
2451
+ async update(input, opts = {}) {
2452
+ const res = await request(this.ctx, {
2453
+ method: "PATCH",
2454
+ path: "/api/v1/shares",
2455
+ query: {
2456
+ resource_type: input.resource_type,
2457
+ resource_id: input.resource_id,
2458
+ grantee_actor_id: input.grantee_actor_id
2459
+ },
2460
+ body: { permission: input.permission },
2461
+ signal: opts.signal
2462
+ });
2463
+ return res.data;
2464
+ }
2465
+ /**
2466
+ * Remove a share. The keying triple goes on the QUERY string; the
2467
+ * request takes NO body. The response is the removed triple at the TOP
2468
+ * LEVEL — `{ deleted, resource_type, resource_id, grantee_actor_id }` —
2469
+ * not nested under `data` (a share has no standalone id).
2470
+ */
2471
+ async remove(input, opts = {}) {
2472
+ return request(this.ctx, {
2473
+ method: "DELETE",
2474
+ path: "/api/v1/shares",
2475
+ query: input,
2476
+ signal: opts.signal
2477
+ });
2478
+ }
2479
+ // ---------------------------------------------------------------------------
2480
+ // /v1/shared-with-me — inverse list
2481
+ // ---------------------------------------------------------------------------
2482
+ /**
2483
+ * List resources others have shared with the caller. Omit `resource_type`
2484
+ * for the unified view across chats / agents / files / workspaces.
2485
+ */
2486
+ async listSharedWithMe(query = {}, opts = {}) {
2487
+ const res = await request(this.ctx, {
2488
+ method: "GET",
2489
+ path: "/api/v1/shared-with-me",
2490
+ query,
2491
+ signal: opts.signal
2492
+ });
2493
+ return res.data;
2494
+ }
2495
+ };
2496
+
2497
+ // ../sdk/src/api/store.ts
2498
+ var StoreApi = class {
2499
+ constructor(ctx) {
2500
+ this.ctx = ctx;
2501
+ }
2502
+ /**
2503
+ * Search the hub. Open envelope — items are heterogeneous (skills carry
2504
+ * version/license, agents carry icons/prompts, computer templates carry
2505
+ * sizing) so we surface them as `StoreItem` with an open index signature.
2506
+ */
2507
+ async search(query = {}, opts = {}) {
2508
+ const res = await request(this.ctx, {
2509
+ method: "GET",
2510
+ path: "/api/v1/store/search",
2511
+ query,
2512
+ signal: opts.signal
2513
+ });
2514
+ return res.data;
2515
+ }
2516
+ /**
2517
+ * Install a hub item into one of the caller's workspaces. Returns whatever
2518
+ * the per-template installer produces (folder ids, agent ids, ...) —
2519
+ * `StoreInstallResult` carries the common fields and leaves the rest
2520
+ * open.
2521
+ */
2522
+ async install(resourceId, input, opts = {}) {
2523
+ const res = await request(this.ctx, {
2524
+ method: "POST",
2525
+ path: `/api/v1/store/${resourceId}/install`,
2526
+ body: input,
2527
+ signal: opts.signal
2528
+ });
2529
+ return res.data;
2530
+ }
2531
+ };
2532
+
2533
+ // ../sdk/src/api/subscription.ts
2534
+ var SubscriptionApi = class {
2535
+ constructor(ctx) {
2536
+ this.ctx = ctx;
2537
+ }
2538
+ async get(opts = {}) {
2539
+ const res = await request(this.ctx, {
2540
+ method: "GET",
2541
+ path: "/api/v1/subscription",
2542
+ signal: opts.signal
2543
+ });
2544
+ return res.data;
2545
+ }
2546
+ };
2547
+
2548
+ // ../sdk/src/api/tables.ts
2549
+ var enc4 = encodeURIComponent;
2550
+ var TablesApi = class {
2551
+ constructor(ctx) {
2552
+ this.ctx = ctx;
2553
+ }
2554
+ async list(opts = {}) {
2555
+ const q = new URLSearchParams();
2556
+ if (opts.workspaceId) q.set("workspace_id", opts.workspaceId);
2557
+ if (opts.cursor) q.set("cursor", opts.cursor);
2558
+ if (opts.limit) q.set("limit", String(opts.limit));
2559
+ const qs = q.toString() ? `?${q.toString()}` : "";
2560
+ const res = await request(this.ctx, {
2561
+ method: "GET",
2562
+ path: `/api/v1/tables${qs}`,
2563
+ signal: opts.signal
2564
+ });
2565
+ return res.data;
2566
+ }
2567
+ async get(id, opts = {}) {
2568
+ const res = await request(this.ctx, {
2569
+ method: "GET",
2570
+ path: `/api/v1/tables/${enc4(id)}`,
2571
+ signal: opts.signal
2572
+ });
2573
+ return res.data;
2574
+ }
2575
+ async create(input, opts = {}) {
2576
+ const res = await request(this.ctx, {
2577
+ method: "POST",
2578
+ path: "/api/v1/tables",
2579
+ body: input,
2580
+ signal: opts.signal
2581
+ });
2582
+ return res.data;
2583
+ }
2584
+ async update(id, input, opts = {}) {
2585
+ const res = await request(this.ctx, {
2586
+ method: "PATCH",
2587
+ path: `/api/v1/tables/${enc4(id)}`,
2588
+ body: input,
2589
+ signal: opts.signal
2590
+ });
2591
+ return res.data;
2592
+ }
2593
+ async delete(id, opts = {}) {
2594
+ return request(this.ctx, {
2595
+ method: "DELETE",
2596
+ path: `/api/v1/tables/${enc4(id)}`,
2597
+ signal: opts.signal
2598
+ });
2599
+ }
2600
+ /** Query a collection's records with the filter/sort/paginate DSL. */
2601
+ async query(id, query = {}, opts = {}) {
2602
+ const res = await request(this.ctx, {
2603
+ method: "POST",
2604
+ path: `/api/v1/tables/${enc4(id)}/query`,
2605
+ body: query,
2606
+ signal: opts.signal
2607
+ });
2608
+ return res.data;
2609
+ }
2610
+ async createRecord(id, values, opts = {}) {
2611
+ const res = await request(this.ctx, {
2612
+ method: "POST",
2613
+ path: `/api/v1/tables/${enc4(id)}/records`,
2614
+ body: { values },
2615
+ signal: opts.signal
2616
+ });
2617
+ return res.data;
2618
+ }
2619
+ async getRecord(id, recordId, opts = {}) {
2620
+ const res = await request(this.ctx, {
2621
+ method: "GET",
2622
+ path: `/api/v1/tables/${enc4(id)}/records/${enc4(recordId)}`,
2623
+ signal: opts.signal
2624
+ });
2625
+ return res.data;
2626
+ }
2627
+ async updateRecord(id, recordId, values, opts = {}) {
2628
+ const res = await request(this.ctx, {
2629
+ method: "PATCH",
2630
+ path: `/api/v1/tables/${enc4(id)}/records/${enc4(recordId)}`,
2631
+ body: { values },
2632
+ signal: opts.signal
2633
+ });
2634
+ return res.data;
2635
+ }
2636
+ async deleteRecord(id, recordId, opts = {}) {
2637
+ return request(this.ctx, {
2638
+ method: "DELETE",
2639
+ path: `/api/v1/tables/${enc4(id)}/records/${enc4(recordId)}`,
2640
+ signal: opts.signal
2641
+ });
2642
+ }
2643
+ /** Export a collection's records as a CSV string (header row = field keys). */
2644
+ async exportCsv(id, opts = {}) {
2645
+ const res = await requestRaw(this.ctx, {
2646
+ method: "GET",
2647
+ path: `/api/v1/tables/${enc4(id)}/export`,
2648
+ signal: opts.signal,
2649
+ expectJson: false
2650
+ });
2651
+ return res.text();
2652
+ }
2653
+ /** Import records into a collection from a CSV string. Returns the count. */
2654
+ async importCsv(id, csv, opts = {}) {
2655
+ const res = await request(this.ctx, {
2656
+ method: "POST",
2657
+ path: `/api/v1/tables/${enc4(id)}/import`,
2658
+ body: { csv },
2659
+ signal: opts.signal
2660
+ });
2661
+ return res.data;
2662
+ }
2663
+ };
2664
+
2665
+ // ../sdk/src/api/triggers.ts
2666
+ var TriggersApi = class {
2667
+ constructor(ctx) {
2668
+ this.ctx = ctx;
2669
+ }
2670
+ // ---------------------------------------------------------------------------
2671
+ // CRUD
2672
+ // ---------------------------------------------------------------------------
2673
+ async list(query = {}, opts = {}) {
2674
+ const res = await request(this.ctx, {
2675
+ method: "GET",
2676
+ path: "/api/v1/triggers",
2677
+ query,
2678
+ signal: opts.signal
2679
+ });
2680
+ return res.data;
2681
+ }
2682
+ /**
2683
+ * Create a trigger. `workspace_id` is passed in the request BODY (alongside
2684
+ * the flat trigger definition) — it is not a query param. The request
2685
+ * body is flat: every scheduling field (`cron_expression`, …) and every
2686
+ * action field (`agent_id`, `prompt_template`, `file_id`, …) sits at the
2687
+ * top level — there are no nested `trigger_config` / `action_config`
2688
+ * objects any more.
2689
+ *
2690
+ * For a `webhook` trigger the response additionally carries a one-time
2691
+ * plaintext `secret` (use `TriggerWithSecret`); other trigger types
2692
+ * resolve to a plain `Trigger`.
2693
+ */
2694
+ async create(workspaceId, input, opts = {}) {
2695
+ const res = await request(
2696
+ this.ctx,
2697
+ {
2698
+ method: "POST",
2699
+ path: "/api/v1/triggers",
2700
+ body: { workspace_id: workspaceId, ...input },
2701
+ signal: opts.signal
2702
+ }
2703
+ );
2704
+ return res.data;
2705
+ }
2706
+ async get(id, opts = {}) {
2707
+ const res = await request(this.ctx, {
2708
+ method: "GET",
2709
+ path: `/api/v1/triggers/${id}`,
2710
+ signal: opts.signal
2711
+ });
2712
+ return res.data;
2713
+ }
2714
+ async update(id, input, opts = {}) {
2715
+ const res = await request(this.ctx, {
2716
+ method: "PATCH",
2717
+ path: `/api/v1/triggers/${id}`,
2718
+ body: input,
2719
+ signal: opts.signal
2720
+ });
2721
+ return res.data;
2722
+ }
2723
+ async delete(id, opts = {}) {
2724
+ return request(this.ctx, {
2725
+ method: "DELETE",
2726
+ path: `/api/v1/triggers/${id}`,
2727
+ signal: opts.signal
2728
+ });
2729
+ }
2730
+ async archive(id, opts = {}) {
2731
+ const res = await request(this.ctx, {
2732
+ method: "POST",
2733
+ path: `/api/v1/triggers/${id}/archive`,
2734
+ signal: opts.signal
2735
+ });
2736
+ return res.data;
2737
+ }
2738
+ async unarchive(id, opts = {}) {
2739
+ const res = await request(this.ctx, {
2740
+ method: "POST",
2741
+ path: `/api/v1/triggers/${id}/unarchive`,
2742
+ signal: opts.signal
2743
+ });
2744
+ return res.data;
2745
+ }
2746
+ // ---------------------------------------------------------------------------
2747
+ // Fire + rotate-secret + runs
2748
+ // ---------------------------------------------------------------------------
2749
+ /**
2750
+ * Fire a trigger via its webhook secret. This endpoint does NOT use the
2751
+ * client's `ap_` key — the bearer is the trigger's secret — so we build a
2752
+ * one-off HttpContext for it rather than mutating the shared one.
2753
+ *
2754
+ * Responds HTTP 202. The response identifies the fired trigger via `id`
2755
+ * only.
2756
+ */
2757
+ async fire(id, input, opts = {}) {
2758
+ const localCtx = {
2759
+ apiUrl: this.ctx.apiUrl,
2760
+ key: input.secret,
2761
+ fetch: this.ctx.fetch
2762
+ };
2763
+ const res = await request(localCtx, {
2764
+ method: "POST",
2765
+ path: `/api/v1/triggers/${id}/fire`,
2766
+ body: input.body ?? {},
2767
+ signal: opts.signal
2768
+ });
2769
+ return res.data;
2770
+ }
2771
+ /**
2772
+ * Rotate the webhook secret. Returns the trigger with a fresh one-time
2773
+ * plaintext `secret` populated (`TriggerWithSecret`). The old secret
2774
+ * immediately stops working.
2775
+ */
2776
+ async rotateSecret(id, opts = {}) {
2777
+ const res = await request(this.ctx, {
2778
+ method: "POST",
2779
+ path: `/api/v1/triggers/${id}/rotate-secret`,
2780
+ signal: opts.signal
2781
+ });
2782
+ return res.data;
2783
+ }
2784
+ /** Recent fires + their success/error outcome. */
2785
+ async listRuns(id, query = {}, opts = {}) {
2786
+ const res = await request(this.ctx, {
2787
+ method: "GET",
2788
+ path: `/api/v1/triggers/${id}/runs`,
2789
+ query,
2790
+ signal: opts.signal
2791
+ });
2792
+ return res.data;
2793
+ }
2794
+ async getCostStats(id, opts = {}) {
2795
+ const res = await request(this.ctx, {
2796
+ method: "GET",
2797
+ path: `/api/v1/triggers/${id}/cost-stats`,
2798
+ signal: opts.signal
2799
+ });
2800
+ return res.data;
2801
+ }
2802
+ async getCostStatsMap(query = {}, opts = {}) {
2803
+ const res = await request(this.ctx, {
2804
+ method: "GET",
2805
+ path: "/api/v1/triggers/cost-stats",
2806
+ query,
2807
+ signal: opts.signal
2808
+ });
2809
+ return res.data.by_id;
2810
+ }
2811
+ };
2812
+
2813
+ // ../sdk/src/api/user.ts
2814
+ var UserApi = class {
2815
+ constructor(ctx) {
2816
+ this.ctx = ctx;
2817
+ }
2818
+ /** Current authenticated user's profile. */
2819
+ async me(opts = {}) {
2820
+ const res = await request(this.ctx, {
2821
+ method: "GET",
2822
+ path: "/api/v1/me",
2823
+ signal: opts.signal
2824
+ });
2825
+ return res.data;
2826
+ }
2827
+ /**
2828
+ * Storage usage summary — bytes used / total, snapshot footprint.
2829
+ * Corresponds to `GET /me/usage?view=summary` (the default).
2830
+ */
2831
+ async usage(opts = {}) {
2832
+ const res = await request(this.ctx, {
2833
+ method: "GET",
2834
+ path: "/api/v1/me/usage",
2835
+ query: { view: "summary" },
2836
+ signal: opts.signal
2837
+ });
2838
+ return res.data;
2839
+ }
2840
+ /**
2841
+ * Paginated history of LLM / image / audio calls + cost.
2842
+ * Corresponds to `GET /me/usage?view=history`.
2843
+ */
2844
+ async usageHistory(query = {}, opts = {}) {
2845
+ const res = await request(this.ctx, {
2846
+ method: "GET",
2847
+ path: "/api/v1/me/usage",
2848
+ query: { view: "history", ...query },
2849
+ signal: opts.signal
2850
+ });
2851
+ return res.data;
2852
+ }
2853
+ };
2854
+
2855
+ // ../sdk/src/api/web.ts
2856
+ var WebSearchApi = class {
2857
+ constructor(ctx) {
2858
+ this.ctx = ctx;
2859
+ }
2860
+ async search(input, opts = {}) {
2861
+ const res = await request(this.ctx, {
2862
+ method: "POST",
2863
+ path: "/api/v1/web/search",
2864
+ body: input,
2865
+ signal: opts.signal
2866
+ });
2867
+ return res.data;
2868
+ }
2869
+ };
2870
+
2871
+ // ../sdk/src/api/workspaces.ts
2872
+ var WorkspacesApi = class {
2873
+ constructor(ctx) {
2874
+ this.ctx = ctx;
2875
+ }
2876
+ // ---------------------------------------------------------------------------
2877
+ // CRUD
2878
+ // ---------------------------------------------------------------------------
2879
+ async list(query = {}, opts = {}) {
2880
+ const res = await request(this.ctx, {
2881
+ method: "GET",
2882
+ path: "/api/v1/workspaces",
2883
+ query,
2884
+ signal: opts.signal
2885
+ });
2886
+ return res.data;
2887
+ }
2888
+ async create(input, opts = {}) {
2889
+ const res = await request(this.ctx, {
2890
+ method: "POST",
2891
+ path: "/api/v1/workspaces",
2892
+ body: input,
2893
+ signal: opts.signal
2894
+ });
2895
+ return res.data;
2896
+ }
2897
+ async get(id, opts = {}) {
2898
+ const res = await request(this.ctx, {
2899
+ method: "GET",
2900
+ path: `/api/v1/workspaces/${id}`,
2901
+ signal: opts.signal
2902
+ });
2903
+ return res.data;
2904
+ }
2905
+ async update(id, input, opts = {}) {
2906
+ const res = await request(this.ctx, {
2907
+ method: "PATCH",
2908
+ path: `/api/v1/workspaces/${id}`,
2909
+ body: input,
2910
+ signal: opts.signal
2911
+ });
2912
+ return res.data;
2913
+ }
2914
+ async delete(id, opts = {}) {
2915
+ return request(this.ctx, {
2916
+ method: "DELETE",
2917
+ path: `/api/v1/workspaces/${id}`,
2918
+ signal: opts.signal
2919
+ });
2920
+ }
2921
+ async archive(id, opts = {}) {
2922
+ const res = await request(this.ctx, {
2923
+ method: "POST",
2924
+ path: `/api/v1/workspaces/${id}/archive`,
2925
+ signal: opts.signal
2926
+ });
2927
+ return res.data;
2928
+ }
2929
+ async unarchive(id, opts = {}) {
2930
+ const res = await request(this.ctx, {
2931
+ method: "POST",
2932
+ path: `/api/v1/workspaces/${id}/unarchive`,
2933
+ signal: opts.signal
2934
+ });
2935
+ return res.data;
2936
+ }
2937
+ // ---------------------------------------------------------------------------
2938
+ // Members
2939
+ // ---------------------------------------------------------------------------
2940
+ async listMembers(id, opts = {}) {
2941
+ const res = await request(this.ctx, {
2942
+ method: "GET",
2943
+ path: `/api/v1/workspaces/${id}/members`,
2944
+ signal: opts.signal
2945
+ });
2946
+ return res.data;
2947
+ }
2948
+ async addMember(id, input, opts = {}) {
2949
+ const res = await request(this.ctx, {
2950
+ method: "POST",
2951
+ path: `/api/v1/workspaces/${id}/members`,
2952
+ body: input,
2953
+ signal: opts.signal
2954
+ });
2955
+ return res.data;
2956
+ }
2957
+ async updateMember(id, memberId, input, opts = {}) {
2958
+ const res = await request(this.ctx, {
2959
+ method: "PATCH",
2960
+ path: `/api/v1/workspaces/${id}/members/${memberId}`,
2961
+ body: input,
2962
+ signal: opts.signal
2963
+ });
2964
+ return res.data;
2965
+ }
2966
+ async removeMember(id, memberId, opts = {}) {
2967
+ return request(this.ctx, {
2968
+ method: "DELETE",
2969
+ path: `/api/v1/workspaces/${id}/members/${memberId}`,
2970
+ signal: opts.signal
2971
+ });
2972
+ }
2973
+ // ---------------------------------------------------------------------------
2974
+ // Invitations
2975
+ // ---------------------------------------------------------------------------
2976
+ async listInvitations(id, opts = {}) {
2977
+ const res = await request(this.ctx, {
2978
+ method: "GET",
2979
+ path: `/api/v1/workspaces/${id}/invitations`,
2980
+ signal: opts.signal
2981
+ });
2982
+ return res.data;
2983
+ }
2984
+ /**
2985
+ * Invite an existing idapt user by their profile slug. Idempotent on
2986
+ * the `(workspace_id, slug)` pair — reissue safely. Personal workspaces
2987
+ * reject invitations.
2988
+ */
2989
+ async createInvitation(id, input, opts = {}) {
2990
+ const res = await request(
2991
+ this.ctx,
2992
+ {
2993
+ method: "POST",
2994
+ path: `/api/v1/workspaces/${id}/invitations`,
2995
+ body: { slug: input.slug, role: input.role ?? "viewer" },
2996
+ signal: opts.signal
2997
+ }
2998
+ );
2999
+ return res.data;
3000
+ }
3001
+ /**
3002
+ * Revoke a pending invitation. The invitation is identified by the
3003
+ * invitee's profile slug, passed as the `invitee_slug` query param —
3004
+ * there is no invitation id. Returns `{ deleted: true, id }` where `id`
3005
+ * is the workspace id (top-level, not enveloped under `data`).
3006
+ */
3007
+ async deleteInvitation(id, inviteeSlug, opts = {}) {
3008
+ return request(this.ctx, {
3009
+ method: "DELETE",
3010
+ path: `/api/v1/workspaces/${id}/invitations`,
3011
+ query: { invitee_slug: inviteeSlug },
3012
+ signal: opts.signal
3013
+ });
3014
+ }
3015
+ };
3016
+
3017
+ // src/api/app.ts
3018
+ var RemoteBundleReader = class {
3019
+ constructor(ctx, id) {
3020
+ this.ctx = ctx;
3021
+ this.id = id;
3022
+ }
3023
+ list(opts = {}) {
3024
+ return listFiles(this.ctx, { parent_id: this.id }, opts);
3025
+ }
3026
+ listFolder(parentId, opts = {}) {
3027
+ return listFiles(this.ctx, { parent_id: parentId }, opts);
3028
+ }
3029
+ read(fileId, opts = {}) {
3030
+ return getFileText(this.ctx, fileId, opts);
3031
+ }
3032
+ async readJSON(fileId, opts = {}) {
3033
+ const text = await this.read(fileId, opts);
3034
+ return JSON.parse(text);
3035
+ }
3036
+ readBlob(fileId, opts = {}) {
3037
+ return getFileBlob(this.ctx, fileId, opts);
3038
+ }
3039
+ };
3040
+ var AppFolder = class {
3041
+ /**
3042
+ * Two construction shapes:
3043
+ *
3044
+ * - `new AppFolder(ctx, folderId)` — builds a {@link RemoteBundleReader}
3045
+ * internally. The normal path.
3046
+ * - `new AppFolder(reader)` — inject any {@link BundleReader} (e.g. a fake
3047
+ * in tests).
3048
+ */
3049
+ constructor(ctxOrReader, folderId) {
3050
+ if (isBundleReader(ctxOrReader)) {
3051
+ this.reader = ctxOrReader;
3052
+ } else {
3053
+ this.reader = new RemoteBundleReader(ctxOrReader, folderId ?? "");
3054
+ }
3055
+ }
3056
+ /** The code folder's id. */
3057
+ get id() {
3058
+ return this.reader.id ?? "";
3059
+ }
3060
+ /** List files in the app folder. */
3061
+ list(opts = {}) {
3062
+ return this.reader.list(opts);
3063
+ }
3064
+ /** List a specific subfolder of the app folder. */
3065
+ listFolder(parentId, opts = {}) {
3066
+ return this.reader.listFolder(parentId, opts);
3067
+ }
3068
+ /**
3069
+ * Read a file's text content (by Drive file id). UTF-8 decoded; use
3070
+ * `readBlob` for binary.
3071
+ */
3072
+ read(fileId, opts = {}) {
3073
+ return this.reader.read(fileId, opts);
3074
+ }
3075
+ /** Read + parse JSON in one call. */
3076
+ readJSON(fileId, opts = {}) {
3077
+ return this.reader.readJSON(fileId, opts);
3078
+ }
3079
+ /** Read a file's raw bytes as a Blob. Preserves the server's MIME type. */
3080
+ readBlob(fileId, opts = {}) {
3081
+ return this.reader.readBlob(fileId, opts);
3082
+ }
3083
+ };
3084
+ function isBundleReader(x) {
3085
+ return typeof x.read === "function" && typeof x.readBlob === "function";
3086
+ }
3087
+
3088
+ // src/api/data-remote.ts
3089
+ var RemoteDataStore = class {
3090
+ /**
3091
+ * @param ctx HTTP context (carries the app's `ap_` bearer + api origin).
3092
+ * @param id The app's **resource id** (the `{appResId}` path segment) — this
3093
+ * is the boot's `appResourceId`, NOT a Drive folder id.
3094
+ */
3095
+ constructor(ctx, id) {
3096
+ this.ctx = ctx;
3097
+ this.id = id;
3098
+ }
3099
+ /** Idapt origin this store talks to — surfaced by `DataFolder` for tooling. */
3100
+ get apiUrl() {
3101
+ return this.ctx.apiUrl;
3102
+ }
3103
+ /** URL-encode a single key segment for the `{...key}` catch-all route. */
3104
+ keyPath(key) {
3105
+ const encoded = key.split("/").map((seg) => encodeURIComponent(seg)).join("/");
3106
+ return `/api/browser-app/data/${encodeURIComponent(this.id)}/${encoded}`;
3107
+ }
3108
+ /** List the principal's keys for this app → `File[]` with `id = name = key`. */
3109
+ async list(opts = {}) {
3110
+ const res = await request(this.ctx, {
3111
+ method: "GET",
3112
+ // Trailing slash → empty `[...key]` → the route's "list keys" branch.
3113
+ path: `/api/browser-app/data/${encodeURIComponent(this.id)}/`,
3114
+ signal: opts.signal
3115
+ });
3116
+ const keys = Array.isArray(res?.keys) ? res.keys : [];
3117
+ return keys.map(keyToFile);
3118
+ }
3119
+ /**
3120
+ * No KV analogue — the app-data KV is a flat per-principal namespace with no
3121
+ * subfolders. Throws so apps that try to enumerate a folder get a clear error.
3122
+ */
3123
+ listFolder(_parentId, _opts = {}) {
3124
+ return Promise.reject(unsupported("listFolder"));
3125
+ }
3126
+ /** Metadata for one key — GET it; 404 surfaces as `NotFoundError`. */
3127
+ async getMetadata(key, opts = {}) {
3128
+ await requestRaw(this.ctx, {
3129
+ method: "GET",
3130
+ path: this.keyPath(key),
3131
+ signal: opts.signal
3132
+ });
3133
+ return keyToFile(key);
3134
+ }
3135
+ /** Read a key's stored string. Inline JSON → `{value}`; blob → raw text. */
3136
+ async read(key, opts = {}) {
3137
+ const res = await requestRaw(this.ctx, {
3138
+ method: "GET",
3139
+ path: this.keyPath(key),
3140
+ signal: opts.signal
3141
+ });
3142
+ const ct = res.headers.get("content-type") ?? "";
3143
+ if (ct.includes("application/json")) {
3144
+ const body = await res.json();
3145
+ return valueToString(body?.value);
3146
+ }
3147
+ return res.text();
3148
+ }
3149
+ /** Read a key's raw bytes as a Blob (handles blob-backed binary entries). */
3150
+ async readBlob(key, opts = {}) {
3151
+ const res = await requestRaw(this.ctx, {
3152
+ method: "GET",
3153
+ path: this.keyPath(key),
3154
+ signal: opts.signal
3155
+ });
3156
+ const ct = res.headers.get("content-type") ?? "";
3157
+ if (ct.includes("application/json")) {
3158
+ const body = await res.json();
3159
+ return new Blob([valueToString(body?.value)], { type: "text/plain" });
3160
+ }
3161
+ return res.blob();
3162
+ }
3163
+ /** Upsert a key's value. `content` is stored verbatim as `{value: content}`. */
3164
+ async write(key, content, opts = {}) {
3165
+ await request(this.ctx, {
3166
+ method: "PUT",
3167
+ path: this.keyPath(key),
3168
+ body: { value: content },
3169
+ signal: opts.signal
3170
+ });
3171
+ return keyToFile(key);
3172
+ }
3173
+ /** Delete a key (idempotent — DELETE on a missing key still resolves). */
3174
+ async delete(key, opts = {}) {
3175
+ await request(this.ctx, {
3176
+ method: "DELETE",
3177
+ path: this.keyPath(key),
3178
+ signal: opts.signal
3179
+ });
3180
+ return { deleted: true, id: key };
3181
+ }
3182
+ /**
3183
+ * Store a blob under `name` (the KV key). This is the create path behind
3184
+ * `DataFolder.set()` / `setJSON()`, which always upload a `text/plain` blob —
3185
+ * we read the blob as **text** and PUT it as the key's `{value}`. Binary
3186
+ * blobs are stored as their UTF-8 text (the KV PUT route is JSON-only); use
3187
+ * `set()`/`setJSON()` for structured data.
3188
+ */
3189
+ async upload(input, opts = {}) {
3190
+ if (input.parent_id && input.parent_id !== this.id) {
3191
+ throw unsupported("upload into a subfolder");
3192
+ }
3193
+ const name = input.name ?? (input.file instanceof globalThis.File ? input.file.name : void 0);
3194
+ if (!name) {
3195
+ throw new InvalidRequestError({
3196
+ message: "upload requires a name (the KV key)",
3197
+ status: 422,
3198
+ body: null
3199
+ });
3200
+ }
3201
+ const text = await input.file.text();
3202
+ await request(this.ctx, {
3203
+ method: "PUT",
3204
+ path: this.keyPath(name),
3205
+ body: { value: text },
3206
+ signal: opts.signal
3207
+ });
3208
+ return { id: name, name };
3209
+ }
3210
+ /** No KV analogue — the namespace is flat. */
3211
+ createFolder(_input, _opts = {}) {
3212
+ return Promise.reject(unsupported("createFolder"));
3213
+ }
3214
+ /** No KV analogue — there are no folders to move between. */
3215
+ move(_key, _parentId, _opts = {}) {
3216
+ return Promise.reject(unsupported("move"));
3217
+ }
3218
+ };
3219
+ function keyToFile(key) {
3220
+ return { id: key, name: key };
3221
+ }
3222
+ function valueToString(value) {
3223
+ if (typeof value === "string") return value;
3224
+ if (value === null || value === void 0) return "";
3225
+ return JSON.stringify(value);
3226
+ }
3227
+ function unsupported(surface) {
3228
+ return new InvalidRequestError({
3229
+ message: `\`${surface}\` is not supported by the browser-app data KV \u2014 it is a flat per-app key/value store with no folders. Use the name-based API (set/get/has/setJSON/getJSON/remove).`,
3230
+ status: 422,
3231
+ body: null
3232
+ });
3233
+ }
3234
+
3235
+ // src/api/data.ts
3236
+ var DataFolder = class {
3237
+ /**
3238
+ * Two construction shapes:
3239
+ *
3240
+ * - `new DataFolder(ctx, appResourceId)` — builds a {@link RemoteDataStore}
3241
+ * internally, scoped by the app's RESOURCE id (the `{appResId}` segment of
3242
+ * the KV path), NOT a Drive folder id. The normal path.
3243
+ * - `new DataFolder(store)` — inject any {@link DataStore} (e.g. a fake in
3244
+ * tests).
3245
+ */
3246
+ constructor(ctxOrStore, appResourceId) {
3247
+ /** name (lowercased) → fileId, populated lazily by list() / set(). */
3248
+ this.nameCache = /* @__PURE__ */ new Map();
3249
+ this.nameCacheLoaded = false;
3250
+ if (isDataStore(ctxOrStore)) {
3251
+ this.store = ctxOrStore;
3252
+ } else {
3253
+ this.store = new RemoteDataStore(ctxOrStore, appResourceId ?? "");
3254
+ }
3255
+ }
3256
+ /** The data folder's id — useful for passing to other SDK calls. */
3257
+ get id() {
3258
+ return this.store.id;
3259
+ }
3260
+ // ---------------------------------------------------------------------------
3261
+ // Raw fileId-based API.
3262
+ // ---------------------------------------------------------------------------
3263
+ /** List every file in the data folder (top level only, excluding subfolders' contents). */
3264
+ async list(opts = {}) {
3265
+ const files = await this.store.list(opts);
3266
+ this.rebuildNameCache(files);
3267
+ return files;
3268
+ }
3269
+ /** List a specific subfolder within the data folder. */
3270
+ listFolder(parentId, opts = {}) {
3271
+ return this.store.listFolder(parentId, opts);
3272
+ }
3273
+ /** Read a file's raw text content by id. UTF-8 decoded. */
3274
+ read(fileId, opts = {}) {
3275
+ return this.store.read(fileId, opts);
3276
+ }
3277
+ /** Read and parse as JSON. */
3278
+ async readJSON(fileId, opts = {}) {
3279
+ const text = await this.read(fileId, opts);
3280
+ return JSON.parse(text);
3281
+ }
3282
+ /** Read raw bytes as a Blob. Use for binary files stored in data folder. */
3283
+ readBlob(fileId, opts = {}) {
3284
+ return this.store.readBlob(fileId, opts);
3285
+ }
3286
+ /** PATCH content on an existing file id (optimistic concurrency via opts). */
3287
+ write(fileId, content, opts = {}) {
3288
+ return this.store.write(fileId, content, opts);
3289
+ }
3290
+ /** Stringify and PATCH. */
3291
+ writeJSON(fileId, data, opts = {}) {
3292
+ return this.write(fileId, JSON.stringify(data, null, 2), opts);
3293
+ }
3294
+ /** Move the file to trash. */
3295
+ async delete(fileId, opts = {}) {
3296
+ await this.store.delete(fileId, opts);
3297
+ for (const [k, v] of this.nameCache) {
3298
+ if (v === fileId) this.nameCache.delete(k);
3299
+ }
3300
+ }
3301
+ // ---------------------------------------------------------------------------
3302
+ // Upload / folder / move — scoped to the data folder.
3303
+ // ---------------------------------------------------------------------------
3304
+ /**
3305
+ * Multipart upload a Blob or File into the data folder (or a subfolder of
3306
+ * it). Returns the upload record. Use this for binary data; for text/JSON
3307
+ * prefer the name-based `set()` / `setJSON()`.
3308
+ */
3309
+ upload(input, opts = {}) {
3310
+ return this.store.upload(
3311
+ {
3312
+ file: input.file,
3313
+ name: input.name,
3314
+ parent_id: input.parent_id,
3315
+ skip_if_exists: input.skip_if_exists
3316
+ },
3317
+ opts
3318
+ );
3319
+ }
3320
+ /**
3321
+ * Create (or get) a subfolder inside the data folder. Returns the folder
3322
+ * record — store the `id` to use as `parent_id` for nested uploads /
3323
+ * `createFolder` calls. Idempotent by name at the given parent.
3324
+ */
3325
+ createFolder(input, opts = {}) {
3326
+ return this.store.createFolder(
3327
+ {
3328
+ name: input.name,
3329
+ parent_id: input.parent_id,
3330
+ icon: input.icon
3331
+ },
3332
+ opts
3333
+ );
3334
+ }
3335
+ /**
3336
+ * Move a file or subfolder within the data folder. Pass the data folder's
3337
+ * own id (or omit `parentId`) to move it back to the data-folder root.
3338
+ */
3339
+ move(fileId, parentId, opts = {}) {
3340
+ return this.store.move(
3341
+ fileId,
3342
+ parentId === void 0 ? this.store.id : parentId,
3343
+ opts
3344
+ );
3345
+ }
3346
+ // ---------------------------------------------------------------------------
3347
+ // Name-based convenience API.
3348
+ // ---------------------------------------------------------------------------
3349
+ /**
3350
+ * Upsert a file by name. Creates it on first call, PATCHes it on
3351
+ * subsequent calls. Returns the resolved file metadata.
3352
+ */
3353
+ async set(name, content, opts = {}) {
3354
+ const existing = await this.resolve(name, opts);
3355
+ if (existing) {
3356
+ return this.write(existing.id, content, opts);
3357
+ }
3358
+ return this.createByName(name, content, opts);
3359
+ }
3360
+ /** Stringify-and-upsert. */
3361
+ setJSON(name, data, opts = {}) {
3362
+ return this.set(name, JSON.stringify(data, null, 2), opts);
3363
+ }
3364
+ /**
3365
+ * Read a file by name. Returns `null` if the file doesn't exist (rather
3366
+ * than throwing) — this is the common "first-run preferences" case.
3367
+ */
3368
+ async get(name, opts = {}) {
3369
+ const ref = await this.resolve(name, opts);
3370
+ if (!ref) return null;
3371
+ try {
3372
+ return await this.read(ref.id, opts);
3373
+ } catch (err) {
3374
+ if (err instanceof NotFoundError) {
3375
+ this.nameCache.delete(name.toLowerCase());
3376
+ const retry = await this.resolve(name, opts);
3377
+ if (!retry) return null;
3378
+ return this.read(retry.id, opts);
3379
+ }
3380
+ throw err;
3381
+ }
3382
+ }
3383
+ /** Read + parse JSON, or null if the file doesn't exist. */
3384
+ async getJSON(name, opts = {}) {
3385
+ const text = await this.get(name, opts);
3386
+ if (text === null) return null;
3387
+ return JSON.parse(text);
3388
+ }
3389
+ /** Does a file with this name exist? */
3390
+ async has(name, opts = {}) {
3391
+ return await this.resolve(name, opts) !== null;
3392
+ }
3393
+ /**
3394
+ * Delete a file by name. Returns `true` if something was deleted, `false`
3395
+ * if the file didn't exist.
3396
+ */
3397
+ async remove(name, opts = {}) {
3398
+ const ref = await this.resolve(name, opts);
3399
+ if (!ref) return false;
3400
+ await this.delete(ref.id, opts);
3401
+ return true;
3402
+ }
3403
+ /**
3404
+ * Find the file id for a given name, or null. Populates the name→id cache
3405
+ * on first call.
3406
+ */
3407
+ async resolve(name, opts = {}) {
3408
+ const normalized = name.toLowerCase();
3409
+ if (!this.nameCacheLoaded) {
3410
+ const files = await this.list(opts);
3411
+ const hit = files.find(
3412
+ (f) => (f.name ?? "").toLowerCase() === normalized
3413
+ );
3414
+ return hit ?? null;
3415
+ }
3416
+ const cachedId = this.nameCache.get(normalized);
3417
+ if (!cachedId) return null;
3418
+ try {
3419
+ return await this.store.getMetadata(cachedId, opts);
3420
+ } catch (err) {
3421
+ if (err instanceof NotFoundError) {
3422
+ this.nameCache.delete(normalized);
3423
+ return null;
3424
+ }
3425
+ throw err;
3426
+ }
3427
+ }
3428
+ /** Drop the name→id cache. Next call will re-list. */
3429
+ invalidate() {
3430
+ this.nameCache.clear();
3431
+ this.nameCacheLoaded = false;
3432
+ }
3433
+ // ---------------------------------------------------------------------------
3434
+ // Internal helpers.
3435
+ // ---------------------------------------------------------------------------
3436
+ /**
3437
+ * Create a new file at the data-folder root via multipart upload. The
3438
+ * `skip_if_exists` flag hedges against concurrent creates; if another tab
3439
+ * wins the race, we re-list and PATCH instead.
3440
+ */
3441
+ async createByName(name, content, opts) {
3442
+ try {
3443
+ const res = await this.store.upload(
3444
+ {
3445
+ file: new Blob([content], { type: "text/plain" }),
3446
+ name,
3447
+ parent_id: this.store.id,
3448
+ skip_if_exists: true
3449
+ },
3450
+ opts
3451
+ );
3452
+ this.nameCache.set(name.toLowerCase(), res.id);
3453
+ return res;
3454
+ } catch (err) {
3455
+ const normalized = name.toLowerCase();
3456
+ const files = await this.list(opts);
3457
+ const winner = files.find(
3458
+ (f) => (f.name ?? "").toLowerCase() === normalized
3459
+ );
3460
+ if (winner) return this.write(winner.id, content, opts);
3461
+ throw err;
3462
+ }
3463
+ }
3464
+ rebuildNameCache(files) {
3465
+ this.nameCache.clear();
3466
+ for (const f of files) {
3467
+ if (!f.name || !f.id) continue;
3468
+ this.nameCache.set(f.name.toLowerCase(), f.id);
3469
+ }
3470
+ this.nameCacheLoaded = true;
3471
+ }
3472
+ };
3473
+ function isDataStore(x) {
3474
+ return typeof x.write === "function" && typeof x.upload === "function" && typeof x.getMetadata === "function";
3475
+ }
3476
+
3477
+ // src/client.ts
3478
+ var IdaptClient2 = class {
3479
+ constructor(deps) {
3480
+ this.deps = deps;
3481
+ const cred = deps.credential;
3482
+ this.mode = "remote";
3483
+ const auth = cred.auth ?? (cred.key ? "bearer" : "cookie");
3484
+ const ctx = {
3485
+ apiUrl: cred.apiUrl,
3486
+ key: cred.key ?? "",
3487
+ auth,
3488
+ fetch: deps.fetch
3489
+ };
3490
+ this.ctx = ctx;
3491
+ const dataCtx = ctx;
3492
+ let v1Ctx = ctx;
3493
+ if (auth === "cookie") {
3494
+ const mintToken = this.makeAppTokenMinter(ctx.apiUrl, deps.fetch);
3495
+ v1Ctx = {
3496
+ ...ctx,
3497
+ getToken: mintToken.get,
3498
+ onUnauthorized: mintToken.invalidate
3499
+ };
3500
+ }
3501
+ const appResourceId = deriveAppId(cred, deps.browserAppDomain);
3502
+ this.app = new AppFolder(v1Ctx, cred.appFolderId);
3503
+ this.data = new DataFolder(dataCtx, appResourceId);
3504
+ this.user = new UserApi(v1Ctx);
3505
+ this.files = new FilesApi(v1Ctx);
3506
+ this.agents = new AgentsApi(v1Ctx);
3507
+ this.chats = new ChatsApi(v1Ctx);
3508
+ this.workspaces = new WorkspacesApi(v1Ctx);
3509
+ this.triggers = new TriggersApi(v1Ctx);
3510
+ this.computers = new ComputersApi(v1Ctx);
3511
+ this.secrets = new SecretsApi(v1Ctx);
3512
+ this.kv = new DatastoreApi(v1Ctx);
3513
+ this.blobs = new BlobsApi(v1Ctx);
3514
+ this.tables = new TablesApi(v1Ctx);
3515
+ this.realtime = new RealtimeApi(v1Ctx);
3516
+ this.apiKeys = new ApiKeysApi(v1Ctx);
3517
+ this.notifications = new NotificationsApi(v1Ctx);
3518
+ this.settings = new SettingsApi(v1Ctx);
3519
+ this.subscription = new SubscriptionApi(v1Ctx);
3520
+ this.sharing = new SharingApi(v1Ctx);
3521
+ this.store = new StoreApi(v1Ctx);
3522
+ this.models = new ModelsApi(v1Ctx);
3523
+ this.providerEndpoints = new ProviderEndpointsApi(v1Ctx);
3524
+ this.images = new ImagesApi(v1Ctx);
3525
+ this.audio = new AudioApi(v1Ctx);
3526
+ this.code = new CodeRunsApi(v1Ctx);
3527
+ this.search = new SearchApi(v1Ctx);
3528
+ this.web = new WebSearchApi(v1Ctx);
3529
+ this.guide = new GuideApi(v1Ctx);
3530
+ this.docs = new DocsApi(v1Ctx);
3531
+ this.meta = {
3532
+ appId: appResourceId,
3533
+ apiUrl: cred.apiUrl,
3534
+ appFolderId: cred.appFolderId,
3535
+ dataFolderId: cred.dataFolderId,
3536
+ mode: this.mode
3537
+ };
3538
+ }
3539
+ /**
3540
+ * The `ap_`/`uk_` key — use only for raw v1 calls the SDK doesn't wrap.
3541
+ * In cookie mode there is no raw key — it lives only in the httpOnly app-key
3542
+ * cookie the browser attaches automatically — so this throws; use the SDK
3543
+ * surfaces (or `getAuthToken()` for ad-hoc bearer use) instead.
3544
+ */
3545
+ getApiKey() {
3546
+ if (this.ctx.auth === "cookie" || !this.ctx.key) {
3547
+ throw new Error(
3548
+ "getApiKey() is unavailable in cookie mode \u2014 the app key lives only in the httpOnly cookie. Use the SDK surfaces or getAuthToken() instead."
3549
+ );
3550
+ }
3551
+ return this.ctx.key;
3552
+ }
3553
+ /** API base URL the client targets. */
3554
+ getApiUrl() {
3555
+ return this.ctx.apiUrl;
3556
+ }
3557
+ /**
3558
+ * Mint a short-lived JWT for ad-hoc Bearer use (custom fetch calls,
3559
+ * third-party libraries, WebSocket protocols). Expires after ~15 min —
3560
+ * cache in memory, never persist to localStorage.
3561
+ *
3562
+ * Two mint paths depending on auth mode:
3563
+ * - **bearer/library** — the raw `uk_`/`ap_` key authenticates better-auth's
3564
+ * `/api/auth/token` directly.
3565
+ * - **cookie** — the iframe carries only the httpOnly `__Secure-idapt_app_key`
3566
+ * cookie, which better-auth's session-based `/api/auth/token` can't see.
3567
+ * We mint via `/api/browser-app/token` instead (resolves the app principal
3568
+ * from that cookie). The request rides `credentials: "include"` and sends
3569
+ * NO empty `Bearer` header.
3570
+ */
3571
+ async getAuthToken() {
3572
+ if (this.ctx.auth === "cookie") {
3573
+ return (await this.fetchAppToken(this.ctx.apiUrl, this.deps.fetch)).token;
3574
+ }
3575
+ const doFetch = this.deps.fetch ?? globalThis.fetch;
3576
+ if (!doFetch) throw new Error("No fetch implementation available");
3577
+ const res = await doFetch(`${this.ctx.apiUrl}/api/auth/token`, {
3578
+ method: "GET",
3579
+ headers: {
3580
+ Authorization: `Bearer ${this.ctx.key}`,
3581
+ "Sec-Fetch-Site": "same-origin"
3582
+ }
3583
+ });
3584
+ if (!res.ok) throw new Error(`getAuthToken failed: ${res.status}`);
3585
+ const body = await res.json();
3586
+ if (!body.token) throw new Error("getAuthToken: missing token in response");
3587
+ return body.token;
3588
+ }
3589
+ /**
3590
+ * Low-level mint against `/api/browser-app/token` — exchanges the httpOnly
3591
+ * app cookie for a short-lived `idapt-api` JWT. Used by the cookie-mode v1
3592
+ * surfaces' token provider AND by `getAuthToken()` in cookie mode.
3593
+ *
3594
+ * Carries the app cookie via `credentials: "include"`; sends `Sec-Fetch-Site:
3595
+ * same-origin` so the server's CSRF guard accepts it (the SDK only ever runs
3596
+ * same-origin under the app subdomain in cookie mode).
3597
+ */
3598
+ async fetchAppToken(apiUrl, fetchImpl) {
3599
+ const doFetch = fetchImpl ?? globalThis.fetch;
3600
+ if (!doFetch) throw new Error("No fetch implementation available");
3601
+ const res = await doFetch(`${apiUrl}/api/browser-app/token`, {
3602
+ method: "GET",
3603
+ headers: { "Sec-Fetch-Site": "same-origin" },
3604
+ credentials: "include"
3605
+ });
3606
+ if (!res.ok) throw new Error(`app token mint failed: ${res.status}`);
3607
+ const body = await res.json();
3608
+ if (!body.token) throw new Error("app token mint: missing token");
3609
+ const expiresAt = typeof body.expiresAt === "number" ? body.expiresAt : Date.now();
3610
+ return { token: body.token, expiresAt };
3611
+ }
3612
+ /**
3613
+ * Build a lazily-minting, self-refreshing app-token provider for the
3614
+ * cookie-mode v1 contexts. Caches the JWT until ~30 s before its stated
3615
+ * expiry; `invalidate()` (wired to the request layer's 401 hook) drops the
3616
+ * cache so the next call re-mints.
3617
+ */
3618
+ makeAppTokenMinter(apiUrl, fetchImpl) {
3619
+ const SKEW_MS = 3e4;
3620
+ let cached = null;
3621
+ let inflight = null;
3622
+ const get = async () => {
3623
+ if (cached && cached.expiresAt - SKEW_MS > Date.now()) {
3624
+ return cached.token;
3625
+ }
3626
+ if (!inflight) {
3627
+ inflight = this.fetchAppToken(apiUrl, fetchImpl).finally(() => {
3628
+ inflight = null;
3629
+ });
3630
+ }
3631
+ const minted = await inflight;
3632
+ cached = minted;
3633
+ return minted.token;
3634
+ };
3635
+ const invalidate = () => {
3636
+ cached = null;
3637
+ };
3638
+ return { get, invalidate };
3639
+ }
3640
+ /**
3641
+ * Request additional scopes at runtime. Navigates to the consent page;
3642
+ * the returned promise never resolves because the page is navigating away.
3643
+ *
3644
+ * The credential doesn't change — principal.authorization is broadened
3645
+ * server-side. When the user lands back in the app, the cached key now
3646
+ * has the new scope.
3647
+ */
3648
+ escalate(permissions) {
3649
+ const appId = this.meta.appId;
3650
+ const url = `${this.ctx.apiUrl.replace(/\/+$/, "")}/apps/${encodeURIComponent(appId)}?standalone_return=1`;
3651
+ this.navigate(url);
3652
+ return new Promise(() => {
3653
+ });
3654
+ }
3655
+ /**
3656
+ * No-op in the cookie-based flow — the `__idapt_app_key` cookie is
3657
+ * httpOnly + server-managed, so the SDK can't clear it from JS. To end a
3658
+ * hosted session, revoke the credential from the main-app Connected Apps
3659
+ * settings.
3660
+ */
3661
+ disconnect() {
3662
+ }
3663
+ navigate(url) {
3664
+ if (this.deps.navigate) {
3665
+ this.deps.navigate(url);
3666
+ return;
3667
+ }
3668
+ if (typeof window !== "undefined") {
3669
+ window.location.href = url;
3670
+ }
3671
+ }
3672
+ };
3673
+ function deriveAppId(cred, _browserAppDomain) {
3674
+ if (cred.appResourceId) return cred.appResourceId;
3675
+ if (typeof window === "undefined") return "";
3676
+ try {
3677
+ const host = window.location.hostname;
3678
+ return host.split(".")[0]?.toLowerCase() ?? "";
3679
+ } catch {
3680
+ return cred.appFolderId;
3681
+ }
3682
+ }
3683
+
3684
+ // src/detect.ts
3685
+ function detectAppId(browserAppDomain, loc) {
3686
+ const location = loc ?? safeWindowLocation();
3687
+ if (!location) return "";
3688
+ if (browserAppDomain && location.hostname.endsWith(`.${browserAppDomain}`)) {
3689
+ const label = location.hostname.slice(
3690
+ 0,
3691
+ location.hostname.length - browserAppDomain.length - 1
3692
+ );
3693
+ return label.toLowerCase();
3694
+ }
3695
+ const first = location.hostname.split(".")[0];
3696
+ return first ? first.toLowerCase() : "";
3697
+ }
3698
+ function safeWindowLocation() {
3699
+ if (typeof window === "undefined") return void 0;
3700
+ return window.location;
3701
+ }
3702
+
3703
+ // src/connect.ts
3704
+ var NOT_ON_IDAPT = "idapt SDK: not connected \u2014 this app must run on idapt";
3705
+ var LOOP_KEY = "idapt:reauth-at";
3706
+ var LOOP_WINDOW_MS = 3e4;
3707
+ function sessionStorageSafe() {
3708
+ try {
3709
+ if (typeof window === "undefined") return null;
3710
+ return window.sessionStorage ?? null;
3711
+ } catch {
3712
+ return null;
3713
+ }
3714
+ }
3715
+ function readLoopMarker() {
3716
+ const ss = sessionStorageSafe();
3717
+ if (!ss) return 0;
3718
+ try {
3719
+ const raw = ss.getItem(LOOP_KEY);
3720
+ if (!raw) return 0;
3721
+ const n = Number(raw);
3722
+ return Number.isFinite(n) ? n : 0;
3723
+ } catch {
3724
+ return 0;
3725
+ }
3726
+ }
3727
+ function writeLoopMarker(ts) {
3728
+ const ss = sessionStorageSafe();
3729
+ if (!ss) return;
3730
+ try {
3731
+ ss.setItem(LOOP_KEY, String(ts));
3732
+ } catch {
3733
+ }
3734
+ }
3735
+ function clearLoopMarker() {
3736
+ const ss = sessionStorageSafe();
3737
+ if (!ss) return;
3738
+ try {
3739
+ ss.removeItem(LOOP_KEY);
3740
+ } catch {
3741
+ }
3742
+ }
3743
+ function makeConnect(defaults) {
3744
+ return async function connect3(options = {}) {
3745
+ const apiUrl = options.apiUrl || defaults.apiUrl;
3746
+ const browserAppDomain = options.browserAppDomain !== void 0 ? options.browserAppDomain : defaults.browserAppDomain;
3747
+ const location = resolveLocation(options);
3748
+ if (options.key) {
3749
+ if (!apiUrl) {
3750
+ return Promise.reject(new Error("connect({ key }) requires `apiUrl`"));
3751
+ }
3752
+ return new IdaptClient2({
3753
+ credential: {
3754
+ key: options.key,
3755
+ apiUrl,
3756
+ appResourceId: detectAppId(browserAppDomain, location) || void 0,
3757
+ appFolderId: "",
3758
+ dataFolderId: "",
3759
+ mode: "remote"
3760
+ },
3761
+ browserAppDomain,
3762
+ fetch: options.fetch
3763
+ });
3764
+ }
3765
+ const boot = await readBootstrap(options);
3766
+ if (boot.credential) {
3767
+ clearLoopMarker();
3768
+ return new IdaptClient2({
3769
+ credential: boot.credential,
3770
+ browserAppDomain,
3771
+ fetch: options.fetch
3772
+ });
3773
+ }
3774
+ if (typeof window === "undefined") {
3775
+ return Promise.reject(
3776
+ new Error("connect() requires a browser environment")
3777
+ );
3778
+ }
3779
+ if (!boot.reachable && !boot.authorizeUrl) {
3780
+ return Promise.reject(new Error(NOT_ON_IDAPT));
3781
+ }
3782
+ const lastAttempt = readLoopMarker();
3783
+ if (lastAttempt && Date.now() - lastAttempt < LOOP_WINDOW_MS) {
3784
+ clearLoopMarker();
3785
+ return Promise.reject(
3786
+ new Error(
3787
+ "idapt: connection failed after authorization. The app-key cookie was not delivered back to the server \u2014 this usually means third-party cookies are blocked, an extension is stripping the request, or a CDN is filtering the cookie. Open the app directly in a new tab, or disable third-party cookie blocking for this site."
3788
+ )
3789
+ );
3790
+ }
3791
+ const isEmbedded = (() => {
3792
+ try {
3793
+ return !!window.parent && window.parent !== window;
3794
+ } catch {
3795
+ return false;
3796
+ }
3797
+ })();
3798
+ if (isEmbedded) {
3799
+ try {
3800
+ window.parent.postMessage({ type: "idapt:reauth-needed" }, "*");
3801
+ } catch {
3802
+ }
3803
+ const err2 = new Error(
3804
+ "idapt: bootstrap 401 inside iframe \u2014 waiting for parent to re-provision"
3805
+ );
3806
+ err2.__redirecting = true;
3807
+ return Promise.reject(err2);
3808
+ }
3809
+ const target = buildAuthorizeUrl({
3810
+ serverHint: boot.authorizeUrl,
3811
+ apiUrl,
3812
+ browserAppDomain,
3813
+ location
3814
+ });
3815
+ writeLoopMarker(Date.now());
3816
+ window.location.href = target;
3817
+ const err = new Error(
3818
+ "idapt: redirecting to authorize"
3819
+ );
3820
+ err.__redirecting = true;
3821
+ return Promise.reject(err);
3822
+ };
3823
+ }
3824
+ async function readBootstrap(options) {
3825
+ const empty = {
3826
+ credential: null,
3827
+ authorizeUrl: null,
3828
+ reachable: false
3829
+ };
3830
+ if (typeof window === "undefined") return empty;
3831
+ const fetcher = options.fetch ?? globalThis.fetch;
3832
+ if (typeof fetcher !== "function") return empty;
3833
+ try {
3834
+ const res = await fetcher("/__idapt__/bootstrap", {
3835
+ credentials: "include"
3836
+ });
3837
+ if (!res.ok) {
3838
+ if (res.status === 401) {
3839
+ const hdr = typeof res.headers?.get === "function" ? res.headers.get("x-idapt-authorize-url") : null;
3840
+ return { credential: null, authorizeUrl: hdr, reachable: true };
3841
+ }
3842
+ return empty;
3843
+ }
3844
+ const body = await res.json().catch(() => null);
3845
+ const appResourceId = typeof body?.appResourceId === "string" ? body.appResourceId : null;
3846
+ if (!appResourceId) {
3847
+ return { credential: null, authorizeUrl: null, reachable: true };
3848
+ }
3849
+ return {
3850
+ credential: {
3851
+ key: null,
3852
+ apiUrl: window.location.origin,
3853
+ appResourceId,
3854
+ appFolderId: "",
3855
+ dataFolderId: "",
3856
+ mode: "remote",
3857
+ auth: "cookie"
3858
+ },
3859
+ authorizeUrl: null,
3860
+ reachable: true
3861
+ };
3862
+ } catch {
3863
+ return empty;
3864
+ }
3865
+ }
3866
+ function buildAuthorizeUrl(params) {
3867
+ const { serverHint, apiUrl, browserAppDomain, location } = params;
3868
+ if (serverHint && /^https?:\/\//i.test(serverHint)) {
3869
+ return serverHint;
3870
+ }
3871
+ if (serverHint?.startsWith("/")) {
3872
+ const base2 = apiUrl.replace(/\/+$/, "");
3873
+ const url = new URL(serverHint, base2);
3874
+ url.searchParams.set("standalone_return", "1");
3875
+ return url.toString();
3876
+ }
3877
+ const appId = detectAppId(browserAppDomain, location);
3878
+ const base = apiUrl.replace(/\/+$/, "");
3879
+ return `${base}/apps/${encodeURIComponent(appId)}?standalone_return=1`;
3880
+ }
3881
+ function resolveLocation(options) {
3882
+ if (options.location) return options.location;
3883
+ if (typeof window !== "undefined") return window.location;
3884
+ return {
3885
+ hostname: "",
3886
+ pathname: "",
3887
+ origin: "",
3888
+ href: "",
3889
+ search: ""
3890
+ };
3891
+ }
3892
+
3893
+ // src/index.ts
3894
+ function connect2(options = {}) {
3895
+ return makeConnect({
3896
+ apiUrl: options.apiUrl ?? "",
3897
+ browserAppDomain: options.browserAppDomain ?? ""
3898
+ })(options);
3899
+ }
3900
+ var IdaptContext = react.createContext({ client: null, error: null });
3901
+ function IdaptProvider({
3902
+ children,
3903
+ options,
3904
+ client: injected
3905
+ }) {
3906
+ const [state, setState] = react.useState(() => ({
3907
+ client: injected ?? null,
3908
+ error: null
3909
+ }));
3910
+ const optsRef = react.useRef(options);
3911
+ optsRef.current = options;
3912
+ react.useEffect(() => {
3913
+ if (injected) return;
3914
+ let cancelled = false;
3915
+ connect2(optsRef.current ?? {}).then((c) => {
3916
+ if (cancelled) return;
3917
+ setState({ client: c, error: null });
3918
+ }).catch((err) => {
3919
+ if (cancelled) return;
3920
+ setState({
3921
+ client: null,
3922
+ error: err instanceof Error ? err : new Error(String(err))
3923
+ });
3924
+ });
3925
+ return () => {
3926
+ cancelled = true;
3927
+ };
3928
+ }, [injected]);
3929
+ return /* @__PURE__ */ jsxRuntime.jsx(IdaptContext.Provider, { value: state, children });
3930
+ }
3931
+ function useIdapt() {
3932
+ return react.useContext(IdaptContext).client;
3933
+ }
3934
+ function useIdaptError() {
3935
+ return react.useContext(IdaptContext).error;
3936
+ }
3937
+ function useAppFile(fileId, options = {}) {
3938
+ const client = useIdapt();
3939
+ const { json = false } = options;
3940
+ const [data, setData] = react.useState(null);
3941
+ const [loading, setLoading] = react.useState(true);
3942
+ const [error, setError] = react.useState(null);
3943
+ const mounted = react.useRef(true);
3944
+ react.useEffect(
3945
+ () => () => {
3946
+ mounted.current = false;
3947
+ },
3948
+ []
3949
+ );
3950
+ const load = react.useCallback(
3951
+ async (signal) => {
3952
+ if (!client || !fileId) {
3953
+ setLoading(false);
3954
+ return;
3955
+ }
3956
+ setLoading(true);
3957
+ setError(null);
3958
+ try {
3959
+ const value = json ? await client.app.readJSON(fileId, { signal }) : await client.app.read(fileId, { signal });
3960
+ if (!mounted.current || signal?.aborted) return;
3961
+ setData(value);
3962
+ } catch (err) {
3963
+ if (!mounted.current || signal?.aborted) return;
3964
+ setError(err instanceof Error ? err : new Error(String(err)));
3965
+ } finally {
3966
+ if (mounted.current && !signal?.aborted) setLoading(false);
3967
+ }
3968
+ },
3969
+ [client, fileId, json]
3970
+ );
3971
+ react.useEffect(() => {
3972
+ const controller = new AbortController();
3973
+ load(controller.signal);
3974
+ return () => controller.abort();
3975
+ }, [load]);
3976
+ return { data, loading, error, refresh: load };
3977
+ }
3978
+ function useChannel(key, onEvent) {
3979
+ const client = useIdapt();
3980
+ const [error, setError] = react.useState(null);
3981
+ const onEventRef = react.useRef(onEvent);
3982
+ onEventRef.current = onEvent;
3983
+ react.useEffect(() => {
3984
+ if (!client || !key) return;
3985
+ setError(null);
3986
+ const unsubscribe = client.realtime.subscribe(
3987
+ [key],
3988
+ (event) => onEventRef.current(event),
3989
+ { onError: (err) => setError(err) }
3990
+ );
3991
+ return unsubscribe;
3992
+ }, [client, key]);
3993
+ return { error };
3994
+ }
3995
+ function useDataFile(name, options = {}) {
3996
+ const client = useIdapt();
3997
+ const { json = true, initial = null, onChange } = options;
3998
+ const [data, setData] = react.useState(initial);
3999
+ const [loading, setLoading] = react.useState(true);
4000
+ const [error, setError] = react.useState(null);
4001
+ const mounted = react.useRef(true);
4002
+ react.useEffect(
4003
+ () => () => {
4004
+ mounted.current = false;
4005
+ },
4006
+ []
4007
+ );
4008
+ const load = react.useCallback(
4009
+ async (signal) => {
4010
+ if (!client) return;
4011
+ setLoading(true);
4012
+ setError(null);
4013
+ try {
4014
+ const value = json ? await client.data.getJSON(name, { signal }) : await client.data.get(name, { signal });
4015
+ if (!mounted.current || signal?.aborted) return;
4016
+ setData(value);
4017
+ onChange?.(value);
4018
+ } catch (err) {
4019
+ if (!mounted.current || signal?.aborted) return;
4020
+ setError(err instanceof Error ? err : new Error(String(err)));
4021
+ } finally {
4022
+ if (mounted.current && !signal?.aborted) setLoading(false);
4023
+ }
4024
+ },
4025
+ [client, name, json, onChange]
4026
+ );
4027
+ react.useEffect(() => {
4028
+ const controller = new AbortController();
4029
+ load(controller.signal);
4030
+ return () => controller.abort();
4031
+ }, [load]);
4032
+ const save = react.useCallback(
4033
+ async (value) => {
4034
+ if (!client) throw new Error("Idapt client not connected");
4035
+ if (json) {
4036
+ await client.data.setJSON(name, value);
4037
+ } else {
4038
+ await client.data.set(name, value);
4039
+ }
4040
+ if (!mounted.current) return;
4041
+ setData(value);
4042
+ onChange?.(value);
4043
+ },
4044
+ [client, name, json, onChange]
4045
+ );
4046
+ const remove = react.useCallback(async () => {
4047
+ if (!client) throw new Error("Idapt client not connected");
4048
+ await client.data.remove(name);
4049
+ if (!mounted.current) return;
4050
+ setData(null);
4051
+ onChange?.(null);
4052
+ }, [client, name, onChange]);
4053
+ const refresh = react.useCallback(async () => {
4054
+ if (!client) return;
4055
+ client.data.invalidate();
4056
+ await load();
4057
+ }, [client, load]);
4058
+ return { data, loading, error, save, refresh, remove };
4059
+ }
4060
+ function useLiveQuery(collectionResId, dsl = {}) {
4061
+ const client = useIdapt();
4062
+ const [records, setRecords] = react.useState([]);
4063
+ const [loading, setLoading] = react.useState(true);
4064
+ const [error, setError] = react.useState(null);
4065
+ const dslKey = JSON.stringify(dsl);
4066
+ const dslRef = react.useRef(dsl);
4067
+ dslRef.current = dsl;
4068
+ const [nonce, setNonce] = react.useState(0);
4069
+ react.useEffect(() => {
4070
+ if (!client || !collectionResId) return;
4071
+ let active = true;
4072
+ let inflight = false;
4073
+ let queued = false;
4074
+ const runQuery = async () => {
4075
+ if (inflight) {
4076
+ queued = true;
4077
+ return;
4078
+ }
4079
+ inflight = true;
4080
+ try {
4081
+ const rows = await client.tables.query(collectionResId, dslRef.current);
4082
+ if (!active) return;
4083
+ setRecords(rows);
4084
+ setError(null);
4085
+ } catch (err) {
4086
+ if (!active) return;
4087
+ setError(err instanceof Error ? err : new Error(String(err)));
4088
+ } finally {
4089
+ inflight = false;
4090
+ if (active) setLoading(false);
4091
+ if (active && queued) {
4092
+ queued = false;
4093
+ void runQuery();
4094
+ }
4095
+ }
4096
+ };
4097
+ setLoading(true);
4098
+ void runQuery();
4099
+ const unsubscribe = client.realtime.subscribe(
4100
+ [`table:${collectionResId}`],
4101
+ (event) => {
4102
+ if (event.type !== "broadcast") void runQuery();
4103
+ },
4104
+ { onError: (err) => active && setError(err) }
4105
+ );
4106
+ return () => {
4107
+ active = false;
4108
+ unsubscribe();
4109
+ };
4110
+ }, [client, collectionResId, dslKey, nonce]);
4111
+ return {
4112
+ records,
4113
+ loading,
4114
+ error,
4115
+ refresh: () => setNonce((n) => n + 1)
4116
+ };
4117
+ }
4118
+ var DEFAULT_INTERVAL_MS = 1e4;
4119
+ function usePresence(key, options = {}) {
4120
+ const client = useIdapt();
4121
+ const { meta, intervalMs = DEFAULT_INTERVAL_MS } = options;
4122
+ const [entries, setEntries] = react.useState([]);
4123
+ const [loading, setLoading] = react.useState(true);
4124
+ const [error, setError] = react.useState(null);
4125
+ const metaRef = react.useRef(meta);
4126
+ metaRef.current = meta;
4127
+ const metaKey = meta ? JSON.stringify(meta) : "";
4128
+ react.useEffect(() => {
4129
+ if (!client || !key) return;
4130
+ let active = true;
4131
+ setLoading(true);
4132
+ const tick = async () => {
4133
+ try {
4134
+ if (metaRef.current) {
4135
+ await client.realtime.heartbeat(key, { meta: metaRef.current });
4136
+ }
4137
+ const list = await client.realtime.presence(key);
4138
+ if (!active) return;
4139
+ setEntries(list);
4140
+ setError(null);
4141
+ } catch (err) {
4142
+ if (!active) return;
4143
+ setError(err instanceof Error ? err : new Error(String(err)));
4144
+ } finally {
4145
+ if (active) setLoading(false);
4146
+ }
4147
+ };
4148
+ void tick();
4149
+ const timer = setInterval(tick, intervalMs);
4150
+ return () => {
4151
+ active = false;
4152
+ clearInterval(timer);
4153
+ };
4154
+ }, [client, key, intervalMs, metaKey]);
4155
+ return { entries, loading, error };
4156
+ }
4157
+
4158
+ exports.IdaptProvider = IdaptProvider;
4159
+ exports.useAppFile = useAppFile;
4160
+ exports.useChannel = useChannel;
4161
+ exports.useDataFile = useDataFile;
4162
+ exports.useIdapt = useIdapt;
4163
+ exports.useIdaptError = useIdaptError;
4164
+ exports.useLiveQuery = useLiveQuery;
4165
+ exports.usePresence = usePresence;
4166
+ //# sourceMappingURL=react.cjs.map
4167
+ //# sourceMappingURL=react.cjs.map