@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
122 lines • 4.35 kB
JavaScript
import { Agent } from 'undici';
import { TIMEOUT_SOCKET_DEFAULT_MS } from '../constants.js';
import { getModelContextLimit } from '../models/index.js';
import { getLogger } from '../utils/logging/index.js';
import { handleChat } from './chat/chat-handler.js';
import { createProvider } from './providers/provider-factory.js';
export class AISDKClient {
provider;
currentModel;
availableModels;
providerConfig;
undiciAgent;
cachedContextSize;
maxRetries;
constructor(providerConfig) {
const logger = getLogger();
this.providerConfig = providerConfig;
this.availableModels = providerConfig.models;
this.currentModel = providerConfig.models[0] || '';
this.cachedContextSize = 0;
// Default to 2 retries (same as AI SDK default), or use configured value
this.maxRetries = providerConfig.maxRetries ?? 2;
logger.info('AI SDK client initializing', {
models: this.availableModels,
defaultModel: this.currentModel,
provider: providerConfig.name || 'unknown',
baseUrl: providerConfig.config.baseURL ? '[REDACTED]' : undefined,
maxRetries: this.maxRetries,
});
const { connectionPool } = this.providerConfig;
const { requestTimeout, socketTimeout } = this.providerConfig;
const effectiveSocketTimeout = socketTimeout ?? requestTimeout;
const resolvedSocketTimeout = effectiveSocketTimeout === -1
? 0
: (effectiveSocketTimeout ?? TIMEOUT_SOCKET_DEFAULT_MS);
this.undiciAgent = new Agent({
connect: {
timeout: resolvedSocketTimeout,
},
bodyTimeout: resolvedSocketTimeout,
headersTimeout: resolvedSocketTimeout,
keepAliveTimeout: connectionPool?.idleTimeout,
keepAliveMaxTimeout: connectionPool?.cumulativeMaxIdleTimeout,
});
this.provider = createProvider(this.providerConfig, this.undiciAgent);
// Fetch context size asynchronously (don't block construction)
void this.updateContextSize();
}
/**
* Fetch and cache context size from models.dev
*/
async updateContextSize() {
const logger = getLogger();
try {
const contextSize = await getModelContextLimit(this.currentModel);
this.cachedContextSize = contextSize || 0;
}
catch (error) {
logger.debug('Failed to get model context size', {
model: this.currentModel,
error,
});
this.cachedContextSize = 0;
}
}
static create(providerConfig) {
const client = new AISDKClient(providerConfig);
return Promise.resolve(client);
}
setModel(model) {
const logger = getLogger();
const previousModel = this.currentModel;
this.currentModel = model;
logger.info('Model changed', {
previousModel,
newModel: model,
provider: this.providerConfig.name,
});
// Update context size when model changes
void this.updateContextSize();
}
getCurrentModel() {
return this.currentModel;
}
getContextSize() {
return this.cachedContextSize;
}
getMaxRetries() {
return this.maxRetries;
}
getAvailableModels() {
return Promise.resolve(this.availableModels);
}
/**
* Stream chat with real-time token updates
*/
async chat(messages, tools, callbacks, signal) {
// Get the language model instance from the provider
const model = this.provider(this.currentModel);
// Delegate to chat handler
return await handleChat({
model,
currentModel: this.currentModel,
providerConfig: this.providerConfig,
messages,
tools,
callbacks,
signal,
maxRetries: this.maxRetries,
});
}
clearContext() {
const logger = getLogger();
logger.debug('AI SDK client context cleared', {
model: this.currentModel,
provider: this.providerConfig.name,
});
// No internal state to clear
return Promise.resolve();
}
}
//# sourceMappingURL=ai-sdk-client.js.map