UNPKG

@nanocollective/nanocoder

Version:

A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter

197 lines 6.82 kB
import { TIMEOUT_PROVIDER_CONNECTION_MS } from '../constants.js'; import { isLocalURL } from '../utils/url-utils.js'; /** * Validates the structure of the configuration object */ export function validateConfig(providers, mcpServers) { const errors = []; const warnings = []; // Validate providers if (providers.length === 0) { warnings.push('No providers configured. Nanocoder requires at least one provider to function.'); } for (const provider of providers) { if (!provider.name) { errors.push('Provider missing name'); } if (!provider.models || provider.models.length === 0) { errors.push(`Provider "${provider.name}" has no models configured`); } // Validate base URL if present if (provider.baseUrl) { try { new URL(provider.baseUrl); } catch { errors.push(`Provider "${provider.name}" has invalid base URL: ${provider.baseUrl}`); } } } // Validate MCP servers for (const [name, server] of Object.entries(mcpServers)) { if (!server.command) { errors.push(`MCP server "${name}" missing command`); } if (!server.args) { errors.push(`MCP server "${name}" missing args array`); } if (server.alwaysAllow && !Array.isArray(server.alwaysAllow)) { errors.push(`MCP server "${name}" has invalid alwaysAllow (must be an array of strings)`); } if (Array.isArray(server.alwaysAllow)) { const invalidItems = server.alwaysAllow.filter(item => typeof item !== 'string'); if (invalidItems.length > 0) { errors.push(`MCP server "${name}" has non-string entries in alwaysAllow`); } } } return { valid: errors.length === 0, errors, warnings, }; } /** * Tests connectivity to a provider */ export async function testProviderConnection(provider, timeout = TIMEOUT_PROVIDER_CONNECTION_MS) { // If no base URL, assume it's valid (will be validated when actually connecting) if (!provider.baseUrl) { return { providerName: provider.name, connected: true, }; } try { // Only test localhost connections (don't want to spam cloud APIs) if (!isLocalURL(provider.baseUrl)) { return { providerName: provider.name, connected: true, // Assume cloud APIs are reachable }; } // Test localhost connection with a simple fetch const controller = new AbortController(); const timeoutId = setTimeout(() => { controller.abort(); }, timeout); const response = await fetch(provider.baseUrl, { method: 'GET', signal: controller.signal, }); clearTimeout(timeoutId); return { providerName: provider.name, connected: response.ok || response.status === 404, // 404 is ok, server is running }; } catch (error) { return { providerName: provider.name, connected: false, error: error instanceof Error ? error.message : 'Unknown error testing connection', }; } } /** * Builds the provider configuration object for agents.config.json */ export function buildProviderConfigObject(providers) { const config = { nanocoder: { providers: providers.map(p => { const providerConfig = { name: p.name, models: p.models, }; if (p.baseUrl) { providerConfig.baseUrl = p.baseUrl; } if (p.apiKey) { providerConfig.apiKey = p.apiKey; } if (p.organizationId) { providerConfig.organizationId = p.organizationId; } if (p.timeout) { providerConfig.timeout = p.timeout; } if (p.sdkProvider) { providerConfig.sdkProvider = p.sdkProvider; } return providerConfig; }), }, }; return config; } /** * Builds the MCP configuration object for .mcp.json * Uses Claude Code object format (servers as object keys) */ export function buildMcpConfigObject(mcpServers) { // Add enabled flag to all servers configured via wizard const serversWithEnabled = {}; for (const [key, server] of Object.entries(mcpServers)) { serversWithEnabled[key] = { ...server, enabled: true, // Default to enabled for wizard configurations }; } return { mcpServers: serversWithEnabled, }; } /** * @deprecated Use buildProviderConfigObject and buildMcpConfigObject instead * This function is kept for backward compatibility during migration */ export function buildConfigObject(providers, mcpServers) { const config = { nanocoder: { providers: providers.map(p => { const providerConfig = { name: p.name, models: p.models, }; if (p.baseUrl) { providerConfig.baseUrl = p.baseUrl; } if (p.apiKey) { providerConfig.apiKey = p.apiKey; } if (p.organizationId) { providerConfig.organizationId = p.organizationId; } if (p.timeout) { providerConfig.timeout = p.timeout; } if (p.sdkProvider) { providerConfig.sdkProvider = p.sdkProvider; } return providerConfig; }), }, }; // Add MCP servers if any - convert Record<string, McpServerConfig> to new array format if (Object.keys(mcpServers).length > 0) { config.nanocoder.mcpServers = Object.values(mcpServers).map(server => ({ name: server.name, transport: server.transport || 'stdio', // Default to stdio for backward compatibility command: server.command, args: server.args, env: server.env, url: server.url, headers: server.headers, timeout: server.timeout, alwaysAllow: server.alwaysAllow, description: server.description, tags: server.tags, enabled: true, // Default to enabled for wizard configurations })); } return config; } //# sourceMappingURL=validation.js.map