@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/LICENSE +202 -0
- package/NOTICE +6 -0
- package/README.md +53 -0
- package/dist/chunk-6FSNLUVJ.js +435 -0
- package/dist/chunk-6FSNLUVJ.js.map +1 -0
- package/dist/chunk-AQZ2QVBK.js +7079 -0
- package/dist/chunk-AQZ2QVBK.js.map +1 -0
- package/dist/client-CO-P-xYI.d.cts +2213 -0
- package/dist/client-h2Wsvn7a.d.ts +2213 -0
- package/dist/data-Chus9wn2.d.cts +1714 -0
- package/dist/data-Chus9wn2.d.ts +1714 -0
- package/dist/index.cjs +7569 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +372 -0
- package/dist/index.d.ts +372 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +4167 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +150 -0
- package/dist/react.d.ts +150 -0
- package/dist/react.js +266 -0
- package/dist/react.js.map +1 -0
- package/dist/testing.cjs +862 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +113 -0
- package/dist/testing.d.ts +113 -0
- package/dist/testing.js +126 -0
- package/dist/testing.js.map +1 -0
- package/package.json +76 -0
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
|