@kivel.io/sdk 0.0.0-stage → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,523 @@
1
+ export * from './types.js';
2
+ export class KivelError extends Error {
3
+ status;
4
+ requestId;
5
+ retryAfter;
6
+ requiredPlan;
7
+ upgradeUrl;
8
+ constructor(message, status, requestId, retryAfter, requiredPlan, upgradeUrl) {
9
+ super(message);
10
+ this.status = status;
11
+ this.requestId = requestId;
12
+ this.retryAfter = retryAfter;
13
+ this.requiredPlan = requiredPlan;
14
+ this.upgradeUrl = upgradeUrl;
15
+ this.name = 'KivelError';
16
+ }
17
+ }
18
+ export class Kivel {
19
+ options;
20
+ baseUrl;
21
+ fetcher;
22
+ timeoutMs;
23
+ constructor(options = {}) {
24
+ this.options = options;
25
+ const url = new URL(options.baseUrl ?? 'https://kivel.io');
26
+ if (!['http:', 'https:'].includes(url.protocol) ||
27
+ url.username ||
28
+ url.password ||
29
+ url.pathname !== '/' ||
30
+ url.search ||
31
+ url.hash)
32
+ throw new Error('baseUrl must be an HTTP(S) origin.');
33
+ if (url.protocol !== 'https:' &&
34
+ !['localhost', '127.0.0.1', '[::1]'].includes(url.hostname))
35
+ throw new Error('Remote API origins must use HTTPS.');
36
+ this.baseUrl = url.origin;
37
+ this.fetcher = options.fetch ?? fetch;
38
+ this.timeoutMs = options.timeoutMs ?? 25000;
39
+ }
40
+ async request(method, path, body, resourceToken, headers = {}) {
41
+ const pathname = path.split('?')[0];
42
+ if ((!/^\/(?:api|mock)\//.test(path) &&
43
+ !/^\/ping\/[A-Za-z0-9_-]{32}(?:\/fail)?$/.test(path)) ||
44
+ /[\\#\x00-\x20]/.test(path) ||
45
+ pathname.split('/').some((part) => {
46
+ try {
47
+ return ['.', '..'].includes(decodeURIComponent(part)) || /%2f|%5c/i.test(part);
48
+ }
49
+ catch {
50
+ return true;
51
+ }
52
+ }))
53
+ throw new Error('Use a relative /api/ or /mock/ path without traversal.');
54
+ const requestHeaders = new Headers(headers);
55
+ requestHeaders.set('Accept', 'application/json');
56
+ if (this.options.apiKey)
57
+ requestHeaders.set('Authorization', `Bearer ${this.options.apiKey}`);
58
+ if (resourceToken)
59
+ requestHeaders.set(path.startsWith('/mock/') ? 'X-Access-Token' : 'X-Resource-Token', resourceToken);
60
+ const binary = body instanceof Uint8Array || body instanceof ArrayBuffer;
61
+ if (body !== undefined)
62
+ requestHeaders.set('Content-Type', binary ? 'application/octet-stream' : 'application/json');
63
+ const response = await this.fetcher(`${this.baseUrl}${path}`, {
64
+ method,
65
+ headers: requestHeaders,
66
+ ...(body !== undefined
67
+ ? { body: binary ? body : JSON.stringify(body) }
68
+ : {}),
69
+ redirect: 'error',
70
+ credentials: 'omit',
71
+ signal: AbortSignal.timeout(this.timeoutMs),
72
+ });
73
+ if (response.status === 204 ||
74
+ response.status === 304 ||
75
+ (method.toUpperCase() === 'HEAD' && response.ok))
76
+ return undefined;
77
+ const text = await response.text();
78
+ let value;
79
+ try {
80
+ value = JSON.parse(text);
81
+ }
82
+ catch {
83
+ if (response.ok && path.startsWith('/mock/'))
84
+ return text;
85
+ throw new KivelError('The server returned an invalid response.', response.status, response.headers.get('x-request-id') ?? undefined);
86
+ }
87
+ if (!response.ok) {
88
+ const message = value &&
89
+ typeof value === 'object' &&
90
+ 'error' in value &&
91
+ typeof value.error === 'string'
92
+ ? value.error
93
+ : `Request failed (${response.status}).`;
94
+ throw new KivelError(message, response.status, response.headers.get('x-request-id') ?? undefined, Number(response.headers.get('retry-after')) || undefined, value &&
95
+ typeof value === 'object' &&
96
+ 'requiredPlan' in value &&
97
+ typeof value.requiredPlan === 'string'
98
+ ? value.requiredPlan
99
+ : undefined, value &&
100
+ typeof value === 'object' &&
101
+ 'upgradeUrl' in value &&
102
+ typeof value.upgradeUrl === 'string'
103
+ ? value.upgradeUrl
104
+ : undefined);
105
+ }
106
+ return value;
107
+ }
108
+ tools() {
109
+ return this.request('GET', '/api/tools');
110
+ }
111
+ tool(id) {
112
+ return this.request('GET', `/api/tools/${encodeURIComponent(id)}`);
113
+ }
114
+ run(toolId, input = '', options = {}, file) {
115
+ return this.request('POST', `/api/tools/${encodeURIComponent(toolId)}/run`, {
116
+ input,
117
+ options,
118
+ ...(file ? { file } : {}),
119
+ });
120
+ }
121
+ plans() {
122
+ return this.request('GET', '/api/plans');
123
+ }
124
+ activity() {
125
+ return this.request('GET', '/api/account/activity');
126
+ }
127
+ startLogin(name, client) {
128
+ return this.request('POST', '/api/agents/login', { name, client });
129
+ }
130
+ finishLogin(deviceCode) {
131
+ return this.request('POST', '/api/agents/token', { deviceCode });
132
+ }
133
+ startTrial(name, humanEmail, client) {
134
+ return this.request('POST', '/api/agents/trial', { name, humanEmail, client });
135
+ }
136
+ agents() {
137
+ return this.request('GET', '/api/agents');
138
+ }
139
+ /** Only for accounts your human asked you to create or operate. */
140
+ addAuthenticator(input) {
141
+ return this.request('POST', '/api/authenticators', input);
142
+ }
143
+ listAuthenticators() {
144
+ return this.request('GET', '/api/authenticators');
145
+ }
146
+ getTotpCode(id) {
147
+ return this.request('GET', `/api/authenticators/${encodeURIComponent(id)}/code`);
148
+ }
149
+ renameAuthenticator(id, name) {
150
+ return this.request('PATCH', `/api/authenticators/${encodeURIComponent(id)}`, { name });
151
+ }
152
+ deleteAuthenticator(id) {
153
+ return this.request('DELETE', `/api/authenticators/${encodeURIComponent(id)}`);
154
+ }
155
+ getEmailAttachment(id, messageId, attachmentId) {
156
+ return this.request('GET', `/api/email-inboxes/${encodeURIComponent(id)}/messages/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(attachmentId)}`);
157
+ }
158
+ async downloadEmailAttachment(id, messageId, attachmentId) {
159
+ const link = await this.getEmailAttachment(id, messageId, attachmentId);
160
+ const url = new URL(link.downloadUrl);
161
+ if (url.origin !== new URL(this.baseUrl).origin ||
162
+ !url.pathname.startsWith('/api/email-inboxes/'))
163
+ throw new Error('Invalid attachment download URL.');
164
+ const response = await this.fetcher(url, {
165
+ redirect: 'error',
166
+ credentials: 'omit',
167
+ signal: AbortSignal.timeout(this.timeoutMs),
168
+ });
169
+ if (!response.ok)
170
+ throw new KivelError('Attachment link expired or unavailable.', response.status, response.headers.get('x-request-id') ?? undefined);
171
+ return new Uint8Array(await response.arrayBuffer());
172
+ }
173
+ createTestWallet(input) {
174
+ return this.request('POST', '/api/test-wallets', input);
175
+ }
176
+ createTestPaywall(input) {
177
+ return this.request('POST', '/api/test-paywalls', input);
178
+ }
179
+ createTestCheckout(input) {
180
+ return this.request('POST', '/api/test-checkouts', input);
181
+ }
182
+ testPayments(kind) {
183
+ return this.request('GET', `/api/test-${kind}`);
184
+ }
185
+ testPayment(kind, id) {
186
+ return this.request('GET', `/api/test-${kind}/${encodeURIComponent(id)}`);
187
+ }
188
+ updateTestPayment(kind, id, input) {
189
+ return this.request('PATCH', `/api/test-${kind}/${encodeURIComponent(id)}`, input);
190
+ }
191
+ deleteTestPayment(kind, id) {
192
+ return this.request('DELETE', `/api/test-${kind}/${encodeURIComponent(id)}`);
193
+ }
194
+ walletBalance(id) {
195
+ return this.testPayment('wallets', id);
196
+ }
197
+ topUpTestWallet(id, amount) {
198
+ return this.request('POST', `/api/test-wallets/${encodeURIComponent(id)}/top-up`, { amount });
199
+ }
200
+ walletPay(id, input) {
201
+ return this.request('POST', `/api/test-wallets/${encodeURIComponent(id)}/pay`, input);
202
+ }
203
+ listWalletTransactions(id, options = {}) {
204
+ const query = new URLSearchParams();
205
+ if (options.cursor)
206
+ query.set('cursor', options.cursor);
207
+ if (options.limit !== undefined)
208
+ query.set('limit', String(options.limit));
209
+ return this.request('GET', `/api/test-wallets/${encodeURIComponent(id)}/transactions?${query}`);
210
+ }
211
+ completeTestCheckout(id, input) {
212
+ return this.request('POST', `/api/test-checkouts/${encodeURIComponent(id)}/complete`, input);
213
+ }
214
+ confirmTestCheckout(id, confirmationToken) {
215
+ return this.request('POST', `/api/test-checkouts/${encodeURIComponent(id)}/confirm`, { confirmationToken });
216
+ }
217
+ saveCredential(input) {
218
+ return this.request('POST', '/api/credentials', input);
219
+ }
220
+ credentials() {
221
+ return this.request('GET', '/api/credentials');
222
+ }
223
+ credential(id) {
224
+ return this.request('GET', `/api/credentials/${encodeURIComponent(id)}`);
225
+ }
226
+ updateCredential(id, input) {
227
+ return this.request('PATCH', `/api/credentials/${encodeURIComponent(id)}`, input);
228
+ }
229
+ deleteCredential(id) {
230
+ return this.request('DELETE', `/api/credentials/${encodeURIComponent(id)}`);
231
+ }
232
+ revealCredential(id, approvalId) {
233
+ return this.request('POST', `/api/credentials/${encodeURIComponent(id)}/reveal`, approvalId ? { approvalId } : {});
234
+ }
235
+ generatePassword(length = 24) {
236
+ return this.request('POST', '/api/passwords/generate', { length });
237
+ }
238
+ createEmailInbox(settings = {}) {
239
+ return this.request('POST', '/api/email-inboxes', settings);
240
+ }
241
+ emailInboxes() {
242
+ return this.request('GET', '/api/email-inboxes');
243
+ }
244
+ clearEmails(id, token) {
245
+ return this.request('DELETE', `/api/email-inboxes/${encodeURIComponent(id)}/messages`, undefined, token);
246
+ }
247
+ sendEmail(id, input) {
248
+ const { idempotencyKey, ...body } = input;
249
+ return this.request('POST', `/api/email-inboxes/${encodeURIComponent(id)}/send`, body, undefined, { 'Idempotency-Key': idempotencyKey });
250
+ }
251
+ emailThreads(id, settings = {}) {
252
+ const query = new URLSearchParams();
253
+ if (settings.cursor)
254
+ query.set('cursor', settings.cursor);
255
+ if (settings.limit !== undefined)
256
+ query.set('limit', String(settings.limit));
257
+ const path = id
258
+ ? `/api/email-inboxes/${encodeURIComponent(id)}/threads`
259
+ : '/api/email-threads';
260
+ return this.request('GET', path + (query.size ? `?${query}` : ''));
261
+ }
262
+ emailThread(id, threadId) {
263
+ return this.request('GET', `/api/email-inboxes/${encodeURIComponent(id)}/threads/${encodeURIComponent(threadId)}`);
264
+ }
265
+ createEmailDraft(id, content) {
266
+ return this.request('POST', `/api/email-inboxes/${encodeURIComponent(id)}/drafts`, content);
267
+ }
268
+ emailDrafts(id) {
269
+ return this.request('GET', `/api/email-inboxes/${encodeURIComponent(id)}/drafts`);
270
+ }
271
+ emailDraft(id) {
272
+ return this.request('GET', `/api/email-drafts/${encodeURIComponent(id)}`);
273
+ }
274
+ submitEmailDraft(id, settings = {}) {
275
+ return this.request('POST', `/api/email-drafts/${encodeURIComponent(id)}/send`, settings);
276
+ }
277
+ deleteEmailDraft(id) {
278
+ return this.request('DELETE', `/api/email-drafts/${encodeURIComponent(id)}`);
279
+ }
280
+ emailInboxRules(id) {
281
+ return this.request('GET', `/api/email-inboxes/${encodeURIComponent(id)}/rules`);
282
+ }
283
+ waitForEvent(sources, timeoutSeconds = 20) {
284
+ return this.request('POST', '/api/events/wait', { sources, timeoutSeconds: this.waitTimeout(timeoutSeconds) });
285
+ }
286
+ emailForwarding(id, url, token) {
287
+ return this.request('PUT', `/api/email-inboxes/${encodeURIComponent(id)}/forwarding`, { url }, token);
288
+ }
289
+ emailInbox(id, token) {
290
+ return this.request('GET', `/api/email-inboxes/${encodeURIComponent(id)}`, undefined, token);
291
+ }
292
+ emails(id, token) {
293
+ return this.request('GET', `/api/email-inboxes/${encodeURIComponent(id)}/messages`, undefined, token);
294
+ }
295
+ waitForEmail(id, options = {}) {
296
+ return this.request('GET', `/api/email-inboxes/${encodeURIComponent(id)}/wait${this.waitQuery(options)}`, undefined, options.token);
297
+ }
298
+ memorySet(namespace, key, value, ttlSeconds) {
299
+ return this.request('PUT', memoryPath(namespace, key), {
300
+ value,
301
+ ttlSeconds,
302
+ });
303
+ }
304
+ memoryGet(namespace, key) {
305
+ return this.request('GET', memoryPath(namespace, key));
306
+ }
307
+ memoryList(namespace, prefix = '') {
308
+ return this.request('GET', `${memoryPath(namespace)}${query({ prefix })}`);
309
+ }
310
+ memoryDelete(namespace, key) {
311
+ return this.request('DELETE', memoryPath(namespace, key));
312
+ }
313
+ availablePhoneNumbers(areaCode) {
314
+ return this.request('GET', `/api/phone-numbers/available${query({ areaCode })}`);
315
+ }
316
+ createPhoneNumber(number, idempotencyKey) {
317
+ return this.request('POST', '/api/phone-numbers', { number }, undefined, { 'Idempotency-Key': idempotencyKey });
318
+ }
319
+ listPhoneNumbers() {
320
+ return this.request('GET', '/api/phone-numbers');
321
+ }
322
+ releasePhoneNumber(id) {
323
+ return this.request('DELETE', `/api/phone-numbers/${encodeURIComponent(id)}`);
324
+ }
325
+ listSms(id) {
326
+ return this.request('GET', `/api/phone-numbers/${encodeURIComponent(id)}/sms`);
327
+ }
328
+ waitForSms(id, options = {}) {
329
+ return this.request('GET', `/api/phone-numbers/${encodeURIComponent(id)}/wait${this.waitQuery(options)}`);
330
+ }
331
+ createSmtpInbox(settings = {}) {
332
+ return this.request('POST', `/api/smtp-inboxes${query(settings)}`);
333
+ }
334
+ smtpInbox(id) {
335
+ return this.request('GET', `/api/smtp-inboxes/${encodeURIComponent(id)}`);
336
+ }
337
+ rotateSmtpPassword(id) {
338
+ return this.request('POST', `/api/smtp-inboxes/${encodeURIComponent(id)}/rotate`);
339
+ }
340
+ createScheduledJob(config, settings = {}) {
341
+ return this.request('POST', `/api/scheduled-jobs${query(settings)}`, config);
342
+ }
343
+ scheduledJobs() {
344
+ return this.request('GET', '/api/scheduled-jobs');
345
+ }
346
+ scheduledJob(id) {
347
+ return this.request('GET', `/api/scheduled-jobs/${encodeURIComponent(id)}`);
348
+ }
349
+ cancelScheduledJob(id) {
350
+ return this.request('DELETE', `/api/scheduled-jobs/${encodeURIComponent(id)}`);
351
+ }
352
+ createStatusPage(config, settings = {}) {
353
+ return this.request('POST', `/api/status-pages${query(settings)}`, config);
354
+ }
355
+ updateStatusPage(id, config) {
356
+ return this.request('PUT', `/api/status-pages/${encodeURIComponent(id)}`, config);
357
+ }
358
+ acceptDnsChanges(id) {
359
+ return this.request('POST', `/api/monitors/${encodeURIComponent(id)}/accept-dns`);
360
+ }
361
+ createMonitor(settings) {
362
+ return this.request('POST', '/api/monitors', settings);
363
+ }
364
+ monitors() {
365
+ return this.request('GET', '/api/monitors');
366
+ }
367
+ monitor(id) {
368
+ return this.request('GET', `/api/monitors/${encodeURIComponent(id)}`);
369
+ }
370
+ pingMonitor(token, failed = false) {
371
+ if (!/^[A-Za-z0-9_-]{32}$/.test(token))
372
+ throw new Error('Provide the token from the monitor pingUrl.');
373
+ return this.request('POST', `/ping/${token}${failed ? '/fail' : ''}`);
374
+ }
375
+ pauseMonitor(id, paused = true) {
376
+ return this.request('PATCH', `/api/monitors/${encodeURIComponent(id)}`, { paused });
377
+ }
378
+ deleteMonitor(id) {
379
+ return this.request('DELETE', `/api/monitors/${encodeURIComponent(id)}`);
380
+ }
381
+ askHuman(input) {
382
+ return this.request('POST', '/api/approvals', input);
383
+ }
384
+ approvals(pendingOnly = false) {
385
+ return this.request('GET', `/api/approvals${pendingOnly ? '?status=pending' : ''}`);
386
+ }
387
+ approval(id) {
388
+ return this.request('GET', `/api/approvals/${encodeURIComponent(id)}`);
389
+ }
390
+ waitForApproval(id, timeoutSeconds) {
391
+ return this.request('GET', `/api/approvals/${encodeURIComponent(id)}/wait${this.waitQuery({ timeoutSeconds })}`);
392
+ }
393
+ inspectResource(kind, id, token) {
394
+ return this.request('GET', `/api/${kind}/${encodeURIComponent(id)}`, undefined, token);
395
+ }
396
+ updateMock(id, config, token) {
397
+ return this.request('PUT', `/api/mocks/${encodeURIComponent(id)}`, config, token);
398
+ }
399
+ resetMock(id, token) {
400
+ return this.request('POST', `/api/mocks/${encodeURIComponent(id)}/reset`, {}, token);
401
+ }
402
+ mockCalls(id, token) {
403
+ return this.request('GET', `/api/mocks/${encodeURIComponent(id)}/calls`, undefined, token);
404
+ }
405
+ setInboxForward(id, url, token) {
406
+ return this.request(url === null ? 'DELETE' : 'PUT', `/api/inboxes/${encodeURIComponent(id)}/forward`, url === null ? undefined : { url }, token);
407
+ }
408
+ inboxDeliveries(id, token) {
409
+ return this.request('GET', `/api/inboxes/${encodeURIComponent(id)}/deliveries`, undefined, token);
410
+ }
411
+ waitTimeout(timeoutSeconds) {
412
+ const ceiling = Math.max(1, Math.floor(this.timeoutMs / 1000) - 5);
413
+ return Math.min(timeoutSeconds ?? 20, ceiling, 30);
414
+ }
415
+ waitQuery(options) {
416
+ return query({
417
+ after: options.after,
418
+ timeoutSeconds: this.waitTimeout(options.timeoutSeconds),
419
+ });
420
+ }
421
+ usage() {
422
+ return this.request('GET', '/api/usage');
423
+ }
424
+ resources() {
425
+ return this.request('GET', '/api/account/resources');
426
+ }
427
+ /** Senders can POST to webhookUrl without credentials by default. Inspection stays private.
428
+ * Set visibility to private to require X-Access-Token on incoming webhooks. */
429
+ createInbox(settings = {}) {
430
+ return this.request('POST', `/api/inboxes${creationQuery({ ...settings, visibility: settings.visibility ?? 'public' })}`);
431
+ }
432
+ inspectInbox(id, token) {
433
+ return this.request('GET', `/api/inboxes/${encodeURIComponent(id)}/requests`, undefined, token);
434
+ }
435
+ /**
436
+ * Long-polls until the inbox captures a request newer than `after` (any request when omitted).
437
+ * Resolves with `timedOut: true` when nothing arrives; call again to keep waiting.
438
+ */
439
+ waitForRequests(id, options = {}) {
440
+ // Stay inside the client timeout so the server answers before the request is aborted.
441
+ const query = new URLSearchParams({
442
+ timeoutSeconds: String(this.waitTimeout(options.timeoutSeconds)),
443
+ ...(options.after ? { after: options.after } : {}),
444
+ });
445
+ return this.request('GET', `/api/inboxes/${encodeURIComponent(id)}/wait?${query}`, undefined, options.token);
446
+ }
447
+ setInboxResponse(id, response, token) {
448
+ if (response !== null &&
449
+ (!response || typeof response !== 'object' || Array.isArray(response)))
450
+ throw new Error('Provide response settings as an object, or null to reset.');
451
+ return this.request(response === null ? 'DELETE' : 'PUT', `/api/inboxes/${encodeURIComponent(id)}/response`, response ?? undefined, token);
452
+ }
453
+ createOAuthProvider(config, settings = {}) {
454
+ return this.request('POST', `/api/oauth-providers${creationQuery(settings)}`, config);
455
+ }
456
+ oauthProvider(id) {
457
+ return this.request('GET', `/api/oauth-providers/${encodeURIComponent(id)}`);
458
+ }
459
+ oauthProviders() {
460
+ return this.request('GET', '/api/oauth-providers');
461
+ }
462
+ updateOAuthProvider(id, config) {
463
+ return this.request('PUT', `/api/oauth-providers/${encodeURIComponent(id)}`, config);
464
+ }
465
+ deleteOAuthProvider(id) {
466
+ return this.request('DELETE', `/api/oauth-providers/${encodeURIComponent(id)}`);
467
+ }
468
+ createMock(config, settings = {}) {
469
+ return this.request('POST', `/api/mocks${creationQuery(settings)}`, config);
470
+ }
471
+ createMcpServer(config, settings = {}) {
472
+ return this.request('POST', `/api/mcp-servers${creationQuery(settings)}`, config);
473
+ }
474
+ upload(name, data, settings = {}) {
475
+ return this.request('POST', `/api/files${creationQuery(settings)}`, data, undefined, { 'X-File-Name': encodeURIComponent(name) });
476
+ }
477
+ sharing(kind, id, visibility, token) {
478
+ return this.request('POST', `/api/${kind}/${encodeURIComponent(id)}/sharing`, { visibility }, token);
479
+ }
480
+ async downloadFile(id, accessToken) {
481
+ if (!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(id))
482
+ throw new Error('Provide a file UUID.');
483
+ const headers = {};
484
+ if (accessToken)
485
+ headers['X-Access-Token'] = accessToken;
486
+ if (this.options.apiKey)
487
+ headers.Authorization = `Bearer ${this.options.apiKey}`;
488
+ const response = await this.fetcher(`${this.baseUrl}/files/${id}`, {
489
+ method: 'GET',
490
+ headers,
491
+ redirect: 'error',
492
+ credentials: 'omit',
493
+ signal: AbortSignal.timeout(this.timeoutMs),
494
+ });
495
+ if (!response.ok)
496
+ throw new KivelError('File unavailable or access denied.', response.status, response.headers.get('x-request-id') ?? undefined);
497
+ return new Uint8Array(await response.arrayBuffer());
498
+ }
499
+ remove(kind, id, token) {
500
+ return this.request('DELETE', `/api/${kind}/${encodeURIComponent(id)}`, undefined, token);
501
+ }
502
+ renew(kind, id, settings = {}, token) {
503
+ return this.request('POST', `/api/${kind}/${encodeURIComponent(id)}/renew`, settings, token);
504
+ }
505
+ }
506
+ function query(settings) {
507
+ const params = new URLSearchParams(Object.entries(settings)
508
+ .filter(([, value]) => value !== undefined)
509
+ .map(([key, value]) => [key, String(value)]));
510
+ return params.size ? `?${params}` : '';
511
+ }
512
+ function creationQuery(settings) {
513
+ return query({ ...settings, visibility: settings.visibility ?? 'private' });
514
+ }
515
+ function memoryPath(namespace, key) {
516
+ if (!/^[a-zA-Z0-9_.:-]{1,64}$/.test(namespace) || ['.', '..'].includes(namespace))
517
+ throw new Error('Provide a valid memory namespace.');
518
+ if (key !== undefined &&
519
+ (!/^[a-zA-Z0-9_./:-]{1,200}$/.test(key) ||
520
+ key.split('/').some((part) => ['.', '..'].includes(part))))
521
+ throw new Error('Provide a valid memory key without traversal.');
522
+ return `/api/memory/${encodeURIComponent(namespace)}${key === undefined ? '' : '/' + key.split('/').map(encodeURIComponent).join('/')}`;
523
+ }