agentgui 1.0.203 → 1.0.206
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/package.json +2 -2
- package/server.js +91 -97
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentgui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.206",
|
|
4
4
|
"description": "Multi-agent ACP client with real-time communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.js",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"better-sqlite3": "^12.6.2",
|
|
28
28
|
"busboy": "^1.6.0",
|
|
29
29
|
"express": "^5.2.1",
|
|
30
|
-
"fsbrowse": "^0.2.
|
|
30
|
+
"fsbrowse": "^0.2.18",
|
|
31
31
|
"google-auth-library": "^10.5.0",
|
|
32
32
|
"onnxruntime-node": "^1.24.1",
|
|
33
33
|
"webtalk": "github:AnEntrypoint/webtalk",
|
package/server.js
CHANGED
|
@@ -3,7 +3,6 @@ import fs from 'fs';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import zlib from 'zlib';
|
|
6
|
-
import net from 'net';
|
|
7
6
|
import crypto from 'crypto';
|
|
8
7
|
import { fileURLToPath } from 'url';
|
|
9
8
|
import { WebSocketServer } from 'ws';
|
|
@@ -245,17 +244,19 @@ const GEMINI_OAUTH_FILE = path.join(GEMINI_DIR, 'oauth_creds.json');
|
|
|
245
244
|
const GEMINI_ACCOUNTS_FILE = path.join(GEMINI_DIR, 'google_accounts.json');
|
|
246
245
|
|
|
247
246
|
let geminiOAuthState = { status: 'idle', error: null, email: null };
|
|
248
|
-
let
|
|
249
|
-
|
|
250
|
-
function
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
247
|
+
let geminiOAuthPending = null;
|
|
248
|
+
|
|
249
|
+
function buildBaseUrl(req) {
|
|
250
|
+
const override = process.env.AGENTGUI_BASE_URL;
|
|
251
|
+
if (override) return override.replace(/\/+$/, '');
|
|
252
|
+
const fwdProto = req.headers['x-forwarded-proto'];
|
|
253
|
+
const fwdHost = req.headers['x-forwarded-host'] || req.headers['host'];
|
|
254
|
+
if (fwdHost) {
|
|
255
|
+
const proto = fwdProto || (req.socket.encrypted ? 'https' : 'http');
|
|
256
|
+
const cleanHost = fwdHost.replace(/:443$/, '').replace(/:80$/, '');
|
|
257
|
+
return `${proto}://${cleanHost}`;
|
|
258
|
+
}
|
|
259
|
+
return `http://127.0.0.1:${PORT}`;
|
|
259
260
|
}
|
|
260
261
|
|
|
261
262
|
function saveGeminiCredentials(tokens, email) {
|
|
@@ -292,17 +293,11 @@ function geminiOAuthResultPage(title, message, success) {
|
|
|
292
293
|
</div></body></html>`;
|
|
293
294
|
}
|
|
294
295
|
|
|
295
|
-
async function startGeminiOAuth() {
|
|
296
|
-
if (geminiOAuthCallbackServer) {
|
|
297
|
-
try { geminiOAuthCallbackServer.close(); } catch (_) {}
|
|
298
|
-
geminiOAuthCallbackServer = null;
|
|
299
|
-
}
|
|
300
|
-
|
|
296
|
+
async function startGeminiOAuth(baseUrl) {
|
|
301
297
|
const creds = getGeminiOAuthCreds();
|
|
302
298
|
if (!creds) throw new Error('Could not find Gemini CLI OAuth credentials. Install gemini CLI first.');
|
|
303
299
|
|
|
304
|
-
const
|
|
305
|
-
const redirectUri = `http://127.0.0.1:${port}/oauth2callback`;
|
|
300
|
+
const redirectUri = `${baseUrl}${BASE_URL}/oauth2callback`;
|
|
306
301
|
const state = crypto.randomBytes(32).toString('hex');
|
|
307
302
|
|
|
308
303
|
const client = new OAuth2Client({
|
|
@@ -317,93 +312,87 @@ async function startGeminiOAuth() {
|
|
|
317
312
|
state,
|
|
318
313
|
});
|
|
319
314
|
|
|
315
|
+
geminiOAuthPending = { client, redirectUri, state };
|
|
320
316
|
geminiOAuthState = { status: 'pending', error: null, email: null };
|
|
321
317
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
res.end('Not found');
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
const error = reqUrl.searchParams.get('error');
|
|
333
|
-
if (error) {
|
|
334
|
-
const desc = reqUrl.searchParams.get('error_description') || error;
|
|
335
|
-
geminiOAuthState = { status: 'error', error: desc, email: null };
|
|
336
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
337
|
-
res.end(geminiOAuthResultPage('Authentication Failed', desc, false));
|
|
338
|
-
cbServer.close();
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
318
|
+
setTimeout(() => {
|
|
319
|
+
if (geminiOAuthState.status === 'pending') {
|
|
320
|
+
geminiOAuthState = { status: 'error', error: 'Authentication timed out', email: null };
|
|
321
|
+
geminiOAuthPending = null;
|
|
322
|
+
}
|
|
323
|
+
}, 5 * 60 * 1000);
|
|
341
324
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
345
|
-
res.end(geminiOAuthResultPage('Authentication Failed', 'State mismatch.', false));
|
|
346
|
-
cbServer.close();
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
325
|
+
return authUrl;
|
|
326
|
+
}
|
|
349
327
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
geminiOAuthState = { status: 'error', error: 'No authorization code', email: null };
|
|
353
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
354
|
-
res.end(geminiOAuthResultPage('Authentication Failed', 'No authorization code received.', false));
|
|
355
|
-
cbServer.close();
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
328
|
+
async function handleGeminiOAuthCallback(req, res) {
|
|
329
|
+
const reqUrl = new URL(req.url, buildBaseUrl(req));
|
|
358
330
|
|
|
359
|
-
|
|
360
|
-
|
|
331
|
+
if (!geminiOAuthPending) {
|
|
332
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
333
|
+
res.end(geminiOAuthResultPage('Authentication Failed', 'No pending OAuth flow.', false));
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
361
336
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
email = info.email || '';
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
} catch (_) {}
|
|
337
|
+
const error = reqUrl.searchParams.get('error');
|
|
338
|
+
if (error) {
|
|
339
|
+
const desc = reqUrl.searchParams.get('error_description') || error;
|
|
340
|
+
geminiOAuthState = { status: 'error', error: desc, email: null };
|
|
341
|
+
geminiOAuthPending = null;
|
|
342
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
343
|
+
res.end(geminiOAuthResultPage('Authentication Failed', desc, false));
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
375
346
|
|
|
376
|
-
|
|
377
|
-
geminiOAuthState = { status: 'success', error: null, email };
|
|
347
|
+
const { client, redirectUri, state: expectedState } = geminiOAuthPending;
|
|
378
348
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
cbServer.close();
|
|
387
|
-
}
|
|
388
|
-
});
|
|
349
|
+
if (reqUrl.searchParams.get('state') !== expectedState) {
|
|
350
|
+
geminiOAuthState = { status: 'error', error: 'State mismatch', email: null };
|
|
351
|
+
geminiOAuthPending = null;
|
|
352
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
353
|
+
res.end(geminiOAuthResultPage('Authentication Failed', 'State mismatch.', false));
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
389
356
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
357
|
+
const code = reqUrl.searchParams.get('code');
|
|
358
|
+
if (!code) {
|
|
359
|
+
geminiOAuthState = { status: 'error', error: 'No authorization code', email: null };
|
|
360
|
+
geminiOAuthPending = null;
|
|
361
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
362
|
+
res.end(geminiOAuthResultPage('Authentication Failed', 'No authorization code received.', false));
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
394
365
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
});
|
|
366
|
+
try {
|
|
367
|
+
const { tokens } = await client.getToken({ code, redirect_uri: redirectUri });
|
|
368
|
+
client.setCredentials(tokens);
|
|
399
369
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
370
|
+
let email = '';
|
|
371
|
+
try {
|
|
372
|
+
const { token } = await client.getAccessToken();
|
|
373
|
+
if (token) {
|
|
374
|
+
const resp = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
|
|
375
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
376
|
+
});
|
|
377
|
+
if (resp.ok) {
|
|
378
|
+
const info = await resp.json();
|
|
379
|
+
email = info.email || '';
|
|
380
|
+
}
|
|
404
381
|
}
|
|
405
|
-
}
|
|
406
|
-
|
|
382
|
+
} catch (_) {}
|
|
383
|
+
|
|
384
|
+
saveGeminiCredentials(tokens, email);
|
|
385
|
+
geminiOAuthState = { status: 'success', error: null, email };
|
|
386
|
+
geminiOAuthPending = null;
|
|
387
|
+
|
|
388
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
389
|
+
res.end(geminiOAuthResultPage('Authentication Successful', email ? `Signed in as ${email}` : 'Gemini CLI credentials saved.', true));
|
|
390
|
+
} catch (e) {
|
|
391
|
+
geminiOAuthState = { status: 'error', error: e.message, email: null };
|
|
392
|
+
geminiOAuthPending = null;
|
|
393
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
394
|
+
res.end(geminiOAuthResultPage('Authentication Failed', e.message, false));
|
|
395
|
+
}
|
|
407
396
|
}
|
|
408
397
|
|
|
409
398
|
function getGeminiOAuthStatus() {
|
|
@@ -620,6 +609,11 @@ const server = http.createServer(async (req, res) => {
|
|
|
620
609
|
// Remove query parameters from routePath for matching
|
|
621
610
|
const pathOnly = routePath.split('?')[0];
|
|
622
611
|
|
|
612
|
+
if (pathOnly === '/oauth2callback' && req.method === 'GET') {
|
|
613
|
+
await handleGeminiOAuthCallback(req, res);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
|
|
623
617
|
if (pathOnly === '/api/conversations' && req.method === 'GET') {
|
|
624
618
|
sendJSON(req, res, 200, { conversations: queries.getConversationsList() });
|
|
625
619
|
return;
|
|
@@ -1093,7 +1087,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1093
1087
|
|
|
1094
1088
|
if (pathOnly === '/api/gemini-oauth/start' && req.method === 'POST') {
|
|
1095
1089
|
try {
|
|
1096
|
-
const authUrl = await startGeminiOAuth();
|
|
1090
|
+
const authUrl = await startGeminiOAuth(buildBaseUrl(req));
|
|
1097
1091
|
sendJSON(req, res, 200, { authUrl });
|
|
1098
1092
|
} catch (e) {
|
|
1099
1093
|
console.error('[gemini-oauth] /api/gemini-oauth/start failed:', e);
|
|
@@ -1115,7 +1109,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1115
1109
|
|
|
1116
1110
|
if (agentId === 'gemini') {
|
|
1117
1111
|
try {
|
|
1118
|
-
const authUrl = await startGeminiOAuth();
|
|
1112
|
+
const authUrl = await startGeminiOAuth(buildBaseUrl(req));
|
|
1119
1113
|
const conversationId = '__agent_auth__';
|
|
1120
1114
|
broadcastSync({ type: 'script_started', conversationId, script: 'auth-gemini', agentId: 'gemini', timestamp: Date.now() });
|
|
1121
1115
|
broadcastSync({ type: 'script_output', conversationId, data: `\x1b[36mOpening Google OAuth in your browser...\x1b[0m\r\n\r\nIf it doesn't open automatically, visit:\r\n${authUrl}\r\n`, stream: 'stdout', timestamp: Date.now() });
|