@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
JavaScript
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