@virsanghavi/axis-server 1.0.9 → 1.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/mcp-server.mjs +221 -102
- package/package.json +1 -1
package/dist/mcp-server.mjs
CHANGED
|
@@ -13,6 +13,42 @@ import dotenv2 from "dotenv";
|
|
|
13
13
|
import fs from "fs/promises";
|
|
14
14
|
import path from "path";
|
|
15
15
|
import { Mutex } from "async-mutex";
|
|
16
|
+
|
|
17
|
+
// ../../src/utils/logger.ts
|
|
18
|
+
var Logger = class {
|
|
19
|
+
level = "info" /* INFO */;
|
|
20
|
+
setLevel(level) {
|
|
21
|
+
this.level = level;
|
|
22
|
+
}
|
|
23
|
+
log(level, message, meta) {
|
|
24
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
25
|
+
console.error(JSON.stringify({
|
|
26
|
+
timestamp,
|
|
27
|
+
level,
|
|
28
|
+
message,
|
|
29
|
+
...meta
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
debug(message, meta) {
|
|
33
|
+
if (this.level === "debug" /* DEBUG */) this.log("debug" /* DEBUG */, message, meta);
|
|
34
|
+
}
|
|
35
|
+
info(message, meta) {
|
|
36
|
+
this.log("info" /* INFO */, message, meta);
|
|
37
|
+
}
|
|
38
|
+
warn(message, meta) {
|
|
39
|
+
this.log("warn" /* WARN */, message, meta);
|
|
40
|
+
}
|
|
41
|
+
error(message, error, meta) {
|
|
42
|
+
this.log("error" /* ERROR */, message, {
|
|
43
|
+
...meta,
|
|
44
|
+
error: error instanceof Error ? error.message : String(error),
|
|
45
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var logger = new Logger();
|
|
50
|
+
|
|
51
|
+
// ../../src/local/context-manager.ts
|
|
16
52
|
import * as fsSync from "fs";
|
|
17
53
|
function getEffectiveInstructionsDir() {
|
|
18
54
|
const cwd = process.cwd();
|
|
@@ -131,45 +167,105 @@ var ContextManager = class {
|
|
|
131
167
|
throw new Error("SHARED_CONTEXT_API_URL not configured.");
|
|
132
168
|
}
|
|
133
169
|
const endpoint = this.apiUrl.endsWith("/v1") ? `${this.apiUrl}/search` : `${this.apiUrl}/v1/search`;
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
(
|
|
150
|
-
|
|
170
|
+
const maxRetries = 3;
|
|
171
|
+
const baseDelay = 1e3;
|
|
172
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
173
|
+
const controller = new AbortController();
|
|
174
|
+
const timeout = setTimeout(() => controller.abort(), 15e3);
|
|
175
|
+
try {
|
|
176
|
+
const response = await fetch(endpoint, {
|
|
177
|
+
method: "POST",
|
|
178
|
+
headers: {
|
|
179
|
+
"Content-Type": "application/json",
|
|
180
|
+
"Authorization": `Bearer ${this.apiSecret || ""}`
|
|
181
|
+
},
|
|
182
|
+
body: JSON.stringify({ query, projectName }),
|
|
183
|
+
signal: controller.signal
|
|
184
|
+
});
|
|
185
|
+
clearTimeout(timeout);
|
|
186
|
+
if (!response.ok) {
|
|
187
|
+
const text = await response.text();
|
|
188
|
+
if (response.status >= 400 && response.status < 500) {
|
|
189
|
+
throw new Error(`API Error ${response.status}: ${text}`);
|
|
190
|
+
}
|
|
191
|
+
if (attempt < maxRetries) {
|
|
192
|
+
const delay = baseDelay * Math.pow(2, attempt - 1);
|
|
193
|
+
logger.warn(`[searchContext] 5xx error, retrying in ${delay}ms (attempt ${attempt}/${maxRetries})`);
|
|
194
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
throw new Error(`API Error ${response.status}: ${text}`);
|
|
198
|
+
}
|
|
199
|
+
const result = await response.json();
|
|
200
|
+
if (result.results && Array.isArray(result.results)) {
|
|
201
|
+
return result.results.map(
|
|
202
|
+
(r) => `[Similarity: ${(r.similarity * 100).toFixed(1)}%] ${r.content}`
|
|
203
|
+
).join("\n\n---\n\n") || "No results found.";
|
|
204
|
+
}
|
|
205
|
+
throw new Error("No results format recognized.");
|
|
206
|
+
} catch (e) {
|
|
207
|
+
clearTimeout(timeout);
|
|
208
|
+
if (e.message.startsWith("API Error 4")) throw e;
|
|
209
|
+
if (attempt < maxRetries) {
|
|
210
|
+
const delay = baseDelay * Math.pow(2, attempt - 1);
|
|
211
|
+
logger.warn(`[searchContext] Network/timeout error, retrying in ${delay}ms (attempt ${attempt}/${maxRetries}): ${e.message}`);
|
|
212
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
throw e;
|
|
216
|
+
}
|
|
151
217
|
}
|
|
152
|
-
throw new Error("
|
|
218
|
+
throw new Error("searchContext: unexpected end of retry loop");
|
|
153
219
|
}
|
|
154
220
|
async embedContent(items, projectName = "default") {
|
|
155
221
|
if (!this.apiUrl) {
|
|
156
|
-
|
|
222
|
+
logger.warn("Skipping RAG embedding: SHARED_CONTEXT_API_URL not configured.");
|
|
157
223
|
return;
|
|
158
224
|
}
|
|
159
225
|
const endpoint = this.apiUrl.endsWith("/v1") ? `${this.apiUrl}/embed` : `${this.apiUrl}/v1/embed`;
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
226
|
+
const maxRetries = 3;
|
|
227
|
+
const baseDelay = 1e3;
|
|
228
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
229
|
+
const controller = new AbortController();
|
|
230
|
+
const timeout = setTimeout(() => controller.abort(), 15e3);
|
|
231
|
+
try {
|
|
232
|
+
const response = await fetch(endpoint, {
|
|
233
|
+
method: "POST",
|
|
234
|
+
headers: {
|
|
235
|
+
"Content-Type": "application/json",
|
|
236
|
+
"Authorization": `Bearer ${this.apiSecret || ""}`
|
|
237
|
+
},
|
|
238
|
+
body: JSON.stringify({ items, projectName }),
|
|
239
|
+
signal: controller.signal
|
|
240
|
+
});
|
|
241
|
+
clearTimeout(timeout);
|
|
242
|
+
if (!response.ok) {
|
|
243
|
+
const text = await response.text();
|
|
244
|
+
if (response.status >= 400 && response.status < 500) {
|
|
245
|
+
throw new Error(`API Error ${response.status}: ${text}`);
|
|
246
|
+
}
|
|
247
|
+
if (attempt < maxRetries) {
|
|
248
|
+
const delay = baseDelay * Math.pow(2, attempt - 1);
|
|
249
|
+
logger.warn(`[embedContent] 5xx error, retrying in ${delay}ms (attempt ${attempt}/${maxRetries})`);
|
|
250
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
throw new Error(`API Error ${response.status}: ${text}`);
|
|
254
|
+
}
|
|
255
|
+
return await response.json();
|
|
256
|
+
} catch (e) {
|
|
257
|
+
clearTimeout(timeout);
|
|
258
|
+
if (e.message.startsWith("API Error 4")) throw e;
|
|
259
|
+
if (attempt < maxRetries) {
|
|
260
|
+
const delay = baseDelay * Math.pow(2, attempt - 1);
|
|
261
|
+
logger.warn(`[embedContent] Network/timeout error, retrying in ${delay}ms (attempt ${attempt}/${maxRetries}): ${e.message}`);
|
|
262
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
logger.warn(`[embedContent] Failed after ${maxRetries} attempts: ${e.message}. Skipping embed.`);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
171
268
|
}
|
|
172
|
-
return await response.json();
|
|
173
269
|
}
|
|
174
270
|
};
|
|
175
271
|
|
|
@@ -178,44 +274,16 @@ import { Mutex as Mutex2 } from "async-mutex";
|
|
|
178
274
|
import { createClient } from "@supabase/supabase-js";
|
|
179
275
|
import fs2 from "fs/promises";
|
|
180
276
|
import path2 from "path";
|
|
181
|
-
|
|
182
|
-
// ../../src/utils/logger.ts
|
|
183
|
-
var Logger = class {
|
|
184
|
-
level = "info" /* INFO */;
|
|
185
|
-
setLevel(level) {
|
|
186
|
-
this.level = level;
|
|
187
|
-
}
|
|
188
|
-
log(level, message, meta) {
|
|
189
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
190
|
-
console.error(JSON.stringify({
|
|
191
|
-
timestamp,
|
|
192
|
-
level,
|
|
193
|
-
message,
|
|
194
|
-
...meta
|
|
195
|
-
}));
|
|
196
|
-
}
|
|
197
|
-
debug(message, meta) {
|
|
198
|
-
if (this.level === "debug" /* DEBUG */) this.log("debug" /* DEBUG */, message, meta);
|
|
199
|
-
}
|
|
200
|
-
info(message, meta) {
|
|
201
|
-
this.log("info" /* INFO */, message, meta);
|
|
202
|
-
}
|
|
203
|
-
warn(message, meta) {
|
|
204
|
-
this.log("warn" /* WARN */, message, meta);
|
|
205
|
-
}
|
|
206
|
-
error(message, error, meta) {
|
|
207
|
-
this.log("error" /* ERROR */, message, {
|
|
208
|
-
...meta,
|
|
209
|
-
error: error instanceof Error ? error.message : String(error),
|
|
210
|
-
stack: error instanceof Error ? error.stack : void 0
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
var logger = new Logger();
|
|
215
|
-
|
|
216
|
-
// ../../src/local/nerve-center.ts
|
|
217
277
|
var STATE_FILE = process.env.NERVE_CENTER_STATE_FILE || path2.join(process.cwd(), "history", "nerve-center-state.json");
|
|
218
278
|
var LOCK_TIMEOUT_DEFAULT = 30 * 60 * 1e3;
|
|
279
|
+
var CIRCUIT_FAILURE_THRESHOLD = 5;
|
|
280
|
+
var CIRCUIT_COOLDOWN_MS = 6e4;
|
|
281
|
+
var CircuitOpenError = class extends Error {
|
|
282
|
+
constructor() {
|
|
283
|
+
super("Circuit breaker open \u2014 remote API temporarily unavailable, falling back to local");
|
|
284
|
+
this.name = "CircuitOpenError";
|
|
285
|
+
}
|
|
286
|
+
};
|
|
219
287
|
var NerveCenter = class {
|
|
220
288
|
mutex;
|
|
221
289
|
state;
|
|
@@ -227,6 +295,8 @@ var NerveCenter = class {
|
|
|
227
295
|
// Renamed backing field
|
|
228
296
|
projectName;
|
|
229
297
|
useSupabase;
|
|
298
|
+
_circuitFailures = 0;
|
|
299
|
+
_circuitOpenUntil = 0;
|
|
230
300
|
/**
|
|
231
301
|
* @param contextManager - Instance of ContextManager for legacy operations
|
|
232
302
|
* @param options - Configuration options for state persistence and timeouts
|
|
@@ -335,40 +405,88 @@ var NerveCenter = class {
|
|
|
335
405
|
logger.error("[callCoordination] Remote API not configured - apiUrl is:", this.contextManager.apiUrl);
|
|
336
406
|
throw new Error("Remote API not configured");
|
|
337
407
|
}
|
|
408
|
+
if (this._circuitFailures >= CIRCUIT_FAILURE_THRESHOLD && Date.now() < this._circuitOpenUntil) {
|
|
409
|
+
logger.warn(`[callCoordination] Circuit breaker OPEN \u2014 skipping remote call (resets at ${new Date(this._circuitOpenUntil).toISOString()})`);
|
|
410
|
+
throw new CircuitOpenError();
|
|
411
|
+
}
|
|
412
|
+
if (this._circuitFailures >= CIRCUIT_FAILURE_THRESHOLD && Date.now() >= this._circuitOpenUntil) {
|
|
413
|
+
logger.info("[callCoordination] Circuit breaker half-open \u2014 allowing probe request");
|
|
414
|
+
}
|
|
338
415
|
const url = this.contextManager.apiUrl.endsWith("/v1") ? `${this.contextManager.apiUrl}/${endpoint}` : `${this.contextManager.apiUrl}/v1/${endpoint}`;
|
|
339
416
|
logger.info(`[callCoordination] Full URL: ${method} ${url}`);
|
|
340
417
|
logger.info(`[callCoordination] Request body: ${body ? JSON.stringify({ ...body, projectName: this.projectName }) : "none"}`);
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
418
|
+
const maxRetries = 3;
|
|
419
|
+
const baseDelay = 1e3;
|
|
420
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
421
|
+
const controller = new AbortController();
|
|
422
|
+
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
423
|
+
try {
|
|
424
|
+
const response = await fetch(url, {
|
|
425
|
+
method,
|
|
426
|
+
headers: {
|
|
427
|
+
"Content-Type": "application/json",
|
|
428
|
+
"Authorization": `Bearer ${this.contextManager.apiSecret || ""}`
|
|
429
|
+
},
|
|
430
|
+
body: body ? JSON.stringify({ ...body, projectName: this.projectName }) : void 0,
|
|
431
|
+
signal: controller.signal
|
|
432
|
+
});
|
|
433
|
+
clearTimeout(timeout);
|
|
434
|
+
logger.info(`[callCoordination] Response status: ${response.status} ${response.statusText}`);
|
|
435
|
+
if (!response.ok) {
|
|
436
|
+
const text = await response.text();
|
|
437
|
+
logger.error(`[callCoordination] API Error Response (${response.status}): ${text}`);
|
|
438
|
+
if (response.status >= 400 && response.status < 500) {
|
|
439
|
+
if (response.status === 401) {
|
|
440
|
+
throw new Error(`Authentication failed (401): ${text}. Check if API key is valid and exists in api_keys table.`);
|
|
441
|
+
}
|
|
442
|
+
throw new Error(`API Error (${response.status}): ${text}`);
|
|
443
|
+
}
|
|
444
|
+
if (attempt < maxRetries) {
|
|
445
|
+
const delay = baseDelay * Math.pow(2, attempt - 1);
|
|
446
|
+
logger.warn(`[callCoordination] 5xx error, retrying in ${delay}ms (attempt ${attempt}/${maxRetries})`);
|
|
447
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
this._circuitFailures++;
|
|
451
|
+
if (this._circuitFailures >= CIRCUIT_FAILURE_THRESHOLD) {
|
|
452
|
+
this._circuitOpenUntil = Date.now() + CIRCUIT_COOLDOWN_MS;
|
|
453
|
+
logger.error(`[callCoordination] Circuit breaker OPENED after ${this._circuitFailures} consecutive failures`);
|
|
454
|
+
}
|
|
455
|
+
throw new Error(`Server error (${response.status}): ${text}. Check Vercel logs for details.`);
|
|
360
456
|
}
|
|
457
|
+
if (this._circuitFailures > 0) {
|
|
458
|
+
logger.info(`[callCoordination] Request succeeded, resetting circuit breaker (was at ${this._circuitFailures} failures)`);
|
|
459
|
+
this._circuitFailures = 0;
|
|
460
|
+
this._circuitOpenUntil = 0;
|
|
461
|
+
}
|
|
462
|
+
const jsonResult = await response.json();
|
|
463
|
+
logger.info(`[callCoordination] Success - Response: ${JSON.stringify(jsonResult).substring(0, 200)}...`);
|
|
464
|
+
return jsonResult;
|
|
465
|
+
} catch (e) {
|
|
466
|
+
clearTimeout(timeout);
|
|
467
|
+
if (e instanceof CircuitOpenError) throw e;
|
|
468
|
+
if (e.message.includes("Authentication failed") || e.message.includes("API Error (4")) {
|
|
469
|
+
throw e;
|
|
470
|
+
}
|
|
471
|
+
if (attempt < maxRetries) {
|
|
472
|
+
const delay = baseDelay * Math.pow(2, attempt - 1);
|
|
473
|
+
logger.warn(`[callCoordination] Network/timeout error, retrying in ${delay}ms (attempt ${attempt}/${maxRetries}): ${e.message}`);
|
|
474
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
this._circuitFailures++;
|
|
478
|
+
if (this._circuitFailures >= CIRCUIT_FAILURE_THRESHOLD) {
|
|
479
|
+
this._circuitOpenUntil = Date.now() + CIRCUIT_COOLDOWN_MS;
|
|
480
|
+
logger.error(`[callCoordination] Circuit breaker OPENED after ${this._circuitFailures} consecutive failures`);
|
|
481
|
+
}
|
|
482
|
+
logger.error(`[callCoordination] Fetch failed after ${maxRetries} attempts: ${e.message}`, e);
|
|
483
|
+
if (e.message.includes("401")) {
|
|
484
|
+
throw new Error(`API Authentication Error: ${e.message}. Verify AXIS_API_KEY in MCP config matches a key in the api_keys table.`);
|
|
485
|
+
}
|
|
486
|
+
throw e;
|
|
361
487
|
}
|
|
362
|
-
const jsonResult = await response.json();
|
|
363
|
-
logger.info(`[callCoordination] Success - Response: ${JSON.stringify(jsonResult).substring(0, 200)}...`);
|
|
364
|
-
return jsonResult;
|
|
365
|
-
} catch (e) {
|
|
366
|
-
logger.error(`[callCoordination] Fetch failed: ${e.message}`, e);
|
|
367
|
-
if (e.message.includes("Authentication failed") || e.message.includes("401")) {
|
|
368
|
-
throw new Error(`API Authentication Error: ${e.message}. Verify AXIS_API_KEY in MCP config matches a key in the api_keys table.`);
|
|
369
|
-
}
|
|
370
|
-
throw e;
|
|
371
488
|
}
|
|
489
|
+
throw new Error("callCoordination: unexpected end of retry loop");
|
|
372
490
|
}
|
|
373
491
|
jobFromRecord(record) {
|
|
374
492
|
return {
|
|
@@ -479,6 +597,7 @@ var NerveCenter = class {
|
|
|
479
597
|
p_text: text
|
|
480
598
|
});
|
|
481
599
|
} catch (e) {
|
|
600
|
+
logger.warn("Notepad RPC append failed", e);
|
|
482
601
|
}
|
|
483
602
|
}
|
|
484
603
|
if (this.contextManager.apiUrl) {
|
|
@@ -955,7 +1074,7 @@ var RagEngine = class {
|
|
|
955
1074
|
}
|
|
956
1075
|
async indexContent(filePath, content) {
|
|
957
1076
|
if (!this.projectId) {
|
|
958
|
-
|
|
1077
|
+
logger.error("RAG: Project ID missing.");
|
|
959
1078
|
return false;
|
|
960
1079
|
}
|
|
961
1080
|
try {
|
|
@@ -973,13 +1092,13 @@ var RagEngine = class {
|
|
|
973
1092
|
metadata: { filePath }
|
|
974
1093
|
});
|
|
975
1094
|
if (error) {
|
|
976
|
-
|
|
1095
|
+
logger.error("RAG Insert Error:", error);
|
|
977
1096
|
return false;
|
|
978
1097
|
}
|
|
979
1098
|
logger.info(`Indexed ${filePath}`);
|
|
980
1099
|
return true;
|
|
981
1100
|
} catch (e) {
|
|
982
|
-
|
|
1101
|
+
logger.error("RAG Error:", e);
|
|
983
1102
|
return false;
|
|
984
1103
|
}
|
|
985
1104
|
}
|
|
@@ -998,12 +1117,12 @@ var RagEngine = class {
|
|
|
998
1117
|
p_project_id: this.projectId
|
|
999
1118
|
});
|
|
1000
1119
|
if (error || !data) {
|
|
1001
|
-
|
|
1120
|
+
logger.error("RAG Search DB Error:", error);
|
|
1002
1121
|
return [];
|
|
1003
1122
|
}
|
|
1004
1123
|
return data.map((d) => d.content);
|
|
1005
1124
|
} catch (e) {
|
|
1006
|
-
|
|
1125
|
+
logger.error("RAG Search Fail:", e);
|
|
1007
1126
|
return [];
|
|
1008
1127
|
}
|
|
1009
1128
|
}
|
|
@@ -1037,7 +1156,7 @@ if (process.env.SHARED_CONTEXT_API_URL || process.env.AXIS_API_KEY) {
|
|
|
1037
1156
|
}
|
|
1038
1157
|
if (!envLoaded) {
|
|
1039
1158
|
logger.warn("No configuration found from MCP client (mcp.json) or .env.local");
|
|
1040
|
-
logger.warn("MCP server will use default API URL: https://
|
|
1159
|
+
logger.warn("MCP server will use default API URL: https://useaxis.dev/api/v1");
|
|
1041
1160
|
}
|
|
1042
1161
|
}
|
|
1043
1162
|
logger.info("=== Axis MCP Server Starting ===");
|
|
@@ -1049,7 +1168,7 @@ logger.info("Environment check:", {
|
|
|
1049
1168
|
hasSUPABASE_SERVICE_ROLE_KEY: !!process.env.SUPABASE_SERVICE_ROLE_KEY,
|
|
1050
1169
|
PROJECT_NAME: process.env.PROJECT_NAME || "default"
|
|
1051
1170
|
});
|
|
1052
|
-
var apiUrl = process.env.SHARED_CONTEXT_API_URL || process.env.AXIS_API_URL || "https://
|
|
1171
|
+
var apiUrl = process.env.SHARED_CONTEXT_API_URL || process.env.AXIS_API_URL || "https://useaxis.dev/api/v1";
|
|
1053
1172
|
var apiSecret = process.env.AXIS_API_KEY || process.env.SHARED_CONTEXT_API_SECRET || process.env.AXIS_API_SECRET;
|
|
1054
1173
|
var useRemoteApiOnly = !!process.env.SHARED_CONTEXT_API_URL || !!process.env.AXIS_API_KEY;
|
|
1055
1174
|
if (useRemoteApiOnly) {
|