207 lines
7.3 KiB
JavaScript
207 lines
7.3 KiB
JavaScript
export const registerOpenCodeRoutes = (app, dependencies) => {
|
|||
|
|
const {
|
||
|
|
crypto,
|
||
|
|
clientReloadDelayMs,
|
||
|
|
getOpenCodeResolutionSnapshot,
|
||
|
|
formatSettingsResponse,
|
||
|
|
readSettingsFromDisk,
|
||
|
|
readSettingsFromDiskMigrated,
|
||
|
|
persistSettings,
|
||
|
|
sanitizeProjects,
|
||
|
|
validateDirectoryPath,
|
||
|
|
resolveProjectDirectory,
|
||
|
|
getProviderSources,
|
||
|
|
removeProviderConfig,
|
||
|
|
refreshOpenCodeAfterConfigChange,
|
||
|
|
} = dependencies;
|
||
|
|
|
||
|
|
let authLibrary = null;
|
||
|
|
const getAuthLibrary = async () => {
|
||
|
|
if (!authLibrary) {
|
||
|
|
authLibrary = await import('./auth.js');
|
||
|
|
}
|
||
|
|
return authLibrary;
|
||
|
|
};
|
||
|
|
|
||
|
|
app.get('/api/config/settings', async (_req, res) => {
|
||
|
|
try {
|
||
|
|
const settings = await readSettingsFromDiskMigrated();
|
||
|
|
res.json(formatSettingsResponse(settings));
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Failed to read settings:', error);
|
||
|
|
res.status(500).json({ error: 'Failed to read settings' });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
app.get('/api/config/opencode-resolution', async (_req, res) => {
|
||
|
|
try {
|
||
|
|
const settings = await readSettingsFromDiskMigrated();
|
||
|
|
const resolution = await getOpenCodeResolutionSnapshot(settings);
|
||
|
|
res.json(resolution);
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Failed to resolve OpenCode binary:', error);
|
||
|
|
res.status(500).json({ error: 'Failed to resolve OpenCode binary' });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
app.put('/api/config/settings', async (req, res) => {
|
||
|
|
console.log('[API:PUT /api/config/settings] Received request');
|
||
|
|
try {
|
||
|
|
const updated = await persistSettings(req.body ?? {});
|
||
|
|
console.log(`[API:PUT /api/config/settings] Success, returning ${updated.projects?.length || 0} projects`);
|
||
|
|
res.json(updated);
|
||
|
|
} catch (error) {
|
||
|
|
console.error('[API:PUT /api/config/settings] Failed to save settings:', error);
|
||
|
|
console.error('[API:PUT /api/config/settings] Error stack:', error.stack);
|
||
|
|
res.status(500).json({ error: 'Failed to save settings' });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
app.get('/api/provider/:providerId/source', async (req, res) => {
|
||
|
|
try {
|
||
|
|
const { providerId } = req.params;
|
||
|
|
if (!providerId) {
|
||
|
|
return res.status(400).json({ error: 'Provider ID is required' });
|
||
|
|
}
|
||
|
|
|
||
|
|
const headerDirectory = typeof req.get === 'function' ? req.get('x-opencode-directory') : null;
|
||
|
|
const queryDirectory = Array.isArray(req.query?.directory)
|
||
|
|
? req.query.directory[0]
|
||
|
|
: req.query?.directory;
|
||
|
|
const requestedDirectory = headerDirectory || queryDirectory || null;
|
||
|
|
|
||
|
|
let directory = null;
|
||
|
|
const resolved = await resolveProjectDirectory(req);
|
||
|
|
if (resolved.directory) {
|
||
|
|
directory = resolved.directory;
|
||
|
|
} else if (requestedDirectory) {
|
||
|
|
return res.status(400).json({ error: resolved.error });
|
||
|
|
}
|
||
|
|
|
||
|
|
const sources = getProviderSources(providerId, directory);
|
||
|
|
const { getProviderAuth } = await getAuthLibrary();
|
||
|
|
const auth = getProviderAuth(providerId);
|
||
|
|
sources.sources.auth.exists = Boolean(auth);
|
||
|
|
|
||
|
|
return res.json({
|
||
|
|
providerId,
|
||
|
|
sources: sources.sources,
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Failed to get provider sources:', error);
|
||
|
|
return res.status(500).json({ error: error.message || 'Failed to get provider sources' });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
app.delete('/api/provider/:providerId/auth', async (req, res) => {
|
||
|
|
try {
|
||
|
|
const { providerId } = req.params;
|
||
|
|
if (!providerId) {
|
||
|
|
return res.status(400).json({ error: 'Provider ID is required' });
|
||
|
|
}
|
||
|
|
|
||
|
|
const scope = typeof req.query?.scope === 'string' ? req.query.scope : 'auth';
|
||
|
|
const headerDirectory = typeof req.get === 'function' ? req.get('x-opencode-directory') : null;
|
||
|
|
const queryDirectory = Array.isArray(req.query?.directory)
|
||
|
|
? req.query.directory[0]
|
||
|
|
: req.query?.directory;
|
||
|
|
const requestedDirectory = headerDirectory || queryDirectory || null;
|
||
|
|
let directory = null;
|
||
|
|
|
||
|
|
if (scope === 'project' || requestedDirectory) {
|
||
|
|
const resolved = await resolveProjectDirectory(req);
|
||
|
|
if (!resolved.directory) {
|
||
|
|
return res.status(400).json({ error: resolved.error });
|
||
|
|
}
|
||
|
|
directory = resolved.directory;
|
||
|
|
} else {
|
||
|
|
const resolved = await resolveProjectDirectory(req);
|
||
|
|
if (resolved.directory) {
|
||
|
|
directory = resolved.directory;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
let removed = false;
|
||
|
|
if (scope === 'auth') {
|
||
|
|
const { removeProviderAuth } = await getAuthLibrary();
|
||
|
|
removed = removeProviderAuth(providerId);
|
||
|
|
} else if (scope === 'user' || scope === 'project' || scope === 'custom') {
|
||
|
|
removed = removeProviderConfig(providerId, directory, scope);
|
||
|
|
} else if (scope === 'all') {
|
||
|
|
const { removeProviderAuth } = await getAuthLibrary();
|
||
|
|
const authRemoved = removeProviderAuth(providerId);
|
||
|
|
const userRemoved = removeProviderConfig(providerId, directory, 'user');
|
||
|
|
const projectRemoved = directory ? removeProviderConfig(providerId, directory, 'project') : false;
|
||
|
|
const customRemoved = removeProviderConfig(providerId, directory, 'custom');
|
||
|
|
removed = authRemoved || userRemoved || projectRemoved || customRemoved;
|
||
|
|
} else {
|
||
|
|
return res.status(400).json({ error: 'Invalid scope' });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (removed) {
|
||
|
|
await refreshOpenCodeAfterConfigChange(`provider ${providerId} disconnected (${scope})`);
|
||
|
|
}
|
||
|
|
|
||
|
|
return res.json({
|
||
|
|
success: true,
|
||
|
|
removed,
|
||
|
|
requiresReload: removed,
|
||
|
|
message: removed ? 'Provider disconnected successfully' : 'Provider was not connected',
|
||
|
|
reloadDelayMs: removed ? clientReloadDelayMs : undefined,
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Failed to disconnect provider:', error);
|
||
|
|
return res.status(500).json({ error: error.message || 'Failed to disconnect provider' });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
app.post('/api/opencode/directory', async (req, res) => {
|
||
|
|
try {
|
||
|
|
const requestedPath = typeof req.body?.path === 'string' ? req.body.path.trim() : '';
|
||
|
|
if (!requestedPath) {
|
||
|
|
return res.status(400).json({ error: 'Path is required' });
|
||
|
|
}
|
||
|
|
|
||
|
|
const validated = await validateDirectoryPath(requestedPath);
|
||
|
|
if (!validated.ok) {
|
||
|
|
return res.status(400).json({ error: validated.error });
|
||
|
|
}
|
||
|
|
|
||
|
|
const resolvedPath = validated.directory;
|
||
|
|
const currentSettings = await readSettingsFromDisk();
|
||
|
|
const existingProjects = sanitizeProjects(currentSettings.projects) || [];
|
||
|
|
const existing = existingProjects.find((project) => project.path === resolvedPath) || null;
|
||
|
|
|
||
|
|
const nextProjects = existing
|
||
|
|
? existingProjects
|
||
|
|
: [
|
||
|
|
...existingProjects,
|
||
|
|
{
|
||
|
|
id: crypto.randomUUID(),
|
||
|
|
path: resolvedPath,
|
||
|
|
addedAt: Date.now(),
|
||
|
|
lastOpenedAt: Date.now(),
|
||
|
|
},
|
||
|
|
];
|
||
|
|
|
||
|
|
const activeProjectId = existing ? existing.id : nextProjects[nextProjects.length - 1].id;
|
||
|
|
|
||
|
|
const updated = await persistSettings({
|
||
|
|
projects: nextProjects,
|
||
|
|
activeProjectId,
|
||
|
|
lastDirectory: resolvedPath,
|
||
|
|
});
|
||
|
|
|
||
|
|
return res.json({
|
||
|
|
success: true,
|
||
|
|
restarted: false,
|
||
|
|
path: resolvedPath,
|
||
|
|
settings: updated,
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Failed to update OpenCode working directory:', error);
|
||
|
|
return res.status(500).json({ error: error.message || 'Failed to update working directory' });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
};
|