lexmount 0.2.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,846 @@
1
+ // src/client.ts
2
+ import {
3
+ isAxiosError
4
+ } from "axios";
5
+ import axios from "axios";
6
+ import * as dotenv from "dotenv";
7
+
8
+ // src/errors.ts
9
+ var LexmountError = class extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = new.target.name;
13
+ }
14
+ };
15
+ var AuthenticationError = class extends LexmountError {
16
+ };
17
+ var SessionNotFoundError = class extends LexmountError {
18
+ };
19
+ var ContextNotFoundError = class extends LexmountError {
20
+ };
21
+ var ContextLockedError = class extends LexmountError {
22
+ constructor(message, options = {}) {
23
+ const details = [];
24
+ if (options.activeSessionId) {
25
+ details.push(`locked by session: ${options.activeSessionId}`);
26
+ }
27
+ if (typeof options.retryAfter === "number") {
28
+ details.push(`retry after ${options.retryAfter}s`);
29
+ }
30
+ super(details.length > 0 ? `${message} (${details.join(", ")})` : message);
31
+ this.activeSessionId = options.activeSessionId;
32
+ this.retryAfter = options.retryAfter;
33
+ }
34
+ };
35
+ var APIError = class extends LexmountError {
36
+ constructor(message, options = {}) {
37
+ super(
38
+ typeof options.statusCode === "number" ? `${message} (HTTP ${options.statusCode})` : message
39
+ );
40
+ this.statusCode = options.statusCode;
41
+ this.response = options.response;
42
+ }
43
+ };
44
+ var NetworkError = class extends LexmountError {
45
+ };
46
+ var ValidationError = class extends LexmountError {
47
+ };
48
+ var TimeoutError = class extends LexmountError {
49
+ };
50
+
51
+ // src/logging.ts
52
+ var LOGGER_NAME = "lexmount";
53
+ var LOG_LEVEL_PRIORITY = {
54
+ DEBUG: 10,
55
+ INFO: 20,
56
+ WARNING: 30,
57
+ ERROR: 40,
58
+ CRITICAL: 50,
59
+ SILENT: 60
60
+ };
61
+ var currentLevel = "WARNING";
62
+ function isLogLevel(value) {
63
+ return value in LOG_LEVEL_PRIORITY;
64
+ }
65
+ function formatTimestamp(date) {
66
+ return date.toISOString().replace("T", " ").replace("Z", "");
67
+ }
68
+ function shouldLog(level) {
69
+ return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[currentLevel];
70
+ }
71
+ function emit(level, args) {
72
+ if (!shouldLog(level)) {
73
+ return;
74
+ }
75
+ const prefix = `${formatTimestamp(/* @__PURE__ */ new Date())} - ${LOGGER_NAME} - ${level} -`;
76
+ if (level === "ERROR" || level === "CRITICAL") {
77
+ console.error(prefix, ...args);
78
+ return;
79
+ }
80
+ if (level === "WARNING") {
81
+ console.warn(prefix, ...args);
82
+ return;
83
+ }
84
+ if (level === "INFO") {
85
+ console.info(prefix, ...args);
86
+ return;
87
+ }
88
+ console.debug(prefix, ...args);
89
+ }
90
+ var LexmountLogger = class {
91
+ /**
92
+ * Current SDK log level.
93
+ */
94
+ get level() {
95
+ return currentLevel;
96
+ }
97
+ debug(...args) {
98
+ emit("DEBUG", args);
99
+ }
100
+ info(...args) {
101
+ emit("INFO", args);
102
+ }
103
+ warn(...args) {
104
+ emit("WARNING", args);
105
+ }
106
+ error(...args) {
107
+ emit("ERROR", args);
108
+ }
109
+ critical(...args) {
110
+ emit("CRITICAL", args);
111
+ }
112
+ };
113
+ var logger = new LexmountLogger();
114
+ function getLogger() {
115
+ return logger;
116
+ }
117
+ function setLogLevel(level) {
118
+ if (typeof level !== "string" || level.trim() === "") {
119
+ throw new ValidationError(
120
+ "Invalid log level. Valid levels are: DEBUG, INFO, WARNING, ERROR, CRITICAL, SILENT"
121
+ );
122
+ }
123
+ const normalizedLevel = level.toUpperCase();
124
+ if (!isLogLevel(normalizedLevel)) {
125
+ throw new ValidationError(
126
+ `Invalid log level: ${level}. Valid levels are: DEBUG, INFO, WARNING, ERROR, CRITICAL, SILENT`
127
+ );
128
+ }
129
+ currentLevel = normalizedLevel;
130
+ logger.debug(`Log level set to ${normalizedLevel}`);
131
+ }
132
+ function disableLogging() {
133
+ currentLevel = "SILENT";
134
+ }
135
+ function enableLogging(level = "INFO") {
136
+ setLogLevel(level);
137
+ }
138
+
139
+ // src/contexts.ts
140
+ function asRecord(value) {
141
+ return typeof value === "object" && value !== null ? value : {};
142
+ }
143
+ function getString(value) {
144
+ return typeof value === "string" ? value : void 0;
145
+ }
146
+ function parseTimestamp(value) {
147
+ if (typeof value === "string" && value.length > 0) {
148
+ return value;
149
+ }
150
+ if (typeof value === "number" && Number.isFinite(value)) {
151
+ return new Date(value).toISOString();
152
+ }
153
+ return null;
154
+ }
155
+ function normalizeContextStatus(value) {
156
+ if (value === true || value === "locked") {
157
+ return "locked";
158
+ }
159
+ if (value === false || value === "available") {
160
+ return "available";
161
+ }
162
+ return typeof value === "string" ? value : "available";
163
+ }
164
+ var ContextInfo = class {
165
+ constructor(options) {
166
+ this.id = options.id;
167
+ this.status = options.status;
168
+ this.metadata = options.metadata ?? {};
169
+ this.createdAt = options.createdAt ?? null;
170
+ this.updatedAt = options.updatedAt ?? null;
171
+ }
172
+ get createdAtDate() {
173
+ if (!this.createdAt) {
174
+ return null;
175
+ }
176
+ const date = new Date(this.createdAt);
177
+ return Number.isNaN(date.getTime()) ? null : date;
178
+ }
179
+ get updatedAtDate() {
180
+ if (!this.updatedAt) {
181
+ return null;
182
+ }
183
+ const date = new Date(this.updatedAt);
184
+ return Number.isNaN(date.getTime()) ? null : date;
185
+ }
186
+ isLocked() {
187
+ return this.status === "locked";
188
+ }
189
+ isAvailable() {
190
+ return this.status === "available";
191
+ }
192
+ };
193
+ var ContextListResponse = class {
194
+ constructor(data) {
195
+ this.data = data;
196
+ }
197
+ get length() {
198
+ return this.data.length;
199
+ }
200
+ [Symbol.iterator]() {
201
+ return this.data[Symbol.iterator]();
202
+ }
203
+ at(index) {
204
+ return this.data[index];
205
+ }
206
+ };
207
+ var ContextsResource = class {
208
+ constructor(client) {
209
+ this.client = client;
210
+ }
211
+ /**
212
+ * Create a new persistent context.
213
+ */
214
+ async create(options = {}) {
215
+ const url = `${this.client.baseUrl}/instance/v1/contexts/create-context`;
216
+ const payload = {
217
+ api_key: this.client.apiKey,
218
+ project_id: this.client.projectId
219
+ };
220
+ if (options.metadata) {
221
+ payload.metadata = options.metadata;
222
+ }
223
+ const response = await this.client._post(url, payload);
224
+ if (response.status >= 400) {
225
+ this.handleError(response, "create");
226
+ }
227
+ const result = asRecord(response.data);
228
+ const contextId = getString(result.context_id);
229
+ if (!contextId) {
230
+ throw new APIError("Failed to create context: context_id missing from response", {
231
+ statusCode: response.status,
232
+ response: response.data
233
+ });
234
+ }
235
+ const context = new ContextInfo({
236
+ id: contextId,
237
+ status: normalizeContextStatus(result.locked),
238
+ metadata: result.metadata ?? options.metadata ?? {},
239
+ createdAt: parseTimestamp(result.created_at)
240
+ });
241
+ getLogger().info(`Successfully created context '${context.id}'`);
242
+ return context;
243
+ }
244
+ /**
245
+ * List contexts in the current project.
246
+ */
247
+ async list(options = {}) {
248
+ const url = `${this.client.baseUrl}/instance/v1/contexts/list-contexts`;
249
+ const payload = {
250
+ api_key: this.client.apiKey,
251
+ project_id: this.client.projectId,
252
+ limit: options.limit ?? 20
253
+ };
254
+ if (options.status) {
255
+ payload.status = options.status;
256
+ }
257
+ const response = await this.client._post(url, payload);
258
+ if (response.status >= 400) {
259
+ this.handleError(response, "list");
260
+ }
261
+ const result = asRecord(response.data);
262
+ const contexts = Array.isArray(result.contexts) ? result.contexts.map((item) => {
263
+ const context = asRecord(item);
264
+ return new ContextInfo({
265
+ id: getString(context.context_id) ?? "",
266
+ status: normalizeContextStatus(context.locked),
267
+ createdAt: parseTimestamp(context.created_at),
268
+ updatedAt: parseTimestamp(context.updated_at),
269
+ metadata: context.metadata ?? {}
270
+ });
271
+ }) : [];
272
+ getLogger().info(`Retrieved ${contexts.length} contexts`);
273
+ return new ContextListResponse(contexts);
274
+ }
275
+ /**
276
+ * Fetch a single context.
277
+ */
278
+ async get(contextId) {
279
+ const url = `${this.client.baseUrl}/instance/v1/contexts/${contextId}`;
280
+ const payload = {
281
+ api_key: this.client.apiKey,
282
+ project_id: this.client.projectId
283
+ };
284
+ const response = await this.client._post(url, payload);
285
+ if (response.status >= 400) {
286
+ this.handleError(response, "get", contextId);
287
+ }
288
+ const result = asRecord(response.data);
289
+ const context = asRecord(result.context);
290
+ const contextInfo = new ContextInfo({
291
+ id: getString(context.context_id) ?? contextId,
292
+ status: normalizeContextStatus(context.locked),
293
+ metadata: context.metadata ?? {},
294
+ createdAt: parseTimestamp(context.created_at),
295
+ updatedAt: parseTimestamp(context.updated_at)
296
+ });
297
+ getLogger().info(`Retrieved context '${contextInfo.id}' (status: ${contextInfo.status})`);
298
+ return contextInfo;
299
+ }
300
+ /**
301
+ * Delete a persistent context.
302
+ */
303
+ async delete(contextId) {
304
+ const url = `${this.client.baseUrl}/instance/v1/contexts/${contextId}`;
305
+ const payload = {
306
+ api_key: this.client.apiKey,
307
+ project_id: this.client.projectId
308
+ };
309
+ const response = await this.client._delete(url, payload);
310
+ if (response.status >= 400) {
311
+ this.handleError(response, "delete", contextId);
312
+ }
313
+ getLogger().info(`Successfully deleted context '${contextId}'`);
314
+ }
315
+ /**
316
+ * Force-release a stuck lock on a context.
317
+ */
318
+ async forceRelease(contextId) {
319
+ const url = `${this.client.baseUrl}/instance/v1/contexts/${contextId}/force-release`;
320
+ const payload = {
321
+ api_key: this.client.apiKey,
322
+ project_id: this.client.projectId
323
+ };
324
+ const response = await this.client._post(url, payload);
325
+ if (response.status >= 400) {
326
+ this.handleError(response, "force_release", contextId);
327
+ }
328
+ const result = asRecord(response.data);
329
+ return {
330
+ status: getString(result.status) ?? "unlocked",
331
+ message: getString(result.message) ?? "Lock released successfully"
332
+ };
333
+ }
334
+ handleError(response, operation, contextId) {
335
+ const errorData = asRecord(response.data);
336
+ const errorMessage = getString(errorData.error) ?? getString(errorData.message) ?? "Unknown error";
337
+ const errorCode = getString(errorData.code);
338
+ const metadata = asRecord(errorData.metadata);
339
+ if (response.status === 401) {
340
+ throw new AuthenticationError(`Authentication failed: ${errorMessage}`);
341
+ }
342
+ if (response.status === 404) {
343
+ const suffix = contextId ? `: ${contextId}` : "";
344
+ throw new ContextNotFoundError(`Context not found${suffix}`);
345
+ }
346
+ if (response.status === 409 && errorCode === "context_locked") {
347
+ throw new ContextLockedError(errorMessage, {
348
+ activeSessionId: getString(metadata.activeSessionId),
349
+ retryAfter: typeof metadata.retryAfter === "number" ? metadata.retryAfter : void 0
350
+ });
351
+ }
352
+ if (response.status === 409 && errorCode === "session_active") {
353
+ throw new APIError(
354
+ `Cannot ${operation} context: ${errorMessage} (session is active)`,
355
+ {
356
+ statusCode: response.status,
357
+ response: response.data
358
+ }
359
+ );
360
+ }
361
+ if (response.status === 500) {
362
+ const details = getString(errorData.details);
363
+ throw new APIError(
364
+ `Internal server error (500) during ${operation}: ${errorMessage}${details ? `. Details: ${details}` : ""}`,
365
+ {
366
+ statusCode: response.status,
367
+ response: response.data
368
+ }
369
+ );
370
+ }
371
+ if (response.status === 502) {
372
+ throw new APIError(
373
+ `Server gateway error (502) during ${operation}: The service may be temporarily unavailable. Please retry.`,
374
+ {
375
+ statusCode: response.status,
376
+ response: response.data
377
+ }
378
+ );
379
+ }
380
+ if (response.status === 503) {
381
+ throw new APIError(
382
+ `Service unavailable (503) during ${operation}: The service is temporarily unavailable. Please retry later.`,
383
+ {
384
+ statusCode: response.status,
385
+ response: response.data
386
+ }
387
+ );
388
+ }
389
+ if (response.status === 504) {
390
+ throw new APIError(
391
+ `Gateway timeout (504) during ${operation}: The request timed out. Please retry.`,
392
+ {
393
+ statusCode: response.status,
394
+ response: response.data
395
+ }
396
+ );
397
+ }
398
+ throw new APIError(`Failed to ${operation} context: ${errorMessage}`, {
399
+ statusCode: response.status,
400
+ response: response.data
401
+ });
402
+ }
403
+ };
404
+
405
+ // src/sessions.ts
406
+ function asRecord2(value) {
407
+ return typeof value === "object" && value !== null ? value : {};
408
+ }
409
+ function getString2(value) {
410
+ return typeof value === "string" ? value : void 0;
411
+ }
412
+ function getNumber(value) {
413
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
414
+ }
415
+ function normalizeContextMode(mode) {
416
+ if (mode === "readWrite" || mode === "read_write") {
417
+ return "read_write";
418
+ }
419
+ return "read_only";
420
+ }
421
+ var PaginationInfo = class {
422
+ constructor(shape) {
423
+ this.currentPage = shape.currentPage;
424
+ this.pageSize = shape.pageSize;
425
+ this.totalCount = shape.totalCount;
426
+ this.totalPages = shape.totalPages;
427
+ this.activeCount = shape.activeCount;
428
+ this.closedCount = shape.closedCount;
429
+ }
430
+ };
431
+ var SessionInfo = class {
432
+ /** @internal */
433
+ constructor(options) {
434
+ this.closed = false;
435
+ this.id = options.id;
436
+ this.sessionId = options.id;
437
+ this.status = options.status;
438
+ this.apiKey = options.apiKey;
439
+ this.projectId = options.projectId;
440
+ this.browserType = options.browserType;
441
+ this.createdAt = options.createdAt;
442
+ this.inspectUrl = options.inspectUrl;
443
+ this.containerId = options.containerId;
444
+ this.inspectUrlDbg = options.inspectUrlDbg;
445
+ this.ws = options.ws ?? null;
446
+ this.client = options.client;
447
+ }
448
+ /**
449
+ * Playwright/CDP connection URL.
450
+ */
451
+ get connectUrl() {
452
+ return this.ws ?? "";
453
+ }
454
+ /**
455
+ * Parsed session creation time.
456
+ */
457
+ get createdAtDate() {
458
+ const date = new Date(this.createdAt);
459
+ return Number.isNaN(date.getTime()) ? null : date;
460
+ }
461
+ /**
462
+ * Close the current session.
463
+ *
464
+ * This method is idempotent. Errors are logged and swallowed to keep cleanup safe.
465
+ */
466
+ async close() {
467
+ if (this.closed) {
468
+ getLogger().debug(`Session ${this.sessionId} is already closed.`);
469
+ return;
470
+ }
471
+ if (!this.client) {
472
+ getLogger().warn(
473
+ `Cannot close session ${this.sessionId}: client was not provided during session creation.`
474
+ );
475
+ return;
476
+ }
477
+ try {
478
+ getLogger().debug(`Closing session ${this.sessionId}`);
479
+ await this.client.sessions.delete({
480
+ sessionId: this.sessionId,
481
+ projectId: this.projectId
482
+ });
483
+ this.closed = true;
484
+ getLogger().info(`Session closed: ${this.sessionId}`);
485
+ } catch (error) {
486
+ getLogger().warn(`Failed to close session ${this.sessionId}:`, error);
487
+ }
488
+ }
489
+ };
490
+ var SessionListResponse = class {
491
+ constructor(sessions, pagination) {
492
+ this.sessions = sessions;
493
+ this.pagination = pagination;
494
+ }
495
+ /**
496
+ * Number of sessions in the current page.
497
+ */
498
+ get length() {
499
+ return this.sessions.length;
500
+ }
501
+ [Symbol.iterator]() {
502
+ return this.sessions[Symbol.iterator]();
503
+ }
504
+ at(index) {
505
+ return this.sessions[index];
506
+ }
507
+ };
508
+ var SessionsResource = class {
509
+ constructor(client) {
510
+ this.client = client;
511
+ }
512
+ /**
513
+ * Create a new browser session.
514
+ */
515
+ async create(options = {}) {
516
+ const url = `${this.client.baseUrl}/instance`;
517
+ const payload = {
518
+ api_key: this.client.apiKey,
519
+ project_id: options.projectId ?? this.client.projectId,
520
+ browser_mode: options.browserMode ?? "normal"
521
+ };
522
+ if (options.context) {
523
+ const contextPayload = {
524
+ id: options.context.id,
525
+ mode: normalizeContextMode(options.context.mode)
526
+ };
527
+ payload.context = contextPayload;
528
+ getLogger().debug(
529
+ `Creating session with context (id=${options.context.id}, mode=${contextPayload.mode})`
530
+ );
531
+ } else {
532
+ getLogger().debug(`Creating session with browser_mode=${payload.browser_mode}`);
533
+ }
534
+ const response = await this.client._post(url, payload);
535
+ if (response.status >= 400) {
536
+ this.handleCreateError(response);
537
+ }
538
+ const result = asRecord2(response.data);
539
+ const sessionId = getString2(result.session_id);
540
+ if (!sessionId) {
541
+ throw new APIError("Failed to create session: session_id missing from response", {
542
+ statusCode: response.status,
543
+ response: response.data
544
+ });
545
+ }
546
+ const containerId = getString2(result.container_id) ?? null;
547
+ const wsUrl = await this._getWebSocketDebuggerUrl(sessionId);
548
+ const projectId = options.projectId ?? this.client.projectId;
549
+ getLogger().info(`Session created successfully: id=${sessionId}, container_id=${containerId}`);
550
+ return new SessionInfo({
551
+ id: sessionId,
552
+ status: "active",
553
+ apiKey: this.client.apiKey,
554
+ projectId,
555
+ browserType: options.browserMode ?? "normal",
556
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
557
+ inspectUrl: `${this.client.baseUrl}/inspect?session_id=${sessionId}`,
558
+ containerId,
559
+ ws: wsUrl,
560
+ client: this.client
561
+ });
562
+ }
563
+ /**
564
+ * List sessions for the current project.
565
+ */
566
+ async list(options = {}) {
567
+ const url = `${this.client.baseUrl}/instance/v2/sessions`;
568
+ const payload = {
569
+ api_key: this.client.apiKey,
570
+ project_id: options.projectId ?? this.client.projectId
571
+ };
572
+ if (options.status) {
573
+ payload.status = options.status;
574
+ }
575
+ const response = await this.client._post(url, payload);
576
+ if (response.status >= 400) {
577
+ this.handleListError(response);
578
+ }
579
+ const result = asRecord2(response.data);
580
+ const sessionsData = Array.isArray(result.sessions) ? result.sessions : [];
581
+ const paginationData = asRecord2(result.pagination);
582
+ const sessions = sessionsData.map((item) => {
583
+ const session = asRecord2(item);
584
+ const sessionId = getString2(session.id) ?? "";
585
+ return new SessionInfo({
586
+ id: sessionId,
587
+ status: getString2(session.status) ?? "active",
588
+ apiKey: getString2(session.api_key) ?? this.client.apiKey,
589
+ projectId: getString2(session.project_id) ?? (options.projectId ?? this.client.projectId),
590
+ browserType: getString2(session.browser_type) ?? "normal",
591
+ createdAt: getString2(session.created_at) ?? "",
592
+ inspectUrl: getString2(session.inspect_url) ?? `${this.client.baseUrl}/inspect?session_id=${sessionId}`,
593
+ containerId: getString2(session.container_id) ?? null,
594
+ inspectUrlDbg: getString2(session.inspect_url_dbg),
595
+ ws: getString2(session.ws) ?? null,
596
+ client: this.client
597
+ });
598
+ });
599
+ const pagination = new PaginationInfo({
600
+ currentPage: getNumber(paginationData.currentPage) ?? 1,
601
+ pageSize: getNumber(paginationData.pageSize) ?? sessions.length,
602
+ totalCount: getNumber(paginationData.totalCount) ?? sessions.length,
603
+ totalPages: getNumber(paginationData.totalPages) ?? 1,
604
+ activeCount: getNumber(paginationData.activeCount) ?? 0,
605
+ closedCount: getNumber(paginationData.closedCount) ?? 0
606
+ });
607
+ getLogger().info(
608
+ `Retrieved ${sessions.length} sessions (total: ${pagination.totalCount}, active: ${pagination.activeCount}, closed: ${pagination.closedCount})`
609
+ );
610
+ return new SessionListResponse(sessions, pagination);
611
+ }
612
+ /**
613
+ * Delete a browser session.
614
+ */
615
+ async delete(options) {
616
+ const url = `${this.client.baseUrl}/instance`;
617
+ const payload = {
618
+ api_key: this.client.apiKey,
619
+ project_id: options.projectId ?? this.client.projectId,
620
+ session_id: options.sessionId
621
+ };
622
+ const response = await this.client._delete(url, payload);
623
+ if (response.status >= 400) {
624
+ this.handleDeleteError(response, options.sessionId);
625
+ }
626
+ getLogger().info(`Session deleted successfully: ${options.sessionId}`);
627
+ }
628
+ /**
629
+ * Fetch the debugger WebSocket URL for a session.
630
+ *
631
+ * @internal
632
+ */
633
+ async _getWebSocketDebuggerUrl(sessionId) {
634
+ try {
635
+ const response = await this.client._get(`${this.client.baseUrl}/json/version`, {
636
+ session_id: sessionId
637
+ });
638
+ if (response.status >= 400) {
639
+ getLogger().error("Error getting WebSocket debugger URL:", response.data);
640
+ return null;
641
+ }
642
+ const result = asRecord2(response.data);
643
+ return getString2(result.webSocketDebuggerUrlTransformed) ?? getString2(result.webSocketDebuggerUrl) ?? null;
644
+ } catch (error) {
645
+ getLogger().error("Error getting WebSocket debugger URL:", error);
646
+ return null;
647
+ }
648
+ }
649
+ handleCreateError(response) {
650
+ const errorData = asRecord2(response.data);
651
+ const errorMessage = getString2(errorData.error) ?? getString2(errorData.message) ?? "Unknown error";
652
+ const errorCode = getString2(errorData.code);
653
+ const metadata = asRecord2(errorData.metadata);
654
+ if (response.status === 401) {
655
+ throw new AuthenticationError(
656
+ `Authentication failed: ${errorMessage}. Please check your API key and project ID.`
657
+ );
658
+ }
659
+ if (response.status === 404 && errorCode === "context_not_found") {
660
+ throw new ContextNotFoundError(`Context not found: ${errorMessage}`);
661
+ }
662
+ if (response.status === 404) {
663
+ throw new SessionNotFoundError(`Resource not found: ${errorMessage}`);
664
+ }
665
+ if (response.status === 409 && errorCode === "context_locked") {
666
+ throw new ContextLockedError(errorMessage, {
667
+ activeSessionId: getString2(metadata.activeSessionId),
668
+ retryAfter: getNumber(metadata.retryAfter)
669
+ });
670
+ }
671
+ throw new APIError(`Failed to create session: ${errorMessage}`, {
672
+ statusCode: response.status,
673
+ response: response.data
674
+ });
675
+ }
676
+ handleListError(response) {
677
+ const errorData = asRecord2(response.data);
678
+ const errorMessage = getString2(errorData.error) ?? getString2(errorData.message) ?? "Unknown error";
679
+ if (response.status === 401) {
680
+ throw new AuthenticationError(
681
+ `Authentication failed: ${errorMessage}. Please check your API key and project ID.`
682
+ );
683
+ }
684
+ throw new APIError(`Failed to list sessions: ${errorMessage}`, {
685
+ statusCode: response.status,
686
+ response: response.data
687
+ });
688
+ }
689
+ handleDeleteError(response, sessionId) {
690
+ const errorData = asRecord2(response.data);
691
+ const errorMessage = getString2(errorData.error) ?? getString2(errorData.message) ?? "Unknown error";
692
+ if (response.status === 401) {
693
+ throw new AuthenticationError(
694
+ `Authentication failed: ${errorMessage}. Please check your API key and project ID.`
695
+ );
696
+ }
697
+ if (response.status === 404) {
698
+ throw new SessionNotFoundError(
699
+ `Session not found: ${errorMessage}. Session ID '${sessionId}' may have already been deleted or never existed.`
700
+ );
701
+ }
702
+ throw new APIError(`Failed to delete session: ${errorMessage}`, {
703
+ statusCode: response.status,
704
+ response: response.data
705
+ });
706
+ }
707
+ };
708
+
709
+ // src/client.ts
710
+ dotenv.config();
711
+ var Lexmount = class {
712
+ constructor(config2 = {}) {
713
+ this.apiKey = config2.apiKey ?? process.env.LEXMOUNT_API_KEY ?? "";
714
+ if (!this.apiKey) {
715
+ throw new ValidationError(
716
+ "apiKey must be provided or set LEXMOUNT_API_KEY environment variable"
717
+ );
718
+ }
719
+ this.projectId = config2.projectId ?? process.env.LEXMOUNT_PROJECT_ID ?? "";
720
+ if (!this.projectId) {
721
+ throw new ValidationError(
722
+ "projectId must be provided or set LEXMOUNT_PROJECT_ID environment variable"
723
+ );
724
+ }
725
+ this.baseUrl = (config2.baseUrl ?? process.env.LEXMOUNT_BASE_URL ?? "https://api.lexmount.cn").replace(/\/$/, "");
726
+ if (config2.logLevel) {
727
+ setLogLevel(config2.logLevel);
728
+ }
729
+ this.httpClient = axios.create({
730
+ timeout: config2.timeout ?? 6e4,
731
+ validateStatus: () => true,
732
+ headers: {
733
+ "Content-Type": "application/json"
734
+ }
735
+ });
736
+ this.sessions = new SessionsResource(this);
737
+ this.contexts = new ContextsResource(this);
738
+ }
739
+ /**
740
+ * Internal POST request helper with timeout and network error mapping.
741
+ *
742
+ * @internal
743
+ */
744
+ async _post(url, data) {
745
+ return this.request("POST", url, { data });
746
+ }
747
+ /**
748
+ * Internal GET request helper with timeout and network error mapping.
749
+ *
750
+ * @internal
751
+ */
752
+ async _get(url, params) {
753
+ return this.request("GET", url, { params });
754
+ }
755
+ /**
756
+ * Internal DELETE request helper with timeout and network error mapping.
757
+ *
758
+ * @internal
759
+ */
760
+ async _delete(url, data) {
761
+ return this.request("DELETE", url, { data });
762
+ }
763
+ /**
764
+ * Close the client.
765
+ *
766
+ * Axios does not require explicit teardown, but the method is kept for API symmetry.
767
+ */
768
+ close() {
769
+ getLogger().debug("Closing Lexmount client");
770
+ }
771
+ /**
772
+ * String representation of the client.
773
+ */
774
+ toString() {
775
+ return `Lexmount(projectId='${this.projectId}', baseUrl='${this.baseUrl}')`;
776
+ }
777
+ async request(method, url, config2) {
778
+ const logger2 = getLogger();
779
+ const startTime = Date.now();
780
+ logger2.debug(`${method} request to ${url}`);
781
+ try {
782
+ const response = await this.httpClient.request({
783
+ method,
784
+ url,
785
+ ...config2
786
+ });
787
+ const elapsed = Date.now() - startTime;
788
+ logger2.debug(`${method} response: status=${response.status}, duration=${elapsed.toFixed(2)}ms`);
789
+ return response;
790
+ } catch (error) {
791
+ const elapsed = Date.now() - startTime;
792
+ throw this.normalizeRequestError(error, elapsed);
793
+ }
794
+ }
795
+ normalizeRequestError(error, elapsedMs) {
796
+ const logger2 = getLogger();
797
+ if (!isAxiosError(error)) {
798
+ logger2.error(`Unexpected request error after ${elapsedMs.toFixed(2)}ms:`, error);
799
+ return new APIError("Unexpected HTTP error", { response: error });
800
+ }
801
+ const axiosError = error;
802
+ if (axiosError.code === "ECONNABORTED") {
803
+ logger2.error(`Request timeout after ${elapsedMs.toFixed(2)}ms:`, axiosError.message);
804
+ return new TimeoutError(`Request timed out: ${axiosError.message}`);
805
+ }
806
+ if (axiosError.response) {
807
+ logger2.error(`HTTP error after ${elapsedMs.toFixed(2)}ms:`, axiosError.message);
808
+ return new APIError(`HTTP error: ${axiosError.message}`, {
809
+ statusCode: axiosError.response.status,
810
+ response: axiosError.response.data
811
+ });
812
+ }
813
+ logger2.error(`Network error after ${elapsedMs.toFixed(2)}ms:`, axiosError.message);
814
+ return new NetworkError(`Network error: ${axiosError.message}`);
815
+ }
816
+ };
817
+
818
+ // src/index.ts
819
+ var VERSION = "0.2.0";
820
+ export {
821
+ APIError,
822
+ AuthenticationError,
823
+ ContextInfo,
824
+ ContextListResponse,
825
+ ContextLockedError,
826
+ ContextNotFoundError,
827
+ ContextsResource,
828
+ Lexmount,
829
+ LexmountError,
830
+ LexmountLogger,
831
+ NetworkError,
832
+ PaginationInfo,
833
+ SessionInfo,
834
+ SessionListResponse,
835
+ SessionNotFoundError,
836
+ SessionsResource,
837
+ TimeoutError,
838
+ VERSION,
839
+ ValidationError,
840
+ Lexmount as default,
841
+ disableLogging,
842
+ enableLogging,
843
+ getLogger,
844
+ setLogLevel
845
+ };
846
+ //# sourceMappingURL=index.mjs.map