Skip to content

Commit 5e20787

Browse files
lanmowerclaude
andcommitted
refactor: extract provider-config and server-utils from server.js
Move maskKey, getProviderConfigs, saveProviderConfig, buildSystemPrompt, PROVIDER_CONFIGS to lib/provider-config.js (151L). Move logError, makeCleanupExecution, makeGetModelsForAgent, errLogPath to lib/server-utils.js (61L). cleanupExecution wired after broadcastSync; _debugRoutes receives errLogPath via named export. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 90f38ba commit 5e20787

4 files changed

Lines changed: 217 additions & 206 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## [Unreleased]
22

33
### Refactor
4+
- Extract maskKey, getProviderConfigs, saveProviderConfig, buildSystemPrompt, PROVIDER_CONFIGS from server.js to lib/provider-config.js (151L); extract logError, makeCleanupExecution, makeGetModelsForAgent, errLogPath from server.js to lib/server-utils.js (61L); server.js imports all via named imports; cleanupExecution wired after broadcastSync; _debugRoutes receives errLogPath
45
- Extract parseBody, acceptsEncoding, compressAndSend, sendJSON from server.js to lib/http-utils.js (43L); server.js imports from new module; zlib import removed from server.js
56
- Extract message/stream/queue routes (messagesMatch, streamMatch, queueMatch handlers) to lib/routes-messages.js (140L) and session/chunk/full/execution routes to lib/routes-sessions.js (145L); server.js reduced from 2406L to 2127L; both files ≤200L; wired via _messagesRoutes._match and _sessionsRoutes._match in request handler
67
- Extract runs/scripts/agent-auth/auth-config HTTP routes from server.js to lib/routes-runs.js (157L), lib/routes-scripts.js (136L), lib/routes-agent-actions.js (118L), lib/routes-auth-config.js (30L); routes-auth-config uses getProviderConfigs/saveProviderConfig from server.js deps (no duplication); server.js reduced from 2406L to 1399L total (-1007L)

lib/provider-config.js

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
import os from 'os';
4+
import { getGeminiOAuthStatus } from './oauth-gemini.js';
5+
import { getCodexOAuthStatus } from './oauth-codex.js';
6+
7+
export function buildSystemPrompt(agentId, model, subAgent) {
8+
const parts = [];
9+
if (agentId && agentId !== 'claude-code') {
10+
const displayAgentId = agentId.split('-·-')[0];
11+
parts.push(`Use ${displayAgentId} subagent for all tasks.`);
12+
}
13+
if (model) parts.push(`Model: ${model}.`);
14+
if (subAgent) parts.push(`Subagent: ${subAgent}.`);
15+
return parts.join(' ');
16+
}
17+
18+
const PROVIDER_CONFIGS = {
19+
'anthropic': {
20+
name: 'Anthropic', configPaths: [
21+
path.join(os.homedir(), '.claude.json'),
22+
path.join(os.homedir(), '.config', 'claude', 'settings.json'),
23+
path.join(os.homedir(), '.anthropic.json')
24+
],
25+
configFormat: (apiKey, model) => ({ api_key: apiKey, default_model: model })
26+
},
27+
'openai': {
28+
name: 'OpenAI', configPaths: [
29+
path.join(os.homedir(), '.openai.json'),
30+
path.join(os.homedir(), '.config', 'openai', 'api-key')
31+
],
32+
configFormat: (apiKey, model) => ({ apiKey, defaultModel: model })
33+
},
34+
'google': {
35+
name: 'Google Gemini', configPaths: [
36+
path.join(os.homedir(), '.gemini.json'),
37+
path.join(os.homedir(), '.config', 'gemini', 'credentials.json')
38+
],
39+
configFormat: (apiKey, model) => ({ api_key: apiKey, default_model: model })
40+
},
41+
'openrouter': {
42+
name: 'OpenRouter', configPaths: [
43+
path.join(os.homedir(), '.openrouter.json'),
44+
path.join(os.homedir(), '.config', 'openrouter', 'config.json')
45+
],
46+
configFormat: (apiKey, model) => ({ api_key: apiKey, default_model: model })
47+
},
48+
'github': {
49+
name: 'GitHub Models', configPaths: [
50+
path.join(os.homedir(), '.github.json'),
51+
path.join(os.homedir(), '.config', 'github-copilot.json')
52+
],
53+
configFormat: (apiKey, model) => ({ github_token: apiKey, default_model: model })
54+
},
55+
'azure': {
56+
name: 'Azure OpenAI', configPaths: [
57+
path.join(os.homedir(), '.azure.json'),
58+
path.join(os.homedir(), '.config', 'azure-openai', 'config.json')
59+
],
60+
configFormat: (apiKey, model) => ({ api_key: apiKey, endpoint: '', default_model: model })
61+
},
62+
'anthropic-claude-code': {
63+
name: 'Claude Code Max', configPaths: [
64+
path.join(os.homedir(), '.claude', 'max.json'),
65+
path.join(os.homedir(), '.config', 'claude-code', 'max.json')
66+
],
67+
configFormat: (apiKey, model) => ({ api_key: apiKey, plan: 'max', default_model: model })
68+
},
69+
'opencode': {
70+
name: 'OpenCode', configPaths: [
71+
path.join(os.homedir(), '.opencode', 'config.json'),
72+
path.join(os.homedir(), '.config', 'opencode', 'config.json')
73+
],
74+
configFormat: (apiKey, model) => ({ api_key: apiKey, default_model: model, providers: ['anthropic', 'openai', 'google'] })
75+
},
76+
'proxypilot': {
77+
name: 'ProxyPilot', configPaths: [
78+
path.join(os.homedir(), '.proxypilot', 'config.json'),
79+
path.join(os.homedir(), '.config', 'proxypilot', 'config.json')
80+
],
81+
configFormat: (apiKey, model) => ({ api_key: apiKey, default_model: model })
82+
},
83+
'codex': {
84+
name: 'Codex CLI', configPaths: [
85+
path.join(os.homedir(), '.codex', 'auth.json')
86+
],
87+
configFormat: (apiKey) => ({ auth_mode: 'apikey', OPENAI_API_KEY: apiKey })
88+
}
89+
};
90+
91+
export function maskKey(key) {
92+
if (!key || key.length < 8) return '****';
93+
return '****' + key.slice(-4);
94+
}
95+
96+
export function getProviderConfigs() {
97+
const configs = {};
98+
for (const [providerId, config] of Object.entries(PROVIDER_CONFIGS)) {
99+
if (providerId === 'google') {
100+
const oauthStatus = getGeminiOAuthStatus();
101+
if (oauthStatus) {
102+
configs[providerId] = { name: config.name, ...oauthStatus };
103+
continue;
104+
}
105+
}
106+
if (providerId === 'codex') {
107+
const oauthStatus = getCodexOAuthStatus();
108+
if (oauthStatus) {
109+
configs[providerId] = { name: config.name, ...oauthStatus };
110+
continue;
111+
}
112+
}
113+
for (const configPath of config.configPaths) {
114+
try {
115+
if (fs.existsSync(configPath)) {
116+
const content = fs.readFileSync(configPath, 'utf8');
117+
const parsed = JSON.parse(content);
118+
const rawKey = parsed.api_key || parsed.apiKey || parsed.github_token || parsed.OPENAI_API_KEY || '';
119+
configs[providerId] = {
120+
name: config.name,
121+
apiKey: maskKey(rawKey),
122+
hasKey: !!rawKey,
123+
defaultModel: parsed.default_model || parsed.defaultModel || '',
124+
path: configPath
125+
};
126+
break;
127+
}
128+
} catch (_) {}
129+
}
130+
if (!configs[providerId]) {
131+
configs[providerId] = { name: config.name, apiKey: '', hasKey: false, defaultModel: '', path: '' };
132+
}
133+
}
134+
return configs;
135+
}
136+
137+
export function saveProviderConfig(providerId, apiKey, defaultModel) {
138+
const config = PROVIDER_CONFIGS[providerId];
139+
if (!config) throw new Error('Unknown provider: ' + providerId);
140+
const configPath = config.configPaths[0];
141+
const configDir = path.dirname(configPath);
142+
if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true });
143+
let existing = {};
144+
try {
145+
if (fs.existsSync(configPath)) existing = JSON.parse(fs.readFileSync(configPath, 'utf8'));
146+
} catch (_) {}
147+
const merged = { ...existing, ...config.configFormat(apiKey, defaultModel) };
148+
fs.writeFileSync(configPath, JSON.stringify(merged, null, 2), { mode: 0o600 });
149+
return configPath;
150+
}

