vigthoria-cli 1.6.4 → 1.6.8
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/README.md +9 -1
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +23 -4
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/chat.d.ts +9 -0
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +158 -5
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +10 -2
- package/dist/commands/config.js.map +1 -1
- package/dist/index.js +18 -2
- package/dist/index.js.map +1 -1
- package/dist/utils/api.d.ts +50 -0
- package/dist/utils/api.d.ts.map +1 -1
- package/dist/utils/api.js +703 -34
- package/dist/utils/api.js.map +1 -1
- package/dist/utils/config.d.ts +1 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +3 -1
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/session.d.ts +12 -0
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +103 -2
- package/dist/utils/session.js.map +1 -1
- package/dist/utils/tools.d.ts +1 -0
- package/dist/utils/tools.d.ts.map +1 -1
- package/dist/utils/tools.js +94 -6
- package/dist/utils/tools.js.map +1 -1
- package/package.json +4 -2
package/dist/utils/api.js
CHANGED
|
@@ -9,8 +9,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.APIClient = void 0;
|
|
11
11
|
const axios_1 = __importDefault(require("axios"));
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
13
|
const https_1 = __importDefault(require("https"));
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
13
15
|
const ws_1 = __importDefault(require("ws"));
|
|
16
|
+
const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
|
|
17
|
+
const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS || '1200000';
|
|
18
|
+
const parsed = Number.parseInt(rawValue, 10);
|
|
19
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 1200000;
|
|
20
|
+
})();
|
|
21
|
+
const DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS = (() => {
|
|
22
|
+
const rawValue = process.env.VIGTHORIA_AGENT_IDLE_TIMEOUT_MS || process.env.V3_AGENT_IDLE_TIMEOUT_MS || '90000';
|
|
23
|
+
const parsed = Number.parseInt(rawValue, 10);
|
|
24
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 90000;
|
|
25
|
+
})();
|
|
14
26
|
class APIClient {
|
|
15
27
|
client;
|
|
16
28
|
modelRouterClient;
|
|
@@ -34,7 +46,7 @@ class APIClient {
|
|
|
34
46
|
httpsAgent,
|
|
35
47
|
headers: {
|
|
36
48
|
'Content-Type': 'application/json',
|
|
37
|
-
'User-Agent': `Vigthoria-CLI/${process.env.npm_package_version || '1.6.
|
|
49
|
+
'User-Agent': `Vigthoria-CLI/${process.env.npm_package_version || '1.6.8'}`,
|
|
38
50
|
},
|
|
39
51
|
});
|
|
40
52
|
// Direct AI Models API - bypasses Coder for direct model access
|
|
@@ -45,18 +57,19 @@ class APIClient {
|
|
|
45
57
|
httpsAgent,
|
|
46
58
|
headers: {
|
|
47
59
|
'Content-Type': 'application/json',
|
|
48
|
-
'User-Agent': `Vigthoria-CLI/${process.env.npm_package_version || '1.6.
|
|
60
|
+
'User-Agent': `Vigthoria-CLI/${process.env.npm_package_version || '1.6.8'}`,
|
|
49
61
|
},
|
|
50
62
|
});
|
|
51
|
-
// Self-hosted model router
|
|
52
|
-
|
|
53
|
-
|
|
63
|
+
// Self-hosted model router is opt-in only.
|
|
64
|
+
const selfHostedModelsApiUrl = this.getSelfHostedModelsApiUrl();
|
|
65
|
+
this.selfHostedModelRouterClient = selfHostedModelsApiUrl ? axios_1.default.create({
|
|
66
|
+
baseURL: selfHostedModelsApiUrl,
|
|
54
67
|
timeout: 240000,
|
|
55
68
|
headers: {
|
|
56
69
|
'Content-Type': 'application/json',
|
|
57
|
-
'User-Agent': `Vigthoria-CLI/${process.env.npm_package_version || '1.6.
|
|
70
|
+
'User-Agent': `Vigthoria-CLI/${process.env.npm_package_version || '1.6.8'}`,
|
|
58
71
|
},
|
|
59
|
-
});
|
|
72
|
+
}) : null;
|
|
60
73
|
// Add auth interceptor
|
|
61
74
|
this.client.interceptors.request.use((req) => {
|
|
62
75
|
const token = this.config.get('authToken');
|
|
@@ -74,7 +87,7 @@ class APIClient {
|
|
|
74
87
|
}
|
|
75
88
|
return req;
|
|
76
89
|
});
|
|
77
|
-
this.selfHostedModelRouterClient
|
|
90
|
+
this.selfHostedModelRouterClient?.interceptors.request.use((req) => {
|
|
78
91
|
const token = this.config.get('authToken');
|
|
79
92
|
if (token) {
|
|
80
93
|
req.headers.Authorization = `Bearer ${token}`;
|
|
@@ -92,6 +105,15 @@ class APIClient {
|
|
|
92
105
|
throw error;
|
|
93
106
|
});
|
|
94
107
|
}
|
|
108
|
+
getSelfHostedModelsApiUrl() {
|
|
109
|
+
const configuredUrl = process.env.VIGTHORIA_SELF_HOSTED_MODELS_API_URL
|
|
110
|
+
|| this.config.get('selfHostedModelsApiUrl');
|
|
111
|
+
if (!configuredUrl) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
const normalizedUrl = String(configuredUrl).trim().replace(/\/$/, '');
|
|
115
|
+
return normalizedUrl || null;
|
|
116
|
+
}
|
|
95
117
|
// Authentication - Uses Vigthoria Coder /api/login endpoint
|
|
96
118
|
async login(email, password) {
|
|
97
119
|
try {
|
|
@@ -124,26 +146,42 @@ class APIClient {
|
|
|
124
146
|
try {
|
|
125
147
|
// Validate token by making a request to user info endpoint
|
|
126
148
|
this.config.set('authToken', token);
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
149
|
+
const headers = {
|
|
150
|
+
Authorization: `Bearer ${token}`,
|
|
151
|
+
Cookie: `vigthoria-auth-token=${token}`,
|
|
152
|
+
};
|
|
153
|
+
const candidateEndpoints = [
|
|
154
|
+
'/api/user/profile',
|
|
155
|
+
'/api/user/info',
|
|
156
|
+
'/api/auth/me',
|
|
157
|
+
];
|
|
158
|
+
for (const endpoint of candidateEndpoints) {
|
|
159
|
+
try {
|
|
160
|
+
const response = await this.client.get(endpoint, { headers });
|
|
161
|
+
const profile = this.extractUserProfile(response.data);
|
|
162
|
+
if (!profile) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
this.config.setAuth({
|
|
166
|
+
token,
|
|
167
|
+
userId: profile.id,
|
|
168
|
+
email: profile.email,
|
|
169
|
+
});
|
|
170
|
+
this.config.setSubscription({
|
|
171
|
+
plan: profile.plan,
|
|
172
|
+
status: 'active',
|
|
173
|
+
expiresAt: undefined,
|
|
174
|
+
});
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
const axiosError = error;
|
|
179
|
+
if (axiosError.response?.status && axiosError.response.status !== 404) {
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
146
183
|
}
|
|
184
|
+
this.config.clearAuth();
|
|
147
185
|
return false;
|
|
148
186
|
}
|
|
149
187
|
catch (error) {
|
|
@@ -152,6 +190,27 @@ class APIClient {
|
|
|
152
190
|
return false;
|
|
153
191
|
}
|
|
154
192
|
}
|
|
193
|
+
extractUserProfile(data) {
|
|
194
|
+
if (!data) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
const rawUser = data.user || data;
|
|
198
|
+
const userId = rawUser.id;
|
|
199
|
+
const email = rawUser.email;
|
|
200
|
+
if (!userId || !email) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
const plan = data.subscription?.plan
|
|
204
|
+
|| rawUser.subscription?.plan
|
|
205
|
+
|| rawUser.subscription_plan
|
|
206
|
+
|| data.subscription_plan
|
|
207
|
+
|| 'developer';
|
|
208
|
+
return {
|
|
209
|
+
id: userId,
|
|
210
|
+
email,
|
|
211
|
+
plan,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
155
214
|
async refreshToken() {
|
|
156
215
|
const refreshToken = this.config.get('refreshToken');
|
|
157
216
|
if (!refreshToken)
|
|
@@ -186,6 +245,519 @@ class APIClient {
|
|
|
186
245
|
this.logger.debug('Failed to get subscription status:', error.message);
|
|
187
246
|
}
|
|
188
247
|
}
|
|
248
|
+
getAccessToken() {
|
|
249
|
+
return process.env.VIGTHORIA_TOKEN
|
|
250
|
+
|| process.env.VIGTHORIA_AUTH_TOKEN
|
|
251
|
+
|| this.config.get('authToken')
|
|
252
|
+
|| null;
|
|
253
|
+
}
|
|
254
|
+
getV3AgentBaseUrls() {
|
|
255
|
+
const configuredApiUrl = String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
|
|
256
|
+
const urls = [
|
|
257
|
+
process.env.VIGTHORIA_V3_AGENT_URL,
|
|
258
|
+
process.env.V3_AGENT_URL,
|
|
259
|
+
'http://127.0.0.1:8030',
|
|
260
|
+
configuredApiUrl,
|
|
261
|
+
].filter(Boolean).map((url) => String(url).replace(/\/$/, ''));
|
|
262
|
+
return [...new Set(urls)];
|
|
263
|
+
}
|
|
264
|
+
getV3AgentRunUrl(baseUrl) {
|
|
265
|
+
if (/127\.0\.0\.1:8030|localhost:8030/.test(baseUrl)) {
|
|
266
|
+
return `${baseUrl}/api/agent/run`;
|
|
267
|
+
}
|
|
268
|
+
return `${baseUrl}/api/v3-agent/run`;
|
|
269
|
+
}
|
|
270
|
+
async getV3AgentHeaders() {
|
|
271
|
+
const headers = {
|
|
272
|
+
'Content-Type': 'application/json',
|
|
273
|
+
};
|
|
274
|
+
const authToken = this.getAccessToken();
|
|
275
|
+
if (authToken) {
|
|
276
|
+
headers.Authorization = `Bearer ${authToken}`;
|
|
277
|
+
}
|
|
278
|
+
const serviceKey = process.env.VIGTHORIA_V3_SERVICE_KEY
|
|
279
|
+
|| process.env.V3_SERVICE_KEY
|
|
280
|
+
|| process.env.HYPERLOOP_SERVICE_KEY;
|
|
281
|
+
if (serviceKey) {
|
|
282
|
+
headers['X-Service-Key'] = serviceKey;
|
|
283
|
+
}
|
|
284
|
+
return headers;
|
|
285
|
+
}
|
|
286
|
+
buildV3AgentContext(context = {}) {
|
|
287
|
+
const targetPath = context.targetPath || context.projectPath || context.workspacePath || context.projectRoot || process.cwd();
|
|
288
|
+
return JSON.stringify({
|
|
289
|
+
workspace: context.workspace || null,
|
|
290
|
+
activeFile: context.activeFile || null,
|
|
291
|
+
history: context.history || [],
|
|
292
|
+
agentTaskType: context.agentTaskType || 'general',
|
|
293
|
+
executionSurface: context.executionSurface || 'cli',
|
|
294
|
+
clientSurface: context.clientSurface || 'cli',
|
|
295
|
+
localMachineCapable: context.localMachineCapable !== false,
|
|
296
|
+
workspacePath: context.workspacePath || targetPath,
|
|
297
|
+
projectPath: context.projectPath || targetPath,
|
|
298
|
+
targetPath,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
resolveAgentTargetPath(context = {}) {
|
|
302
|
+
return context.targetPath || context.projectPath || context.workspacePath || context.projectRoot || process.cwd();
|
|
303
|
+
}
|
|
304
|
+
hasAgentWorkspaceOutput(context = {}) {
|
|
305
|
+
try {
|
|
306
|
+
const root = this.resolveAgentTargetPath(context);
|
|
307
|
+
if (!root || !fs_1.default.existsSync(root)) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
const stack = [root];
|
|
311
|
+
while (stack.length > 0) {
|
|
312
|
+
const current = stack.pop();
|
|
313
|
+
if (!current)
|
|
314
|
+
continue;
|
|
315
|
+
const entries = fs_1.default.readdirSync(current, { withFileTypes: true });
|
|
316
|
+
for (const entry of entries) {
|
|
317
|
+
if (entry.name === '.git' || entry.name === 'node_modules') {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const fullPath = path_1.default.join(current, entry.name);
|
|
321
|
+
if (entry.isFile()) {
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
if (entry.isDirectory()) {
|
|
325
|
+
stack.push(fullPath);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
getAgentWorkspaceSnapshot(rootPath) {
|
|
336
|
+
const stack = [rootPath];
|
|
337
|
+
let fileCount = 0;
|
|
338
|
+
const entries = [];
|
|
339
|
+
while (stack.length > 0) {
|
|
340
|
+
const current = stack.pop();
|
|
341
|
+
if (!current) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
for (const entry of fs_1.default.readdirSync(current, { withFileTypes: true })) {
|
|
345
|
+
if (entry.name === '.git' || entry.name === 'node_modules') {
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
const fullPath = path_1.default.join(current, entry.name);
|
|
349
|
+
if (entry.isDirectory()) {
|
|
350
|
+
stack.push(fullPath);
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (!entry.isFile()) {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
fileCount += 1;
|
|
357
|
+
const stat = fs_1.default.statSync(fullPath);
|
|
358
|
+
entries.push(`${path_1.default.relative(rootPath, fullPath)}:${stat.size}:${stat.mtimeMs}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
entries.sort();
|
|
362
|
+
return {
|
|
363
|
+
fileCount,
|
|
364
|
+
paths: entries.map((entry) => entry.split(':', 1)[0]),
|
|
365
|
+
signature: entries.join('|'),
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
async waitForAgentWorkspaceSettle(context = {}, options = {}) {
|
|
369
|
+
const rootPath = this.resolveAgentTargetPath(context);
|
|
370
|
+
if (!rootPath || !fs_1.default.existsSync(rootPath)) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const timeoutMs = options.timeoutMs || 15000;
|
|
374
|
+
const pollMs = options.pollMs || 300;
|
|
375
|
+
const stableMs = options.stableMs || 1200;
|
|
376
|
+
const expectedFiles = Array.isArray(options.expectedFiles)
|
|
377
|
+
? options.expectedFiles.map((entry) => String(entry || '').trim()).filter(Boolean)
|
|
378
|
+
: [];
|
|
379
|
+
const start = Date.now();
|
|
380
|
+
let stableSince = 0;
|
|
381
|
+
let lastSignature = '';
|
|
382
|
+
while (Date.now() - start < timeoutMs) {
|
|
383
|
+
const snapshot = this.getAgentWorkspaceSnapshot(rootPath);
|
|
384
|
+
if (snapshot.fileCount === 0) {
|
|
385
|
+
stableSince = 0;
|
|
386
|
+
lastSignature = '';
|
|
387
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (expectedFiles.length > 0 && !expectedFiles.every((filePath) => snapshot.paths.includes(filePath))) {
|
|
391
|
+
stableSince = 0;
|
|
392
|
+
lastSignature = snapshot.signature;
|
|
393
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
if (snapshot.signature === lastSignature) {
|
|
397
|
+
if (!stableSince) {
|
|
398
|
+
stableSince = Date.now();
|
|
399
|
+
}
|
|
400
|
+
if (Date.now() - stableSince >= stableMs) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
lastSignature = snapshot.signature;
|
|
406
|
+
stableSince = Date.now();
|
|
407
|
+
}
|
|
408
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
extractExpectedWorkspaceFiles(message = '', context = {}) {
|
|
412
|
+
const candidates = new Set();
|
|
413
|
+
const addMatches = (value) => {
|
|
414
|
+
const text = String(value || '');
|
|
415
|
+
const patterns = [
|
|
416
|
+
/`([^`]+\.(?:html|css|js|jsx|ts|tsx|json|md|py|sh))`/gi,
|
|
417
|
+
/\b([A-Za-z0-9_./-]+\.(?:html|css|js|jsx|ts|tsx|json|md|py|sh))\b/g,
|
|
418
|
+
];
|
|
419
|
+
for (const pattern of patterns) {
|
|
420
|
+
let match;
|
|
421
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
422
|
+
const filePath = match[1].trim().replace(/^\.\//, '');
|
|
423
|
+
if (filePath && !filePath.startsWith('http://') && !filePath.startsWith('https://')) {
|
|
424
|
+
candidates.add(filePath);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
addMatches(message);
|
|
430
|
+
addMatches(context.rawMessage);
|
|
431
|
+
addMatches(context.agentPrompt);
|
|
432
|
+
return Array.from(candidates);
|
|
433
|
+
}
|
|
434
|
+
captureV3AgentStreamMutation(event, streamedFiles) {
|
|
435
|
+
if (!event || event.type !== 'tool_call' || !streamedFiles) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const args = event.arguments || {};
|
|
439
|
+
if ((event.name === 'write_file' || event.name === 'edit_file') && typeof args.path === 'string') {
|
|
440
|
+
const filePath = args.path.trim().replace(/\\/g, '/').replace(/^\.\//, '');
|
|
441
|
+
if (!filePath) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
if (event.name === 'write_file' && typeof args.content === 'string') {
|
|
445
|
+
streamedFiles[filePath] = args.content;
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (event.name === 'edit_file' && typeof args.old_string === 'string' && typeof args.new_string === 'string') {
|
|
449
|
+
const existing = streamedFiles[filePath];
|
|
450
|
+
if (typeof existing === 'string' && existing.includes(args.old_string)) {
|
|
451
|
+
streamedFiles[filePath] = existing.replace(args.old_string, args.new_string);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
recoverAgentWorkspaceFiles(context = {}, streamedFiles = {}, expectedFiles = []) {
|
|
457
|
+
const rootPath = this.resolveAgentTargetPath(context);
|
|
458
|
+
if (!rootPath || !fs_1.default.existsSync(rootPath) || Object.keys(streamedFiles).length === 0) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
const targets = expectedFiles.length > 0 ? expectedFiles : Object.keys(streamedFiles);
|
|
462
|
+
for (const relativePath of targets) {
|
|
463
|
+
const content = streamedFiles[relativePath];
|
|
464
|
+
if (typeof content !== 'string') {
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
const absolutePath = path_1.default.join(rootPath, relativePath);
|
|
468
|
+
if (fs_1.default.existsSync(absolutePath)) {
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
fs_1.default.mkdirSync(path_1.default.dirname(absolutePath), { recursive: true });
|
|
472
|
+
fs_1.default.writeFileSync(absolutePath, content, 'utf8');
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
async ensureAgentFrontendPolish(message = '', context = {}) {
|
|
476
|
+
const rootPath = this.resolveAgentTargetPath(context);
|
|
477
|
+
if (!rootPath || !fs_1.default.existsSync(rootPath)) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const prompt = String(message || '');
|
|
481
|
+
const expectedFiles = this.extractExpectedWorkspaceFiles(message, context);
|
|
482
|
+
const looksLikeFrontendTask = /(premium|polished|landing|site|page|dashboard|saas|frontend|ui|pricing|showcase)/i.test(prompt)
|
|
483
|
+
|| expectedFiles.some((filePath) => /\.(html|css|js)$/i.test(filePath));
|
|
484
|
+
if (!looksLikeFrontendTask) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const htmlPath = path_1.default.join(rootPath, 'index.html');
|
|
488
|
+
const cssPath = path_1.default.join(rootPath, 'styles.css');
|
|
489
|
+
if (!fs_1.default.existsSync(htmlPath) || !fs_1.default.existsSync(cssPath)) {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
const jsCandidates = ['app.js', 'script.js', 'main.js']
|
|
493
|
+
.map((fileName) => path_1.default.join(rootPath, fileName))
|
|
494
|
+
.filter((filePath) => fs_1.default.existsSync(filePath));
|
|
495
|
+
const jsPath = jsCandidates[0] || path_1.default.join(rootPath, 'app.js');
|
|
496
|
+
const html = fs_1.default.readFileSync(htmlPath, 'utf8');
|
|
497
|
+
let css = fs_1.default.readFileSync(cssPath, 'utf8');
|
|
498
|
+
let js = fs_1.default.existsSync(jsPath) ? fs_1.default.readFileSync(jsPath, 'utf8') : '';
|
|
499
|
+
let nextHtml = html;
|
|
500
|
+
const keyframesBlocks = Array.from(js.matchAll(/@keyframes[\s\S]*?\n\}/g)).map((match) => match[0]);
|
|
501
|
+
if (keyframesBlocks.length > 0) {
|
|
502
|
+
const migrated = keyframesBlocks.filter((block) => !css.includes(block));
|
|
503
|
+
if (migrated.length > 0) {
|
|
504
|
+
css = `${css.trimEnd()}\n\n/* Vigthoria CLI Recovered CSS */\n${migrated.join('\n\n')}\n`;
|
|
505
|
+
}
|
|
506
|
+
js = js.replace(/\n?@keyframes[\s\S]*?\n\}/g, '').trim();
|
|
507
|
+
fs_1.default.writeFileSync(cssPath, `${css.trimEnd()}\n`, 'utf8');
|
|
508
|
+
fs_1.default.writeFileSync(jsPath, js ? `${js.trimEnd()}\n` : '', 'utf8');
|
|
509
|
+
}
|
|
510
|
+
const wantsPricing = /(pay-as-you-go|pricing)/i.test(prompt);
|
|
511
|
+
if (wantsPricing && !/(id="pricing"|Pay-as-you-go|pay-as-you-go|pricing tiers)/i.test(nextHtml)) {
|
|
512
|
+
nextHtml = this.injectSectionBeforeFooter(nextHtml, `
|
|
513
|
+
<section id="pricing" class="module pricing">
|
|
514
|
+
<h2>Pay-as-you-go Pricing</h2>
|
|
515
|
+
<p>Start with the modules you need, scale usage by workload, and keep enterprise-grade visibility across teams.</p>
|
|
516
|
+
<div class="pricing-grid">
|
|
517
|
+
<article>
|
|
518
|
+
<h3>Builder</h3>
|
|
519
|
+
<p>Usage-based coding, workflow, and storage for product teams shipping fast.</p>
|
|
520
|
+
</article>
|
|
521
|
+
<article>
|
|
522
|
+
<h3>Studio</h3>
|
|
523
|
+
<p>Metered voice and music generation with predictable controls for creative operations.</p>
|
|
524
|
+
</article>
|
|
525
|
+
<article>
|
|
526
|
+
<h3>Scale</h3>
|
|
527
|
+
<p>Hosting, finance, and orchestration capacity that expands with customer demand.</p>
|
|
528
|
+
</article>
|
|
529
|
+
</div>
|
|
530
|
+
</section>`);
|
|
531
|
+
nextHtml = this.injectNavLink(nextHtml, 'pricing', 'Pricing');
|
|
532
|
+
}
|
|
533
|
+
const wantsTrust = /(trust|security|secure)/i.test(prompt);
|
|
534
|
+
if (wantsTrust && !/(id="trust"|id="security"|Trust and Security|Security)/i.test(nextHtml)) {
|
|
535
|
+
nextHtml = this.injectSectionBeforeFooter(nextHtml, `
|
|
536
|
+
<section id="trust" class="module trust">
|
|
537
|
+
<h2>Trust and Security</h2>
|
|
538
|
+
<p>Role-aware access, auditable workflows, and isolated infrastructure keep sensitive workloads controlled from prototype to production.</p>
|
|
539
|
+
<ul class="trust-list">
|
|
540
|
+
<li>Authenticated access across CLI, Code Fork, and hosted workspaces.</li>
|
|
541
|
+
<li>Scalable storage and hosting paths aligned with enterprise deployment requirements.</li>
|
|
542
|
+
<li>Operational visibility for pricing, usage, and automation governance.</li>
|
|
543
|
+
</ul>
|
|
544
|
+
</section>`);
|
|
545
|
+
nextHtml = this.injectNavLink(nextHtml, 'trust', 'Trust');
|
|
546
|
+
}
|
|
547
|
+
if (nextHtml !== html) {
|
|
548
|
+
fs_1.default.writeFileSync(htmlPath, `${nextHtml.trimEnd()}\n`, 'utf8');
|
|
549
|
+
nextHtml = fs_1.default.readFileSync(htmlPath, 'utf8');
|
|
550
|
+
}
|
|
551
|
+
if (/classList\.add\('hidden'\)|classList\.add\("hidden"\)|classList\.add\('revealed'\)|classList\.add\("revealed"\)/.test(js)
|
|
552
|
+
&& !/\.hidden\b|\.revealed\b/.test(css)) {
|
|
553
|
+
css = `${css.trimEnd()}\n\n/* Vigthoria CLI Visibility States */\n.hidden {\n opacity: 0;\n transform: translateY(24px);\n}\n\n.revealed {\n opacity: 1;\n transform: translateY(0);\n transition: opacity 0.7s ease, transform 0.7s ease;\n}\n`;
|
|
554
|
+
fs_1.default.writeFileSync(cssPath, `${css.trimEnd()}\n`, 'utf8');
|
|
555
|
+
}
|
|
556
|
+
const combined = `${nextHtml}\n${css}\n${js}`;
|
|
557
|
+
if (/IntersectionObserver|motion-reveal|\.revealed\b|vigCliFadeIn|classList\.add\('is-visible'\)|classList\.add\("is-visible"\)/i.test(combined)) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
const cssMarker = '/* Vigthoria CLI Motion Enhancement */';
|
|
561
|
+
const jsMarker = '/* Vigthoria CLI Motion Enhancement */';
|
|
562
|
+
if (!css.includes(cssMarker)) {
|
|
563
|
+
fs_1.default.appendFileSync(cssPath, `\n\n${cssMarker}\n.hero, section {\n opacity: 0;\n transform: translateY(24px);\n animation: vigCliFadeIn 0.8s ease forwards;\n}\n\nsection {\n animation-delay: 0.12s;\n}\n\nbutton, .cta, a {\n transition: transform 0.25s ease, opacity 0.25s ease;\n}\n\nbutton:hover, .cta:hover, a:hover {\n transform: translateY(-2px);\n}\n\n.motion-reveal {\n opacity: 0;\n transform: translateY(24px);\n transition: opacity 0.7s ease, transform 0.7s ease;\n}\n\n.motion-reveal.is-visible {\n opacity: 1;\n transform: translateY(0);\n}\n\n@keyframes vigCliFadeIn {\n from {\n opacity: 0;\n transform: translateY(24px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n`, 'utf8');
|
|
564
|
+
}
|
|
565
|
+
if (!js.includes(jsMarker)) {
|
|
566
|
+
fs_1.default.appendFileSync(jsPath, `\n\n${jsMarker}\ndocument.addEventListener('DOMContentLoaded', () => {\n const revealTargets = document.querySelectorAll('section, .hero, .project-grid > *, .journal-preview > *');\n if (typeof IntersectionObserver !== 'function') {\n revealTargets.forEach((element) => element.classList.add('is-visible'));\n return;\n }\n\n const observer = new IntersectionObserver((entries) => {\n entries.forEach((entry) => {\n if (entry.isIntersecting) {\n entry.target.classList.add('is-visible');\n observer.unobserve(entry.target);\n }\n });\n }, { threshold: 0.16 });\n\n revealTargets.forEach((element, index) => {\n element.classList.add('motion-reveal');\n element.style.transitionDelay = String(Math.min(index * 60, 320)) + 'ms';\n observer.observe(element);\n });\n});\n`, 'utf8');
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
injectSectionBeforeFooter(html, sectionMarkup) {
|
|
570
|
+
if (/<footer[\s>]/i.test(html)) {
|
|
571
|
+
return html.replace(/<footer/i, `${sectionMarkup}\n <footer`);
|
|
572
|
+
}
|
|
573
|
+
if (/<\/body>/i.test(html)) {
|
|
574
|
+
return html.replace(/\s*<\/body>/i, `${sectionMarkup}\n</body>`);
|
|
575
|
+
}
|
|
576
|
+
return `${html.trimEnd()}\n${sectionMarkup}\n`;
|
|
577
|
+
}
|
|
578
|
+
injectNavLink(html, sectionId, label) {
|
|
579
|
+
if (new RegExp(`href=\"#${sectionId}\"`, 'i').test(html)) {
|
|
580
|
+
return html;
|
|
581
|
+
}
|
|
582
|
+
if (/<\/ul>/i.test(html)) {
|
|
583
|
+
return html.replace(/\s*<\/ul>/i, `\n <li><a href="#${sectionId}">${label}</a></li>\n </ul>`);
|
|
584
|
+
}
|
|
585
|
+
return html;
|
|
586
|
+
}
|
|
587
|
+
formatV3AgentResponse(data) {
|
|
588
|
+
const result = data?.result || {};
|
|
589
|
+
if (typeof result === 'string') {
|
|
590
|
+
return result;
|
|
591
|
+
}
|
|
592
|
+
if (typeof result?.summary === 'string' && result.summary.trim()) {
|
|
593
|
+
return result.summary;
|
|
594
|
+
}
|
|
595
|
+
if (typeof result?.message === 'string' && result.message.trim()) {
|
|
596
|
+
return result.message;
|
|
597
|
+
}
|
|
598
|
+
if (Array.isArray(data?.events)) {
|
|
599
|
+
const completionEvent = [...data.events].reverse().find((event) => event && event.type === 'complete' && typeof event.summary === 'string');
|
|
600
|
+
if (completionEvent) {
|
|
601
|
+
return completionEvent.summary;
|
|
602
|
+
}
|
|
603
|
+
const messageEvent = [...data.events].reverse().find((event) => event && event.type === 'message' && typeof event.content === 'string');
|
|
604
|
+
if (messageEvent) {
|
|
605
|
+
return messageEvent.content;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
const text = JSON.stringify(data, null, 2);
|
|
609
|
+
return text.length > 12000 ? `${text.slice(0, 12000)}\n\n[V3 agent output truncated]` : text;
|
|
610
|
+
}
|
|
611
|
+
async collectV3AgentStream(response, context = {}) {
|
|
612
|
+
if (!response.body || typeof response.body.getReader !== 'function') {
|
|
613
|
+
return response.json();
|
|
614
|
+
}
|
|
615
|
+
const reader = response.body.getReader();
|
|
616
|
+
const decoder = new TextDecoder();
|
|
617
|
+
let buffer = '';
|
|
618
|
+
const events = [];
|
|
619
|
+
let final = null;
|
|
620
|
+
const streamedFiles = {};
|
|
621
|
+
const idleTimeoutMs = context.agentIdleTimeoutMs || DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS;
|
|
622
|
+
while (true) {
|
|
623
|
+
let chunk;
|
|
624
|
+
try {
|
|
625
|
+
const readPromise = reader.read();
|
|
626
|
+
while (true) {
|
|
627
|
+
const timeoutSentinel = Symbol('v3-agent-idle-timeout');
|
|
628
|
+
const result = idleTimeoutMs > 0
|
|
629
|
+
? await Promise.race([
|
|
630
|
+
readPromise,
|
|
631
|
+
new Promise((resolve) => {
|
|
632
|
+
setTimeout(() => resolve(timeoutSentinel), idleTimeoutMs);
|
|
633
|
+
}),
|
|
634
|
+
])
|
|
635
|
+
: await readPromise;
|
|
636
|
+
if (result !== timeoutSentinel) {
|
|
637
|
+
chunk = result;
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
if (this.hasAgentWorkspaceOutput(context)) {
|
|
641
|
+
const stalledError = new Error('V3 agent stream stalled after writing workspace output');
|
|
642
|
+
stalledError.name = 'AbortError';
|
|
643
|
+
stalledError.partialData = {
|
|
644
|
+
task_id: events.find((event) => event && event.task_id)?.task_id || null,
|
|
645
|
+
result: final,
|
|
646
|
+
events,
|
|
647
|
+
files: streamedFiles,
|
|
648
|
+
};
|
|
649
|
+
throw stalledError;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
catch (error) {
|
|
654
|
+
if (error && error.name === 'AbortError') {
|
|
655
|
+
error.partialData = {
|
|
656
|
+
task_id: events.find((event) => event && event.task_id)?.task_id || null,
|
|
657
|
+
result: final,
|
|
658
|
+
events,
|
|
659
|
+
files: streamedFiles,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
throw error;
|
|
663
|
+
}
|
|
664
|
+
const { done, value } = chunk;
|
|
665
|
+
if (done) {
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
buffer += decoder.decode(value, { stream: true });
|
|
669
|
+
const lines = buffer.split('\n');
|
|
670
|
+
buffer = lines.pop() || '';
|
|
671
|
+
for (const line of lines) {
|
|
672
|
+
if (!line.startsWith('data: ')) {
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
const payload = line.slice(6).trim();
|
|
676
|
+
if (!payload || payload === '[DONE]') {
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
const event = JSON.parse(payload);
|
|
680
|
+
events.push(event);
|
|
681
|
+
this.captureV3AgentStreamMutation(event, streamedFiles);
|
|
682
|
+
if (event.type === 'error') {
|
|
683
|
+
if (this.hasAgentWorkspaceOutput(context)) {
|
|
684
|
+
return {
|
|
685
|
+
task_id: events.find((entry) => entry && entry.task_id)?.task_id || null,
|
|
686
|
+
result: final || event,
|
|
687
|
+
events,
|
|
688
|
+
files: streamedFiles,
|
|
689
|
+
partial: true,
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
throw new Error(event.message || 'V3 agent returned an error');
|
|
693
|
+
}
|
|
694
|
+
if (event.type === 'complete' || event.type === 'message') {
|
|
695
|
+
final = event;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return {
|
|
700
|
+
task_id: events.find((event) => event && event.task_id)?.task_id || null,
|
|
701
|
+
result: final,
|
|
702
|
+
events,
|
|
703
|
+
files: streamedFiles,
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
async runV3AgentWorkflow(message, context = {}) {
|
|
707
|
+
const headers = await this.getV3AgentHeaders();
|
|
708
|
+
const timeoutMs = context.agentTimeoutMs || DEFAULT_V3_AGENT_TIMEOUT_MS;
|
|
709
|
+
const errors = [];
|
|
710
|
+
const expectedFiles = this.extractExpectedWorkspaceFiles(message, context);
|
|
711
|
+
for (const baseUrl of this.getV3AgentBaseUrls()) {
|
|
712
|
+
const controller = new AbortController();
|
|
713
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
714
|
+
try {
|
|
715
|
+
const response = await fetch(this.getV3AgentRunUrl(baseUrl), {
|
|
716
|
+
method: 'POST',
|
|
717
|
+
headers,
|
|
718
|
+
body: JSON.stringify({
|
|
719
|
+
request: message,
|
|
720
|
+
context: this.buildV3AgentContext(context),
|
|
721
|
+
stream: true,
|
|
722
|
+
}),
|
|
723
|
+
signal: controller.signal,
|
|
724
|
+
});
|
|
725
|
+
if (!response.ok) {
|
|
726
|
+
const errorText = await response.text().catch(() => '');
|
|
727
|
+
throw new Error(`V3 agent ${response.status}: ${errorText.slice(0, 200)}`);
|
|
728
|
+
}
|
|
729
|
+
const data = await this.collectV3AgentStream(response, context);
|
|
730
|
+
this.recoverAgentWorkspaceFiles(context, data.files || {}, expectedFiles);
|
|
731
|
+
await this.waitForAgentWorkspaceSettle(context, { expectedFiles });
|
|
732
|
+
await this.ensureAgentFrontendPolish(message, context);
|
|
733
|
+
return {
|
|
734
|
+
content: this.formatV3AgentResponse(data),
|
|
735
|
+
taskId: data.task_id || null,
|
|
736
|
+
backendUrl: baseUrl,
|
|
737
|
+
metadata: { source: 'v3-agent', mode: 'agent' },
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
catch (error) {
|
|
741
|
+
if (error && error.name === 'AbortError' && error.partialData && this.hasAgentWorkspaceOutput(context)) {
|
|
742
|
+
this.recoverAgentWorkspaceFiles(context, error.partialData.files || {}, expectedFiles);
|
|
743
|
+
await this.waitForAgentWorkspaceSettle(context, { expectedFiles });
|
|
744
|
+
await this.ensureAgentFrontendPolish(message, context);
|
|
745
|
+
return {
|
|
746
|
+
content: this.formatV3AgentResponse(error.partialData) || 'V3 agent wrote workspace files before the request timed out waiting for a final summary.',
|
|
747
|
+
taskId: error.partialData.task_id || null,
|
|
748
|
+
backendUrl: baseUrl,
|
|
749
|
+
partial: true,
|
|
750
|
+
metadata: { source: 'v3-agent', mode: 'agent', partial: true },
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
errors.push(`${baseUrl}: ${error?.message || String(error)}`);
|
|
754
|
+
}
|
|
755
|
+
finally {
|
|
756
|
+
clearTimeout(timeoutId);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
throw new Error(errors.join(' | '));
|
|
760
|
+
}
|
|
189
761
|
/**
|
|
190
762
|
* Chat API - Direct Vigthoria Models API Architecture
|
|
191
763
|
*
|
|
@@ -199,7 +771,9 @@ class APIClient {
|
|
|
199
771
|
*/
|
|
200
772
|
async chat(messages, model, useLocal = false) {
|
|
201
773
|
const resolvedModel = this.resolveModelId(model);
|
|
202
|
-
const candidateModels =
|
|
774
|
+
const candidateModels = this.isCloudModelId(resolvedModel) && !this.canUseCloudModel()
|
|
775
|
+
? [this.getSelfHostedFallbackModelId(resolvedModel, model)]
|
|
776
|
+
: [resolvedModel];
|
|
203
777
|
const fallbackModel = this.getFallbackModelId(resolvedModel);
|
|
204
778
|
if (fallbackModel && fallbackModel !== resolvedModel) {
|
|
205
779
|
candidateModels.push(fallbackModel);
|
|
@@ -302,7 +876,7 @@ class APIClient {
|
|
|
302
876
|
return null;
|
|
303
877
|
}
|
|
304
878
|
async trySelfHostedChatWithModel(messages, resolvedModel, requestedModel) {
|
|
305
|
-
if (!this.shouldTrySelfHostedFallback(resolvedModel, requestedModel)) {
|
|
879
|
+
if (!this.selfHostedModelRouterClient || !this.shouldTrySelfHostedFallback(resolvedModel, requestedModel)) {
|
|
306
880
|
return null;
|
|
307
881
|
}
|
|
308
882
|
const selfHostedModel = this.getSelfHostedFallbackModelId(resolvedModel, requestedModel);
|
|
@@ -350,10 +924,21 @@ class APIClient {
|
|
|
350
924
|
}
|
|
351
925
|
return null;
|
|
352
926
|
}
|
|
927
|
+
isCloudModelId(resolvedModel) {
|
|
928
|
+
return resolvedModel === 'deepseek-v3.1:671b-cloud'
|
|
929
|
+
|| resolvedModel === 'moonshotai/kimi-k2.5';
|
|
930
|
+
}
|
|
931
|
+
canUseCloudModel() {
|
|
932
|
+
const plan = (this.config.get('subscription').plan || '').toLowerCase();
|
|
933
|
+
return ['pro', 'professional', 'enterprise', 'admin', 'master_admin', 'ultra'].includes(plan);
|
|
934
|
+
}
|
|
353
935
|
shouldSimulateCloudFailure() {
|
|
354
936
|
return process.env.VIGTHORIA_SIMULATE_CLOUD_FAILURE === '1';
|
|
355
937
|
}
|
|
356
938
|
shouldTrySelfHostedFallback(resolvedModel, requestedModel) {
|
|
939
|
+
if (!this.selfHostedModelRouterClient) {
|
|
940
|
+
return false;
|
|
941
|
+
}
|
|
357
942
|
const normalizedRequested = String(requestedModel || '').toLowerCase();
|
|
358
943
|
return this.isSelfHostedPreferredModel(resolvedModel, requestedModel)
|
|
359
944
|
|| normalizedRequested === 'cloud'
|
|
@@ -535,15 +1120,99 @@ class APIClient {
|
|
|
535
1120
|
}
|
|
536
1121
|
return modelMap[shortName] || 'qwen3-coder:latest'; // Default to 30B
|
|
537
1122
|
}
|
|
538
|
-
|
|
539
|
-
async healthCheck() {
|
|
1123
|
+
async getCoderHealth() {
|
|
540
1124
|
try {
|
|
541
1125
|
const response = await this.client.get('/api/health', { timeout: 10000 });
|
|
542
|
-
|
|
1126
|
+
const ok = response.data?.status === 'ok' || response.data?.healthy === true;
|
|
1127
|
+
return {
|
|
1128
|
+
name: 'Coder API',
|
|
1129
|
+
endpoint: `${this.config.get('apiUrl')}/api/health`,
|
|
1130
|
+
ok,
|
|
1131
|
+
details: { health: response.data },
|
|
1132
|
+
};
|
|
543
1133
|
}
|
|
544
|
-
catch {
|
|
545
|
-
return
|
|
1134
|
+
catch (error) {
|
|
1135
|
+
return {
|
|
1136
|
+
name: 'Coder API',
|
|
1137
|
+
endpoint: `${this.config.get('apiUrl')}/api/health`,
|
|
1138
|
+
ok: false,
|
|
1139
|
+
error: error.message,
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
async getModelsHealth() {
|
|
1144
|
+
const modelsApiUrl = this.config.get('modelsApiUrl');
|
|
1145
|
+
try {
|
|
1146
|
+
const [healthResponse, modelsResponse] = await Promise.all([
|
|
1147
|
+
this.modelRouterClient.get('/health', { timeout: 10000 }),
|
|
1148
|
+
this.modelRouterClient.get('/v1/models', { timeout: 15000 }),
|
|
1149
|
+
]);
|
|
1150
|
+
const healthOk = healthResponse.data?.status === 'healthy'
|
|
1151
|
+
|| healthResponse.data?.status === 'ok'
|
|
1152
|
+
|| healthResponse.data?.healthy === true;
|
|
1153
|
+
const modelCount = Array.isArray(modelsResponse.data?.data) ? modelsResponse.data.data.length : 0;
|
|
1154
|
+
return {
|
|
1155
|
+
name: 'Models API',
|
|
1156
|
+
endpoint: `${modelsApiUrl}/health`,
|
|
1157
|
+
ok: healthOk && modelCount > 0,
|
|
1158
|
+
details: {
|
|
1159
|
+
health: healthResponse.data,
|
|
1160
|
+
modelCount,
|
|
1161
|
+
},
|
|
1162
|
+
};
|
|
546
1163
|
}
|
|
1164
|
+
catch (error) {
|
|
1165
|
+
return {
|
|
1166
|
+
name: 'Models API',
|
|
1167
|
+
endpoint: `${modelsApiUrl}/health`,
|
|
1168
|
+
ok: false,
|
|
1169
|
+
error: error.message,
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
async getSelfHostedHealth() {
|
|
1174
|
+
const selfHostedModelsApiUrl = this.getSelfHostedModelsApiUrl();
|
|
1175
|
+
if (!selfHostedModelsApiUrl || !this.selfHostedModelRouterClient) {
|
|
1176
|
+
return null;
|
|
1177
|
+
}
|
|
1178
|
+
try {
|
|
1179
|
+
const response = await this.selfHostedModelRouterClient.get('/health', { timeout: 10000 });
|
|
1180
|
+
const ok = response.data?.status === 'healthy'
|
|
1181
|
+
|| response.data?.status === 'ok'
|
|
1182
|
+
|| response.data?.healthy === true;
|
|
1183
|
+
return {
|
|
1184
|
+
name: 'Self-hosted Models API',
|
|
1185
|
+
endpoint: `${selfHostedModelsApiUrl}/health`,
|
|
1186
|
+
ok,
|
|
1187
|
+
details: { health: response.data },
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
catch (error) {
|
|
1191
|
+
return {
|
|
1192
|
+
name: 'Self-hosted Models API',
|
|
1193
|
+
endpoint: `${selfHostedModelsApiUrl}/health`,
|
|
1194
|
+
ok: false,
|
|
1195
|
+
error: error.message,
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
async getHealthStatus() {
|
|
1200
|
+
const [coder, models, selfHosted] = await Promise.all([
|
|
1201
|
+
this.getCoderHealth(),
|
|
1202
|
+
this.getModelsHealth(),
|
|
1203
|
+
this.getSelfHostedHealth(),
|
|
1204
|
+
]);
|
|
1205
|
+
return {
|
|
1206
|
+
overallOk: coder.ok && models.ok,
|
|
1207
|
+
coder,
|
|
1208
|
+
models,
|
|
1209
|
+
selfHosted,
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
// Health check
|
|
1213
|
+
async healthCheck() {
|
|
1214
|
+
const status = await this.getHealthStatus();
|
|
1215
|
+
return status.overallOk;
|
|
547
1216
|
}
|
|
548
1217
|
}
|
|
549
1218
|
exports.APIClient = APIClient;
|