lumnisai 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1098 @@
1
+ const DEFAULT_BASE_URL = "https://api.lumnis.ai";
2
+ const DEFAULT_POLL_INTERVAL_MS = 2e3;
3
+ const LONG_POLL_TIMEOUT_S = 10;
4
+ const DEFAULT_TIMEOUT_MS = 3e4;
5
+ const DEFAULT_MAX_RETRIES = 3;
6
+ const DEFAULT_BACKOFF_FACTOR = 0.5;
7
+
8
+ class ExternalAPIKeysResource {
9
+ constructor(http) {
10
+ this.http = http;
11
+ }
12
+ /**
13
+ * Store a new external API key (encrypted)
14
+ */
15
+ async store(data) {
16
+ return this.http.post("/external-api-keys", data);
17
+ }
18
+ /**
19
+ * Create a new external API key (alias for store)
20
+ */
21
+ async create(data) {
22
+ return this.store(data);
23
+ }
24
+ /**
25
+ * List all configured external API keys (metadata only, no key values)
26
+ */
27
+ async list() {
28
+ return this.http.get("/external-api-keys");
29
+ }
30
+ /**
31
+ * Get details for a specific external API key (no key value)
32
+ */
33
+ async get(keyId) {
34
+ return this.http.get(`/external-api-keys/${keyId}`);
35
+ }
36
+ /**
37
+ * Delete an external API key for a specific provider
38
+ */
39
+ async delete(provider) {
40
+ return this.http.delete(`/external-api-keys/${provider}`);
41
+ }
42
+ /**
43
+ * Get the current API key mode
44
+ */
45
+ async getMode() {
46
+ return this.http.get("/external-api-keys/mode");
47
+ }
48
+ /**
49
+ * Update the API key mode
50
+ */
51
+ async updateMode(data) {
52
+ return this.http.patch("/external-api-keys/mode", data);
53
+ }
54
+ /**
55
+ * Set the API key mode (alias for updateMode)
56
+ */
57
+ async setMode(data) {
58
+ return this.updateMode(data);
59
+ }
60
+ }
61
+
62
+ class FilesResource {
63
+ constructor(http) {
64
+ this.http = http;
65
+ }
66
+ /**
67
+ * Upload a new file for processing
68
+ * @param file - The file to upload (File or Blob)
69
+ * @param options - Upload options
70
+ * @param options.scope - File access scope (user or tenant)
71
+ * @param options.userId - User ID or email (required for user-scoped files)
72
+ * @param options.tags - Comma-separated tags for categorization
73
+ * @param options.duplicateHandling - How to handle duplicate filenames
74
+ */
75
+ async upload(file, options) {
76
+ const formData = new FormData();
77
+ formData.append("file", file);
78
+ formData.append("scope", options.scope);
79
+ if (options.userId)
80
+ formData.append("user_id", options.userId);
81
+ if (options.tags)
82
+ formData.append("tags", options.tags);
83
+ if (options.duplicateHandling)
84
+ formData.append("duplicate_handling", options.duplicateHandling);
85
+ return this.http.post("/files/upload", formData);
86
+ }
87
+ /**
88
+ * Upload multiple files at once
89
+ */
90
+ async bulkUpload(files, options) {
91
+ const formData = new FormData();
92
+ for (const file of files)
93
+ formData.append("files", file);
94
+ formData.append("scope", options.scope);
95
+ if (options.userId)
96
+ formData.append("user_id", options.userId);
97
+ if (options.tags)
98
+ formData.append("tags", options.tags);
99
+ return this.http.post("/files/bulk-upload", formData);
100
+ }
101
+ /**
102
+ * Get file metadata by ID
103
+ */
104
+ async get(fileId, userId) {
105
+ const params = userId ? { user_id: userId } : void 0;
106
+ return this.http.get(`/files/${fileId}`, { params });
107
+ }
108
+ /**
109
+ * List files with optional filters
110
+ */
111
+ async list(params) {
112
+ return this.http.get("/files", { params });
113
+ }
114
+ /**
115
+ * Get file content
116
+ */
117
+ async getContent(fileId, options) {
118
+ const params = {
119
+ content_type: options?.contentType,
120
+ start_line: options?.startLine,
121
+ end_line: options?.endLine,
122
+ user_id: options?.userId
123
+ };
124
+ return this.http.get(`/files/${fileId}/content`, { params });
125
+ }
126
+ /**
127
+ * Download original file
128
+ * Returns a redirect URL for blob storage or file content directly
129
+ */
130
+ async download(fileId, userId) {
131
+ const params = userId ? { user_id: userId } : void 0;
132
+ return this.http.request(`/files/${fileId}/download`, {
133
+ method: "GET",
134
+ params
135
+ });
136
+ }
137
+ /**
138
+ * Update file access scope
139
+ */
140
+ async updateScope(fileId, data) {
141
+ return this.http.patch(`/files/${fileId}/scope`, data);
142
+ }
143
+ /**
144
+ * Delete a file
145
+ */
146
+ async delete(fileId, options) {
147
+ const params = {
148
+ hard_delete: options?.hardDelete ?? true,
149
+ user_id: options?.userId
150
+ };
151
+ return this.http.delete(`/files/${fileId}`, { params });
152
+ }
153
+ /**
154
+ * Delete multiple files at once
155
+ */
156
+ async bulkDelete(data, options) {
157
+ const params = {
158
+ hard_delete: options?.hardDelete ?? true,
159
+ user_id: options?.userId
160
+ };
161
+ return this.http.request("/files/bulk", {
162
+ method: "DELETE",
163
+ body: data,
164
+ params
165
+ });
166
+ }
167
+ /**
168
+ * Get file processing status
169
+ */
170
+ async getStatus(fileId, userId) {
171
+ const params = userId ? { user_id: userId } : void 0;
172
+ return this.http.get(`/files/${fileId}/status`, { params });
173
+ }
174
+ /**
175
+ * Semantic search across files
176
+ */
177
+ async search(request) {
178
+ return this.http.post("/files/search", request);
179
+ }
180
+ /**
181
+ * Get file statistics for the tenant
182
+ */
183
+ async getStatistics() {
184
+ return this.http.get("/files/statistics");
185
+ }
186
+ }
187
+
188
+ class IntegrationsResource {
189
+ constructor(http) {
190
+ this.http = http;
191
+ }
192
+ /**
193
+ * Start an OAuth connection flow for a user
194
+ */
195
+ async initiateConnection(data) {
196
+ const requestData = {
197
+ ...data,
198
+ appName: data.appName.toUpperCase()
199
+ };
200
+ return this.http.post("/integrations/connections/initiate", requestData);
201
+ }
202
+ /**
203
+ * Check the status of a specific connection
204
+ */
205
+ async getConnectionStatus(userId, appName) {
206
+ return this.http.get(
207
+ `/integrations/connections/${encodeURIComponent(userId)}/${appName.toUpperCase()}`
208
+ );
209
+ }
210
+ /**
211
+ * Get all connections for a user
212
+ */
213
+ async getUserConnections(userId, appFilter) {
214
+ const params = new URLSearchParams();
215
+ if (appFilter)
216
+ params.append("app_filter", appFilter);
217
+ const query = params.toString() ? `?${params.toString()}` : "";
218
+ return this.http.get(
219
+ `/integrations/connections/${encodeURIComponent(userId)}${query}`
220
+ );
221
+ }
222
+ /**
223
+ * Get available tools for a user based on connections
224
+ */
225
+ async getTools(data) {
226
+ const requestData = {
227
+ ...data,
228
+ appFilter: data.appFilter?.map((app) => app.toUpperCase())
229
+ };
230
+ return this.http.post("/integrations/tools", requestData);
231
+ }
232
+ /**
233
+ * Disconnect a user from an external app
234
+ */
235
+ async disconnect(data) {
236
+ const requestData = {
237
+ ...data,
238
+ appName: data.appName.toUpperCase()
239
+ };
240
+ return this.http.post("/integrations/connections/disconnect", requestData);
241
+ }
242
+ /**
243
+ * Handle OAuth callback (for custom implementations)
244
+ */
245
+ async handleCallback(data) {
246
+ return this.http.post("/integrations/connections/callback", data);
247
+ }
248
+ /**
249
+ * List apps enabled for the tenant
250
+ */
251
+ async listApps(params) {
252
+ const urlParams = new URLSearchParams();
253
+ if (params?.includeAvailable)
254
+ urlParams.append("include_available", "true");
255
+ const query = urlParams.toString() ? `?${urlParams.toString()}` : "";
256
+ return this.http.get(`/integrations/apps${query}`);
257
+ }
258
+ /**
259
+ * Check if a specific app is enabled
260
+ */
261
+ async checkAppEnabled(appName) {
262
+ return this.http.get(
263
+ `/integrations/apps/${appName.toUpperCase()}/enabled`
264
+ );
265
+ }
266
+ /**
267
+ * Enable or disable an app for the tenant
268
+ */
269
+ async updateAppStatus(appName, enabled) {
270
+ return this.http.put(
271
+ `/integrations/apps/${appName.toUpperCase()}?enabled=${enabled}`
272
+ );
273
+ }
274
+ /**
275
+ * Get required fields for non-OAuth authentication (future)
276
+ */
277
+ async getNonOAuthRequiredFields(appName, authScheme) {
278
+ const params = new URLSearchParams({ auth_scheme: authScheme });
279
+ return this.http.get(
280
+ `/integrations/non-oauth/required-fields/${appName.toUpperCase()}?${params.toString()}`
281
+ );
282
+ }
283
+ // Aliases for backward compatibility with client methods
284
+ async isAppEnabled(appName) {
285
+ return this.checkAppEnabled(appName);
286
+ }
287
+ async setAppEnabled(appName, data) {
288
+ return this.updateAppStatus(appName, data.enabled);
289
+ }
290
+ async listConnections(userId, params) {
291
+ return this.getUserConnections(userId, params?.appFilter);
292
+ }
293
+ }
294
+
295
+ class MCPServersResource {
296
+ constructor(http) {
297
+ this.http = http;
298
+ }
299
+ /**
300
+ * Create a new MCP server configuration
301
+ */
302
+ async create(data) {
303
+ return this.http.post("/mcp-servers", data);
304
+ }
305
+ /**
306
+ * List MCP server configurations
307
+ */
308
+ async list(params) {
309
+ const queryParams = new URLSearchParams();
310
+ if (params?.scope)
311
+ queryParams.append("scope", params.scope);
312
+ if (params?.userIdentifier)
313
+ queryParams.append("user_identifier", params.userIdentifier);
314
+ if (params?.isActive !== void 0)
315
+ queryParams.append("is_active", params.isActive.toString());
316
+ if (params?.skip)
317
+ queryParams.append("skip", params.skip.toString());
318
+ if (params?.limit)
319
+ queryParams.append("limit", params.limit.toString());
320
+ const query = queryParams.toString() ? `?${queryParams.toString()}` : "";
321
+ return this.http.get(`/mcp-servers${query}`);
322
+ }
323
+ /**
324
+ * Get a specific MCP server configuration
325
+ */
326
+ async get(serverId) {
327
+ return this.http.get(`/mcp-servers/${serverId}`);
328
+ }
329
+ /**
330
+ * Update an MCP server configuration
331
+ */
332
+ async update(serverId, data) {
333
+ return this.http.patch(`/mcp-servers/${serverId}`, data);
334
+ }
335
+ /**
336
+ * Delete an MCP server configuration
337
+ */
338
+ async delete(serverId) {
339
+ await this.http.delete(`/mcp-servers/${serverId}`);
340
+ }
341
+ /**
342
+ * List tools provided by an MCP server
343
+ * Note: Currently returns empty list, tool indexing coming in Phase 1
344
+ */
345
+ async listTools(serverId) {
346
+ return this.http.get(`/mcp-servers/${serverId}/tools`);
347
+ }
348
+ /**
349
+ * Test connection to an MCP server
350
+ * Note: Currently returns placeholder, testing coming in Phase 1
351
+ */
352
+ async testConnection(serverId) {
353
+ return this.http.post(`/mcp-servers/${serverId}/test`);
354
+ }
355
+ /**
356
+ * Test an MCP server configuration before saving
357
+ * Validates that the server can be connected to without creating a permanent configuration
358
+ */
359
+ async testConfig(config) {
360
+ return this.http.post("/mcp-servers/test", config);
361
+ }
362
+ }
363
+
364
+ class ModelPreferencesResource {
365
+ constructor(http) {
366
+ this.http = http;
367
+ }
368
+ /**
369
+ * Get all model preferences for the tenant
370
+ */
371
+ async get(includeDefaults = true) {
372
+ const params = new URLSearchParams();
373
+ params.append("include_defaults", includeDefaults.toString());
374
+ return this.http.get(`/model-preferences?${params.toString()}`);
375
+ }
376
+ /**
377
+ * Update multiple model preferences in a single request
378
+ */
379
+ async updateBulk(data) {
380
+ return this.http.put("/model-preferences", data);
381
+ }
382
+ /**
383
+ * Update a specific model type preference
384
+ */
385
+ async update(modelType, data) {
386
+ return this.http.patch(`/model-preferences/${modelType}`, data);
387
+ }
388
+ /**
389
+ * Delete a model preference to revert to system default
390
+ */
391
+ async delete(modelType) {
392
+ await this.http.delete(`/model-preferences/${modelType}`);
393
+ }
394
+ /**
395
+ * Check which models are available based on API key configuration
396
+ */
397
+ async checkAvailability(models) {
398
+ return this.http.post("/model-preferences/check-availability", models);
399
+ }
400
+ /**
401
+ * List all model preferences (alias for get)
402
+ */
403
+ async list(params) {
404
+ return this.get(params?.includeDefaults);
405
+ }
406
+ }
407
+
408
+ class LumnisError extends Error {
409
+ code;
410
+ statusCode;
411
+ details;
412
+ requestId;
413
+ constructor(message, options = {}) {
414
+ super(message);
415
+ this.name = "LumnisError";
416
+ this.code = options.code || "UNKNOWN_ERROR";
417
+ this.statusCode = options.statusCode;
418
+ this.details = options.details;
419
+ this.requestId = options.requestId;
420
+ Error.captureStackTrace(this, this.constructor);
421
+ }
422
+ }
423
+ class AuthenticationError extends LumnisError {
424
+ constructor(message, options = {}) {
425
+ super(message, { ...options, statusCode: options.statusCode || 401 });
426
+ this.name = "AuthenticationError";
427
+ }
428
+ }
429
+ class ValidationError extends LumnisError {
430
+ constructor(message, options = {}) {
431
+ super(message, { ...options, statusCode: options.statusCode || 400 });
432
+ this.name = "ValidationError";
433
+ }
434
+ }
435
+ class NotFoundError extends LumnisError {
436
+ constructor(message, options = {}) {
437
+ super(message, { ...options, statusCode: 404 });
438
+ this.name = "NotFoundError";
439
+ }
440
+ }
441
+ class RateLimitError extends LumnisError {
442
+ retryAfter;
443
+ constructor(options = {}) {
444
+ super("Rate limit exceeded", { ...options, statusCode: 429 });
445
+ this.name = "RateLimitError";
446
+ this.retryAfter = options.retryAfter;
447
+ }
448
+ }
449
+ class InternalServerError extends LumnisError {
450
+ constructor(message, options = {}) {
451
+ super(message, { ...options, statusCode: 500 });
452
+ this.name = "InternalServerError";
453
+ }
454
+ }
455
+ class LocalFileNotSupportedError extends ValidationError {
456
+ constructor(filePath) {
457
+ super(
458
+ `Local file paths are not supported yet: ${filePath}. Please wait for the artifact upload API or use artifact IDs.`,
459
+ { code: "LOCAL_FILE_NOT_SUPPORTED" }
460
+ );
461
+ this.name = "LocalFileNotSupportedError";
462
+ }
463
+ }
464
+
465
+ class ResponsesResource {
466
+ constructor(http) {
467
+ this.http = http;
468
+ }
469
+ _validateFileReference(uri) {
470
+ if (uri.startsWith("artifact_"))
471
+ return;
472
+ try {
473
+ const parsed = new URL(uri);
474
+ const allowedSchemes = [
475
+ "http",
476
+ "https",
477
+ "s3",
478
+ "gs",
479
+ "gcs",
480
+ "file",
481
+ "ftp",
482
+ "ftps",
483
+ "blob",
484
+ "data"
485
+ ];
486
+ if (allowedSchemes.includes(parsed.protocol.replace(":", ""))) {
487
+ if ((parsed.protocol === "http:" || parsed.protocol === "https \u043F\u0440\u043E\u0442\u043E\u043A\u043E\u043B:") && !parsed.hostname)
488
+ throw new LocalFileNotSupportedError(uri);
489
+ return;
490
+ } else {
491
+ throw new LocalFileNotSupportedError(uri);
492
+ }
493
+ } catch (error) {
494
+ if (error instanceof LocalFileNotSupportedError)
495
+ throw error;
496
+ }
497
+ const isLocalPath = uri.startsWith("/") || uri.startsWith("./") || uri.startsWith("../") || /^[a-z]:/i.test(uri) || uri.startsWith("\\\\") || uri.split("/").length === 1 && uri.includes(".") && !uri.startsWith("artifact_");
498
+ if (isLocalPath)
499
+ throw new LocalFileNotSupportedError(uri);
500
+ }
501
+ /**
502
+ * Create a new response request for asynchronous processing
503
+ */
504
+ async create(request) {
505
+ if (request.files) {
506
+ for (const file of request.files)
507
+ this._validateFileReference(file.uri);
508
+ }
509
+ return this.http.post("/responses", request);
510
+ }
511
+ /**
512
+ * Get response status and content
513
+ * @param responseId - The response ID
514
+ * @param options - Optional parameters
515
+ * @param options.wait - Wait time in seconds for long-polling
516
+ */
517
+ async get(responseId, options) {
518
+ return this.http.get(`/responses/${responseId}`, { params: options });
519
+ }
520
+ /**
521
+ * List responses with optional filtering
522
+ * @param params - Optional filter parameters
523
+ * @param params.userId - Filter by user ID
524
+ * @param params.status - Filter by response status
525
+ * @param params.startDate - Filter responses created after this date
526
+ * @param params.endDate - Filter responses created before this date
527
+ * @param params.limit - Maximum number of responses to return
528
+ * @param params.offset - Number of responses to skip for pagination
529
+ */
530
+ async list(params) {
531
+ const queryParams = {};
532
+ if (params?.userId)
533
+ queryParams.user_id = params.userId;
534
+ if (params?.status)
535
+ queryParams.status = params.status;
536
+ if (params?.startDate)
537
+ queryParams.start_date = params.startDate;
538
+ if (params?.endDate)
539
+ queryParams.end_date = params.endDate;
540
+ if (params?.limit)
541
+ queryParams.limit = params.limit;
542
+ if (params?.offset)
543
+ queryParams.offset = params.offset;
544
+ return this.http.get("/responses", { params: queryParams });
545
+ }
546
+ /**
547
+ * Cancel a queued or in-progress response
548
+ */
549
+ async cancel(responseId) {
550
+ return this.http.post(`/responses/${responseId}/cancel`);
551
+ }
552
+ /**
553
+ * List artifacts generated by a response
554
+ */
555
+ async listArtifacts(responseId, params) {
556
+ return this.http.get(`/responses/${responseId}/artifacts`, { params });
557
+ }
558
+ }
559
+
560
+ class TenantInfoResource {
561
+ constructor(http) {
562
+ this.http = http;
563
+ }
564
+ /**
565
+ * Get detailed information about a tenant
566
+ * Users can only access information about their own tenant
567
+ */
568
+ async get(tenantId) {
569
+ return this.http.get(`/tenants/${tenantId}`);
570
+ }
571
+ }
572
+
573
+ class ThreadsResource {
574
+ constructor(http) {
575
+ this.http = http;
576
+ }
577
+ /**
578
+ * Create a new thread
579
+ */
580
+ async create(params) {
581
+ return this.http.post("/threads", params);
582
+ }
583
+ /**
584
+ * List threads for the authenticated tenant
585
+ */
586
+ async list(params) {
587
+ return this.http.get("/threads", { params });
588
+ }
589
+ /**
590
+ * Get detailed information about a specific thread
591
+ */
592
+ async get(threadId) {
593
+ return this.http.get(`/threads/${encodeURIComponent(threadId)}`);
594
+ }
595
+ /**
596
+ * Get all responses associated with a specific thread
597
+ */
598
+ async getResponses(threadId, params) {
599
+ return this.http.get(`/threads/${encodeURIComponent(threadId)}/responses`, { params });
600
+ }
601
+ /**
602
+ * Update thread metadata
603
+ */
604
+ async update(threadId, data) {
605
+ return this.http.patch(`/threads/${encodeURIComponent(threadId)}`, data);
606
+ }
607
+ /**
608
+ * Delete a thread and all its associated responses
609
+ */
610
+ async delete(threadId) {
611
+ await this.http.delete(`/threads/${encodeURIComponent(threadId)}`);
612
+ }
613
+ }
614
+
615
+ class UsersResource {
616
+ constructor(http) {
617
+ this.http = http;
618
+ }
619
+ /**
620
+ * Create a new user within the tenant
621
+ * Returns existing user with 200 OK if user with same email already exists
622
+ */
623
+ async create(data) {
624
+ return this.http.post("/users", data);
625
+ }
626
+ /**
627
+ * List all users in the tenant with pagination
628
+ */
629
+ async list(params) {
630
+ const queryParams = new URLSearchParams();
631
+ if (params?.page)
632
+ queryParams.append("page", params.page.toString());
633
+ if (params?.pageSize)
634
+ queryParams.append("page_size", params.pageSize.toString());
635
+ const query = queryParams.toString() ? `?${queryParams.toString()}` : "";
636
+ return this.http.get(`/users${query}`);
637
+ }
638
+ /**
639
+ * Get user details by ID or email
640
+ */
641
+ async get(userIdentifier) {
642
+ return this.http.get(`/users/${encodeURIComponent(userIdentifier)}`);
643
+ }
644
+ /**
645
+ * Update user information by ID or email
646
+ */
647
+ async update(userIdentifier, data) {
648
+ return this.http.put(`/users/${encodeURIComponent(userIdentifier)}`, data);
649
+ }
650
+ /**
651
+ * Delete (deactivate) a user by ID or email
652
+ */
653
+ async delete(userIdentifier) {
654
+ return this.http.delete(`/users/${encodeURIComponent(userIdentifier)}`);
655
+ }
656
+ /**
657
+ * Get all AI responses generated for a specific user
658
+ */
659
+ async getResponses(userIdentifier, params) {
660
+ const queryParams = new URLSearchParams();
661
+ if (params?.page)
662
+ queryParams.append("page", params.page.toString());
663
+ if (params?.pageSize)
664
+ queryParams.append("page_size", params.pageSize.toString());
665
+ const query = queryParams.toString() ? `?${queryParams.toString()}` : "";
666
+ return this.http.get(`/users/${encodeURIComponent(userIdentifier)}/responses${query}`);
667
+ }
668
+ /**
669
+ * Get all conversation threads for a specific user
670
+ */
671
+ async getThreads(userIdentifier, params) {
672
+ const queryParams = new URLSearchParams();
673
+ if (params?.page)
674
+ queryParams.append("page", params.page.toString());
675
+ if (params?.pageSize)
676
+ queryParams.append("page_size", params.pageSize.toString());
677
+ const query = queryParams.toString() ? `?${queryParams.toString()}` : "";
678
+ return this.http.get(`/users/${encodeURIComponent(userIdentifier)}/threads${query}`);
679
+ }
680
+ }
681
+
682
+ function toCamel(s) {
683
+ return s.replace(/([-_][a-z])/gi, ($1) => {
684
+ return $1.toUpperCase().replace("-", "").replace("_", "");
685
+ });
686
+ }
687
+ function toSnake(s) {
688
+ return s.replace(/[A-Z]/g, (letter, index) => {
689
+ return index === 0 ? letter.toLowerCase() : `_${letter.toLowerCase()}`;
690
+ });
691
+ }
692
+ function convertCase(obj, converter) {
693
+ if (Array.isArray(obj)) {
694
+ return obj.map((v) => convertCase(v, converter));
695
+ } else if (obj !== null && typeof obj === "object") {
696
+ return Object.keys(obj).reduce((acc, key) => {
697
+ acc[converter(key)] = convertCase(obj[key], converter);
698
+ return acc;
699
+ }, {});
700
+ }
701
+ return obj;
702
+ }
703
+ function toCamelCase(obj) {
704
+ return convertCase(obj, toCamel);
705
+ }
706
+ function toSnakeCase(obj) {
707
+ return convertCase(obj, toSnake);
708
+ }
709
+
710
+ class Http {
711
+ options;
712
+ constructor(options) {
713
+ this.options = {
714
+ baseUrl: options.baseUrl.replace(/\/$/, ""),
715
+ apiPrefix: options.apiPrefix || "",
716
+ headers: options.headers || {},
717
+ timeoutMs: options.timeoutMs || DEFAULT_TIMEOUT_MS,
718
+ maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
719
+ backoffFactor: options.backoffFactor || DEFAULT_BACKOFF_FACTOR
720
+ };
721
+ }
722
+ async _handleResponse(response) {
723
+ const requestId = response.headers.get("x-request-id");
724
+ if (response.ok) {
725
+ if (response.status === 204)
726
+ return null;
727
+ const contentType = response.headers.get("content-type") || "";
728
+ if (contentType.includes("application/json")) {
729
+ const json = await response.json();
730
+ return toCamelCase(json);
731
+ }
732
+ return await response.text();
733
+ }
734
+ let detail = { raw: await response.text() };
735
+ try {
736
+ if (response.headers.get("content-type")?.includes("application/json"))
737
+ detail = JSON.parse(detail.raw);
738
+ } catch {
739
+ }
740
+ const errorMsg = detail?.error?.message || `Server error: ${response.status}`;
741
+ switch (response.status) {
742
+ case 401:
743
+ throw new AuthenticationError("Invalid or missing API key", { requestId, statusCode: 401, details: detail });
744
+ case 403:
745
+ throw new AuthenticationError("Forbidden - insufficient permissions", { requestId, statusCode: 403, details: detail });
746
+ case 404:
747
+ throw new NotFoundError("Resource not found", { requestId, statusCode: 404, details: detail });
748
+ case 429: {
749
+ const retryAfter = response.headers.get("Retry-After");
750
+ throw new RateLimitError({ requestId, statusCode: 429, details: detail, retryAfter });
751
+ }
752
+ default:
753
+ if (response.status >= 400 && response.status < 500)
754
+ throw new ValidationError(errorMsg, { requestId, statusCode: response.status, details: detail });
755
+ throw new LumnisError(`Server error: ${response.status}`, { requestId, statusCode: response.status, details: detail });
756
+ }
757
+ }
758
+ async request(path, init = {}) {
759
+ const { body, params, idempotencyKey: idempotencyKeyOption, ...fetchOptions } = init;
760
+ const method = fetchOptions.method || "GET";
761
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
762
+ const fullPath = this.options.apiPrefix ? `${this.options.apiPrefix}${normalizedPath}` : normalizedPath;
763
+ const url = new URL(this.options.baseUrl + fullPath);
764
+ if (params) {
765
+ for (const [key, value] of Object.entries(params)) {
766
+ if (value !== void 0 && value !== null)
767
+ url.searchParams.append(key, String(value));
768
+ }
769
+ }
770
+ const idempotencyKey = idempotencyKeyOption || (method !== "GET" ? crypto.randomUUID() : void 0);
771
+ const headers = {
772
+ "content-type": "application/json",
773
+ ...this.options.headers,
774
+ ...fetchOptions.headers,
775
+ ...idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}
776
+ };
777
+ const isIdempotent = method === "GET" || method === "DELETE" || !!idempotencyKey;
778
+ const maxAttempts = isIdempotent ? this.options.maxRetries + 1 : 1;
779
+ let lastError;
780
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
781
+ const controller = new AbortController();
782
+ const timeout = setTimeout(() => controller.abort(), this.options.timeoutMs);
783
+ try {
784
+ const response = await fetch(url.toString(), {
785
+ ...fetchOptions,
786
+ method,
787
+ headers,
788
+ body: body ? JSON.stringify(toSnakeCase(body)) : void 0,
789
+ signal: controller.signal
790
+ });
791
+ return await this._handleResponse(response);
792
+ } catch (error) {
793
+ lastError = error;
794
+ if (error instanceof RateLimitError) {
795
+ const backoff = backoffMs(attempt, error.retryAfter);
796
+ await sleep(backoff);
797
+ continue;
798
+ }
799
+ if (error instanceof LumnisError && (error.statusCode ?? 0) < 500)
800
+ throw error;
801
+ if (attempt < maxAttempts - 1) {
802
+ const backoff = backoffMs(attempt);
803
+ await sleep(backoff);
804
+ }
805
+ } finally {
806
+ clearTimeout(timeout);
807
+ }
808
+ }
809
+ throw lastError || new LumnisError("Request failed after all retries", {});
810
+ }
811
+ get(path, options) {
812
+ return this.request(path, { ...options, method: "GET" });
813
+ }
814
+ post(path, body, options) {
815
+ return this.request(path, { ...options, method: "POST", body });
816
+ }
817
+ put(path, body, options) {
818
+ return this.request(path, { ...options, method: "PUT", body });
819
+ }
820
+ patch(path, body, options) {
821
+ return this.request(path, { ...options, method: "PATCH", body });
822
+ }
823
+ delete(path, options) {
824
+ return this.request(path, { ...options, method: "DELETE" });
825
+ }
826
+ async warnTenantScope() {
827
+ console.warn(
828
+ "Using TENANT scope bypasses user isolation. Ensure this is intentional and follows security best practices."
829
+ );
830
+ }
831
+ }
832
+ function sleep(ms) {
833
+ return new Promise((resolve) => setTimeout(resolve, ms));
834
+ }
835
+ function backoffMs(attempt, retryAfter = null) {
836
+ if (retryAfter) {
837
+ const retryAfterMs = Number(retryAfter) * 1e3;
838
+ if (!Number.isNaN(retryAfterMs))
839
+ return retryAfterMs;
840
+ }
841
+ const jitter = Math.random() * 500;
842
+ return Math.min(1e3 * 2 ** attempt, 1e4) + jitter;
843
+ }
844
+
845
+ class LumnisClient {
846
+ http;
847
+ tenantId;
848
+ // Resources
849
+ threads;
850
+ responses;
851
+ users;
852
+ files;
853
+ tenantInfo;
854
+ externalApiKeys;
855
+ integrations;
856
+ modelPreferences;
857
+ mcpServers;
858
+ _scopedUserId;
859
+ _defaultScope;
860
+ constructor(options = {}) {
861
+ const apiKey = options.apiKey || typeof process !== "undefined" && process.env?.LUMNISAI_API_KEY;
862
+ if (!apiKey) {
863
+ throw new Error("API key is required. Provide it via options.apiKey or set LUMNISAI_API_KEY environment variable.");
864
+ }
865
+ this.tenantId = options.tenantId || typeof process !== "undefined" && process.env?.LUMNISAI_TENANT_ID || void 0;
866
+ const baseUrl = options.baseUrl || typeof process !== "undefined" && process.env?.LUMNISAI_BASE_URL || DEFAULT_BASE_URL;
867
+ this._defaultScope = options.scope || "tenant";
868
+ this._scopedUserId = options._scopedUserId;
869
+ this.http = new Http({
870
+ baseUrl,
871
+ apiPrefix: "/v1",
872
+ headers: {
873
+ "X-API-Key": apiKey
874
+ },
875
+ timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
876
+ maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES
877
+ });
878
+ this.threads = new ThreadsResource(this.http);
879
+ this.responses = new ResponsesResource(this.http);
880
+ this.users = new UsersResource(this.http);
881
+ this.files = new FilesResource(this.http);
882
+ this.tenantInfo = new TenantInfoResource(this.http);
883
+ this.externalApiKeys = new ExternalAPIKeysResource(this.http);
884
+ this.integrations = new IntegrationsResource(this.http);
885
+ this.modelPreferences = new ModelPreferencesResource(this.http);
886
+ this.mcpServers = new MCPServersResource(this.http);
887
+ }
888
+ forUser(userId) {
889
+ return new LumnisClient({
890
+ apiKey: this.http.options.headers["X-API-Key"],
891
+ tenantId: this.tenantId,
892
+ baseUrl: this.http.options.baseUrl,
893
+ timeoutMs: this.http.options.timeoutMs,
894
+ maxRetries: this.http.options.maxRetries,
895
+ scope: "user",
896
+ _scopedUserId: userId
897
+ });
898
+ }
899
+ async invoke(messages, options) {
900
+ const { stream = false, showProgress = true, ...restOptions } = options || {};
901
+ if (stream) {
902
+ if (showProgress)
903
+ console.warn("showProgress is not supported in streaming mode.");
904
+ return this._invokeStream(messages, restOptions);
905
+ } else {
906
+ let progressCallback;
907
+ if (showProgress)
908
+ progressCallback = createSimpleProgressCallback();
909
+ return this._invokeAndWait(messages, restOptions, progressCallback);
910
+ }
911
+ }
912
+ async *_invokeStream(messages, options) {
913
+ const response = await this._createResponse(messages, options);
914
+ console.log(`Response ID: ${response.responseId}`);
915
+ let lastMessageCount = 0;
916
+ while (true) {
917
+ const current = await this.responses.get(response.responseId, { wait: LONG_POLL_TIMEOUT_S });
918
+ const currentMessageCount = current.progress?.length || 0;
919
+ if (currentMessageCount > lastMessageCount && current.progress) {
920
+ for (let i = lastMessageCount; i < currentMessageCount; i++)
921
+ yield current.progress[i];
922
+ lastMessageCount = currentMessageCount;
923
+ }
924
+ if (current.status === "succeeded" || current.status === "failed" || current.status === "cancelled") {
925
+ if (current.status === "succeeded" && current.outputText) {
926
+ const progressEntry = {
927
+ ts: current.completedAt || (/* @__PURE__ */ new Date()).toISOString(),
928
+ state: "completed",
929
+ message: "Task completed successfully",
930
+ outputText: current.outputText
931
+ };
932
+ yield progressEntry;
933
+ }
934
+ break;
935
+ }
936
+ }
937
+ }
938
+ async _invokeAndWait(messages, options, progressCallback) {
939
+ const { pollIntervalMs = DEFAULT_POLL_INTERVAL_MS, maxWaitMs = 3e5, ...createOptions } = options || {};
940
+ const response = await this._createResponse(messages, createOptions);
941
+ if (!progressCallback)
942
+ console.log(`Response ID: ${response.responseId}`);
943
+ const startTime = Date.now();
944
+ while (true) {
945
+ const current = await this.responses.get(response.responseId, { wait: LONG_POLL_TIMEOUT_S });
946
+ if (progressCallback)
947
+ progressCallback(current);
948
+ if (current.status === "succeeded" || current.status === "failed" || current.status === "cancelled")
949
+ return current;
950
+ if (Date.now() - startTime > maxWaitMs)
951
+ throw new Error(`Response ${response.responseId} timed out after ${maxWaitMs}ms`);
952
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
953
+ }
954
+ }
955
+ async _createResponse(messages, options) {
956
+ const { scope: optionScope, userId: optionUserId, ...restOptions } = options;
957
+ const effectiveUserId = optionUserId || this._scopedUserId;
958
+ let scope = optionScope || this._defaultScope;
959
+ if (effectiveUserId && scope === "tenant")
960
+ scope = "user";
961
+ if (scope === "user" && !effectiveUserId)
962
+ throw new Error("user_id is required for user scope");
963
+ if (scope === "tenant")
964
+ await this.http.warnTenantScope();
965
+ let messageArray;
966
+ if (typeof messages === "string")
967
+ messageArray = [{ role: "user", content: messages }];
968
+ else if (Array.isArray(messages))
969
+ messageArray = messages;
970
+ else
971
+ messageArray = [messages];
972
+ return this.responses.create({
973
+ messages: messageArray,
974
+ userId: effectiveUserId,
975
+ ...restOptions
976
+ });
977
+ }
978
+ // Thread methods
979
+ async listThreads(params) {
980
+ return this.threads.list(params);
981
+ }
982
+ async createThread(params) {
983
+ return this.threads.create(params);
984
+ }
985
+ async getThread(threadId) {
986
+ return this.threads.get(threadId);
987
+ }
988
+ async deleteThread(threadId) {
989
+ return this.threads.delete(threadId);
990
+ }
991
+ // User methods
992
+ async createUser(params) {
993
+ return this.users.create(params);
994
+ }
995
+ async getUser(userId) {
996
+ return this.users.get(userId);
997
+ }
998
+ async updateUser(userId, params) {
999
+ return this.users.update(userId, params);
1000
+ }
1001
+ async deleteUser(userId) {
1002
+ return this.users.delete(userId);
1003
+ }
1004
+ async listUsers(params) {
1005
+ return this.users.list(params);
1006
+ }
1007
+ // External API Key methods
1008
+ async addApiKey(params) {
1009
+ return this.externalApiKeys.create(params);
1010
+ }
1011
+ async listApiKeys() {
1012
+ return this.externalApiKeys.list();
1013
+ }
1014
+ async getApiKey(keyId) {
1015
+ return this.externalApiKeys.get(keyId);
1016
+ }
1017
+ async deleteApiKey(provider) {
1018
+ return this.externalApiKeys.delete(provider);
1019
+ }
1020
+ async getApiKeyMode() {
1021
+ return this.externalApiKeys.getMode();
1022
+ }
1023
+ async setApiKeyMode(mode) {
1024
+ return this.externalApiKeys.setMode(mode);
1025
+ }
1026
+ // Integration methods
1027
+ async listApps(params) {
1028
+ return this.integrations.listApps(params);
1029
+ }
1030
+ async isAppEnabled(appName) {
1031
+ return this.integrations.isAppEnabled(appName);
1032
+ }
1033
+ async setAppEnabled(appName, enabled) {
1034
+ return this.integrations.setAppEnabled(appName, { enabled });
1035
+ }
1036
+ async initiateConnection(params) {
1037
+ return this.integrations.initiateConnection(params);
1038
+ }
1039
+ async getConnectionStatus(userId, appName) {
1040
+ return this.integrations.getConnectionStatus(userId, appName);
1041
+ }
1042
+ async listConnections(userId, params) {
1043
+ return this.integrations.listConnections(userId, params);
1044
+ }
1045
+ async getIntegrationTools(userId, params) {
1046
+ return this.integrations.getTools({ userId, appFilter: params?.appFilter });
1047
+ }
1048
+ // Model Preference methods
1049
+ async getModelPreferences(params) {
1050
+ return this.modelPreferences.list(params);
1051
+ }
1052
+ async updateModelPreferences(params) {
1053
+ return this.modelPreferences.updateBulk(params);
1054
+ }
1055
+ // MCP Server methods
1056
+ async createMcpServer(params) {
1057
+ return this.mcpServers.create(params);
1058
+ }
1059
+ async getMcpServer(serverId) {
1060
+ return this.mcpServers.get(serverId);
1061
+ }
1062
+ async listMcpServers(params) {
1063
+ return this.mcpServers.list(params);
1064
+ }
1065
+ async updateMcpServer(serverId, params) {
1066
+ return this.mcpServers.update(serverId, params);
1067
+ }
1068
+ async deleteMcpServer(serverId) {
1069
+ return this.mcpServers.delete(serverId);
1070
+ }
1071
+ async listMcpServerTools(serverId) {
1072
+ return this.mcpServers.listTools(serverId);
1073
+ }
1074
+ async testMcpServer(serverId) {
1075
+ return this.mcpServers.testConnection(serverId);
1076
+ }
1077
+ }
1078
+ function createSimpleProgressCallback() {
1079
+ let lastStatus;
1080
+ const seenMessages = /* @__PURE__ */ new Set();
1081
+ return (response) => {
1082
+ if (response.status !== lastStatus) {
1083
+ console.log(`Status: ${response.status}`);
1084
+ lastStatus = response.status;
1085
+ }
1086
+ if (response.progress) {
1087
+ for (const entry of response.progress) {
1088
+ const messageKey = `${entry.state}:${entry.message}`;
1089
+ if (!seenMessages.has(messageKey)) {
1090
+ console.log(`${entry.state.toUpperCase()}: ${entry.message}`);
1091
+ seenMessages.add(messageKey);
1092
+ }
1093
+ }
1094
+ }
1095
+ };
1096
+ }
1097
+
1098
+ export { AuthenticationError, InternalServerError, LocalFileNotSupportedError, LumnisClient, LumnisError, NotFoundError, RateLimitError, ValidationError, LumnisClient as default };