lib/server-utils.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
import os from 'os';
4+
5+
export const errLogPath = path.join(os.homedir(), 'logs', 'agentgui-errors.log');
6+
7+
export function logError(op, err, ctx = {}) {
8+
try {
9+
const line = JSON.stringify({ ts: new Date().toISOString(), op, msg: err?.message, stack: err?.stack, ...ctx }) + '\n';
10+
fs.appendFile(errLogPath, line, () => {});
11+
} catch (_) {}
12+
}
13+
14+
export function makeCleanupExecution(deps) {
15+
const { execMachine, activeExecutions, queries, broadcastSync, debugLog } = deps;
16+
return function cleanupExecution(conversationId, broadcastCompletion = false) {
17+
debugLog(`[cleanup] Starting cleanup for ${conversationId}`);
18+
const machineSnap = execMachine.snapshot(conversationId);
19+
if (machineSnap && machineSnap.value !== 'idle') {
20+
execMachine.send(conversationId, { type: 'CANCEL' });
21+
}
22+
activeExecutions.delete(conversationId);
23+
queries.setIsStreaming(conversationId, false);
24+
if (broadcastCompletion) {
25+
broadcastSync({
26+
type: 'execution_cleaned_up',
27+
conversationId,
28+
timestamp: Date.now()
29+
});
30+
}
31+
debugLog(`[cleanup] Cleanup complete for ${conversationId}`);
32+
};
33+
}
34+
35+
export function makeGetModelsForAgent(deps) {
36+
const { modelCache, discoveredAgents, ensureRunning, queryACPModels } = deps;
37+
return async function getModelsForAgent(agentId) {
38+
const cached = modelCache.get(agentId);
39+
if (cached && Date.now() - cached.timestamp < 300000) return cached.models;
40+
let models = [];
41+
if (agentId === 'claude-code') {
42+
models = [
43+
{ id: 'haiku', label: 'Haiku' },
44+
{ id: 'sonnet', label: 'Sonnet' },
45+
{ id: 'opus', label: 'Opus' }
46+
];
47+
} else {
48+
const agent = discoveredAgents.find(a => a.id === agentId);
49+
if (agent?.protocol === 'acp') {
50+
await ensureRunning(agentId);
51+
try { models = await queryACPModels(agentId); } catch (_) {}
52+
} else if (agent?.protocol === 'cli-wrapper' && agent.acpId) {
53+
await ensureRunning(agent.acpId);
54+
try { models = await queryACPModels(agent.acpId); } catch (_) {}
55+
}
56+
}
57+
modelCache.set(agentId, { models, timestamp: Date.now() });
58+
return models;
59+
};
60+
}

0 commit comments

Comments
 (0)