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

112 lines 4.56 kB
import { createAnthropic } from '@ai-sdk/anthropic'; import { createGoogleGenerativeAI, } from '@ai-sdk/google'; import { createOpenAI } from '@ai-sdk/openai'; import { createOpenAICompatible, } from '@ai-sdk/openai-compatible'; import { fetch as undiciFetch } from 'undici'; import { COPILOT_HEADERS, getCopilotAccessToken, getCopilotBaseUrl, } from '../../auth/github-copilot.js'; import { getCopilotNoCredentialsMessage, loadCopilotCredential, } from '../../config/copilot-credentials.js'; import { getLogger } from '../../utils/logging/index.js'; /** * Creates an AI SDK provider based on the sdkProvider configuration. * Defaults to 'openai-compatible' if not specified. */ export function createProvider(providerConfig, undiciAgent) { const logger = getLogger(); const { config, sdkProvider } = providerConfig; // Use explicit sdkProvider if set, otherwise default to 'openai-compatible' if (sdkProvider === 'anthropic') { logger.info('Using @ai-sdk/anthropic provider', { provider: providerConfig.name, sdkProvider, }); return createAnthropic({ baseURL: config.baseURL || undefined, apiKey: config.apiKey ?? '', headers: config.headers, }); } if (sdkProvider === 'google') { logger.info('Using @ai-sdk/google provider', { provider: providerConfig.name, sdkProvider, }); return createGoogleGenerativeAI({ apiKey: config.apiKey ?? '', }); } if (sdkProvider === 'github-copilot') { logger.info('Using GitHub Copilot subscription provider', { provider: providerConfig.name, }); const credential = loadCopilotCredential(providerConfig.name); if (!credential) { throw new Error(getCopilotNoCredentialsMessage(providerConfig.name)); } const domain = credential.enterpriseUrl ?? 'github.com'; const baseURL = config.baseURL?.trim() || getCopilotBaseUrl(domain); const copilotFetch = async (input, init) => { const { token } = await getCopilotAccessToken(credential.oauthToken, domain); // Build headers via Headers (case-insensitive) to avoid // duplicate keys when merging SDK lowercase and Copilot mixed-case. const h = new Headers(); if (init?.headers) { const src = init.headers instanceof Headers ? init.headers : new Headers(init.headers); src.forEach((v, k) => { if (k !== 'authorization') { h.set(k, v); } }); } for (const [k, v] of Object.entries(COPILOT_HEADERS)) { h.set(k, v); } h.set('Authorization', `Bearer ${token}`); h.set('Openai-Intent', 'conversation-edits'); h.set('X-Initiator', 'agent'); // Convert to plain object for undici const headers = {}; h.forEach((v, k) => { headers[k] = v; }); return undiciFetch(input, { method: init?.method, body: init?.body, signal: init?.signal, headers, dispatcher: undiciAgent, }); }; return createOpenAI({ baseURL, // Empty key — auth is handled entirely by copilotFetch's Authorization header apiKey: '', fetch: copilotFetch, headers: config.headers ?? {}, }); } // Custom fetch using undici const customFetch = (url, options) => { // Type cast to string | URL since undici's fetch accepts these types // Request objects are converted to URL internally by the fetch spec return undiciFetch(url, { ...options, dispatcher: undiciAgent, }); }; // Add OpenRouter-specific headers for app attribution const headers = config.headers ?? {}; if (providerConfig.name.toLowerCase() === 'openrouter') { headers['HTTP-Referer'] = 'https://github.com/Nano-Collective/nanocoder'; headers['X-Title'] = 'Nanocoder'; } return createOpenAICompatible({ name: providerConfig.name, baseURL: config.baseURL ?? '', apiKey: config.apiKey ?? 'dummy-key', fetch: customFetch, headers, }); } //# sourceMappingURL=provider-factory.js.map