git-coco 0.26.0 → 0.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +8 -0
- package/dist/index.esm.mjs +203 -10
- package/dist/index.js +203 -10
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -86,6 +86,14 @@ type OllamaFields = Partial<OllamaInput> & BaseLLMParams;
|
|
|
86
86
|
type OpenAILLMService = BaseLLMService & {
|
|
87
87
|
provider: 'openai';
|
|
88
88
|
model: OpenAIModel;
|
|
89
|
+
/**
|
|
90
|
+
* Custom base URL for OpenAI-compatible APIs (e.g., OpenRouter, Azure OpenAI).
|
|
91
|
+
* If not specified, uses the default OpenAI API endpoint.
|
|
92
|
+
*
|
|
93
|
+
* @example "https://openrouter.ai/api/v1"
|
|
94
|
+
* @example "https://your-resource.openai.azure.com"
|
|
95
|
+
*/
|
|
96
|
+
baseURL?: string;
|
|
89
97
|
fields?: OpenAIFields;
|
|
90
98
|
};
|
|
91
99
|
type OllamaLLMService = BaseLLMService & {
|
package/dist/index.esm.mjs
CHANGED
|
@@ -46,7 +46,7 @@ import { pathToFileURL } from 'url';
|
|
|
46
46
|
/**
|
|
47
47
|
* Current build version from package.json
|
|
48
48
|
*/
|
|
49
|
-
const BUILD_VERSION = "0.
|
|
49
|
+
const BUILD_VERSION = "0.27.1";
|
|
50
50
|
|
|
51
51
|
const isInteractive = (config) => {
|
|
52
52
|
return config?.mode === 'interactive' || !!config?.interactive;
|
|
@@ -226,6 +226,16 @@ class LangChainAuthenticationError extends LangChainError {
|
|
|
226
226
|
super(message, context);
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Network/connection errors (service unreachable, DNS failures, etc.)
|
|
231
|
+
*/
|
|
232
|
+
class LangChainNetworkError extends LangChainError {
|
|
233
|
+
constructor(message, endpoint, provider, context) {
|
|
234
|
+
super(message, { ...context, endpoint, provider });
|
|
235
|
+
this.endpoint = endpoint;
|
|
236
|
+
this.provider = provider;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
229
239
|
|
|
230
240
|
/**
|
|
231
241
|
* Validates that a required parameter is not null or undefined
|
|
@@ -479,6 +489,7 @@ function loadEnvConfig(config) {
|
|
|
479
489
|
'COCO_SERVICE_PROVIDER',
|
|
480
490
|
'COCO_SERVICE_MODEL',
|
|
481
491
|
'OPEN_AI_KEY',
|
|
492
|
+
'COCO_SERVICE_BASE_URL',
|
|
482
493
|
'COCO_SERVICE_ENDPOINT',
|
|
483
494
|
'COCO_SERVICE_REQUEST_OPTIONS_TIMEOUT',
|
|
484
495
|
'COCO_SERVICE_REQUEST_OPTIONS_MAX_RETRIES',
|
|
@@ -493,6 +504,7 @@ function loadEnvConfig(config) {
|
|
|
493
504
|
if (key === 'COCO_SERVICE_PROVIDER' ||
|
|
494
505
|
key === 'COCO_SERVICE_MODEL' ||
|
|
495
506
|
key === 'OPEN_AI_KEY' ||
|
|
507
|
+
key === 'COCO_SERVICE_BASE_URL' ||
|
|
496
508
|
key === 'COCO_SERVICE_ENDPOINT' ||
|
|
497
509
|
key === 'COCO_SERVICE_REQUEST_OPTIONS_TIMEOUT' ||
|
|
498
510
|
key === 'COCO_SERVICE_REQUEST_OPTIONS_MAX_RETRIES' ||
|
|
@@ -531,6 +543,12 @@ function handleServiceEnvVar(service, key, value) {
|
|
|
531
543
|
};
|
|
532
544
|
}
|
|
533
545
|
break;
|
|
546
|
+
case 'COCO_SERVICE_BASE_URL':
|
|
547
|
+
if (service.provider === 'openai') {
|
|
548
|
+
// Cast to OpenAILLMService to access baseURL property
|
|
549
|
+
service.baseURL = value;
|
|
550
|
+
}
|
|
551
|
+
break;
|
|
534
552
|
case 'COCO_SERVICE_ENDPOINT':
|
|
535
553
|
if (service.provider === 'ollama') {
|
|
536
554
|
service.endpoint = value;
|
|
@@ -659,6 +677,7 @@ function loadGitConfig(config) {
|
|
|
659
677
|
maxParsingAttempts: gitConfigParsed.coco?.serviceMaxParsingAttempts
|
|
660
678
|
? Number(gitConfigParsed.coco.serviceMaxParsingAttempts)
|
|
661
679
|
: undefined,
|
|
680
|
+
baseURL: gitConfigParsed.coco?.serviceBaseURL,
|
|
662
681
|
authentication: {
|
|
663
682
|
type: 'APIKey',
|
|
664
683
|
credentials: {
|
|
@@ -738,6 +757,11 @@ const appendToGitConfig = async (filePath, config) => {
|
|
|
738
757
|
if (service.requestOptions?.maxRetries) {
|
|
739
758
|
contentLines.push(` serviceRequestOptionsMaxRetries = ${service.requestOptions.maxRetries}`);
|
|
740
759
|
}
|
|
760
|
+
// Handle baseURL for OpenAI
|
|
761
|
+
if (service.provider === 'openai' && 'baseURL' in service && service.baseURL) {
|
|
762
|
+
contentLines.push(` serviceBaseURL = ${service.baseURL}`);
|
|
763
|
+
}
|
|
764
|
+
// Handle endpoint for Ollama
|
|
741
765
|
if (service.provider === 'ollama') {
|
|
742
766
|
const ollamaService = service;
|
|
743
767
|
if (ollamaService.endpoint) {
|
|
@@ -928,6 +952,14 @@ const schema$1 = {
|
|
|
928
952
|
"model": {
|
|
929
953
|
"$ref": "#/definitions/LLMModel"
|
|
930
954
|
},
|
|
955
|
+
"baseURL": {
|
|
956
|
+
"type": "string",
|
|
957
|
+
"description": "Custom base URL for OpenAI-compatible APIs (e.g., OpenRouter, Azure OpenAI). If not specified, uses the default OpenAI API endpoint.",
|
|
958
|
+
"examples": [
|
|
959
|
+
"https://openrouter.ai/api/v1",
|
|
960
|
+
"https://your-resource.openai.azure.com"
|
|
961
|
+
]
|
|
962
|
+
},
|
|
931
963
|
"fields": {
|
|
932
964
|
"type": "object",
|
|
933
965
|
"additionalProperties": false,
|
|
@@ -2202,12 +2234,14 @@ function parseServiceConfig(service) {
|
|
|
2202
2234
|
return {
|
|
2203
2235
|
provider: 'openai',
|
|
2204
2236
|
model: service.model,
|
|
2237
|
+
baseURL: service.baseURL,
|
|
2205
2238
|
authentication: {
|
|
2206
2239
|
type: 'APIKey',
|
|
2207
2240
|
credentials: {
|
|
2208
2241
|
apiKey: service.apiKey
|
|
2209
2242
|
}
|
|
2210
|
-
}
|
|
2243
|
+
},
|
|
2244
|
+
fields: service.fields
|
|
2211
2245
|
};
|
|
2212
2246
|
case 'anthropic':
|
|
2213
2247
|
return {
|
|
@@ -2327,6 +2361,59 @@ class Logger {
|
|
|
2327
2361
|
}
|
|
2328
2362
|
}
|
|
2329
2363
|
|
|
2364
|
+
/**
|
|
2365
|
+
* Formats a network error with helpful troubleshooting information
|
|
2366
|
+
*/
|
|
2367
|
+
function formatNetworkError(error, logger) {
|
|
2368
|
+
const endpoint = error.endpoint || 'unknown endpoint';
|
|
2369
|
+
const provider = error.provider || 'LLM service';
|
|
2370
|
+
logger.log('\nFailed to execute command', { color: 'yellow' });
|
|
2371
|
+
logger.log(`\nError: Unable to connect to ${provider}`, { color: 'red' });
|
|
2372
|
+
if (error.endpoint) {
|
|
2373
|
+
logger.log(` Endpoint: ${endpoint}`, { color: 'red' });
|
|
2374
|
+
}
|
|
2375
|
+
logger.log('\nTroubleshooting:', { color: 'cyan' });
|
|
2376
|
+
// Provider-specific troubleshooting
|
|
2377
|
+
if (provider === 'ollama' || endpoint.includes('11434')) {
|
|
2378
|
+
logger.log(' • Is Ollama running? Try: ollama serve', { color: 'white' });
|
|
2379
|
+
logger.log(' • Check if the endpoint is correct in your config', { color: 'white' });
|
|
2380
|
+
logger.log(` • Verify Ollama is accessible: curl ${endpoint}/api/version`, { color: 'white' });
|
|
2381
|
+
}
|
|
2382
|
+
else if (provider === 'openai' || endpoint.includes('openai')) {
|
|
2383
|
+
logger.log(' • Check your internet connection', { color: 'white' });
|
|
2384
|
+
logger.log(' • Verify the API endpoint is accessible', { color: 'white' });
|
|
2385
|
+
logger.log(' • If using a custom baseURL, verify it is correct', { color: 'white' });
|
|
2386
|
+
}
|
|
2387
|
+
else if (provider === 'anthropic') {
|
|
2388
|
+
logger.log(' • Check your internet connection', { color: 'white' });
|
|
2389
|
+
logger.log(' • Verify the Anthropic API is accessible', { color: 'white' });
|
|
2390
|
+
}
|
|
2391
|
+
else {
|
|
2392
|
+
logger.log(' • Check your internet connection', { color: 'white' });
|
|
2393
|
+
logger.log(' • Verify the service endpoint is correct', { color: 'white' });
|
|
2394
|
+
logger.log(' • Ensure the LLM service is running and accessible', { color: 'white' });
|
|
2395
|
+
}
|
|
2396
|
+
logger.verbose(`\nOriginal error: ${error.message}`, { color: 'gray' });
|
|
2397
|
+
}
|
|
2398
|
+
/**
|
|
2399
|
+
* Formats an authentication error with helpful information
|
|
2400
|
+
*/
|
|
2401
|
+
function formatAuthenticationError(error, logger) {
|
|
2402
|
+
logger.log('\nFailed to execute command', { color: 'yellow' });
|
|
2403
|
+
logger.log('\nError: Authentication failed', { color: 'red' });
|
|
2404
|
+
logger.log('\nTroubleshooting:', { color: 'cyan' });
|
|
2405
|
+
logger.log(' • Verify your API key is correct', { color: 'white' });
|
|
2406
|
+
logger.log(' • Check that your API key has not expired', { color: 'white' });
|
|
2407
|
+
logger.log(' • Ensure the API key is set in your environment or config', { color: 'white' });
|
|
2408
|
+
logger.verbose(`\nOriginal error: ${error.message}`, { color: 'gray' });
|
|
2409
|
+
}
|
|
2410
|
+
/**
|
|
2411
|
+
* Formats a generic error
|
|
2412
|
+
*/
|
|
2413
|
+
function formatGenericError(error, logger) {
|
|
2414
|
+
logger.log('\nFailed to execute command', { color: 'yellow' });
|
|
2415
|
+
logger.verbose(`\nError: "${error.message}"`, { color: 'red' });
|
|
2416
|
+
}
|
|
2330
2417
|
function commandExecutor(handler) {
|
|
2331
2418
|
return async (argv) => {
|
|
2332
2419
|
const options = loadConfig(argv);
|
|
@@ -2335,8 +2422,16 @@ function commandExecutor(handler) {
|
|
|
2335
2422
|
await handler(argv, logger);
|
|
2336
2423
|
}
|
|
2337
2424
|
catch (error) {
|
|
2338
|
-
|
|
2339
|
-
|
|
2425
|
+
// Handle specific error types with helpful messages
|
|
2426
|
+
if (error instanceof LangChainNetworkError) {
|
|
2427
|
+
formatNetworkError(error, logger);
|
|
2428
|
+
}
|
|
2429
|
+
else if (error instanceof LangChainAuthenticationError) {
|
|
2430
|
+
formatAuthenticationError(error, logger);
|
|
2431
|
+
}
|
|
2432
|
+
else {
|
|
2433
|
+
formatGenericError(error, logger);
|
|
2434
|
+
}
|
|
2340
2435
|
logger.log('\nThanks for using coco, make it a great day! 👋🤖', { color: 'blue' });
|
|
2341
2436
|
process.exit(0);
|
|
2342
2437
|
}
|
|
@@ -6228,10 +6323,50 @@ async function withRetry(operation, options = {}) {
|
|
|
6228
6323
|
throw new Error(`Operation failed after ${maxAttempts} attempts. Last error: ${lastError.message}`);
|
|
6229
6324
|
}
|
|
6230
6325
|
|
|
6326
|
+
/**
|
|
6327
|
+
* Network error patterns to detect connection failures
|
|
6328
|
+
*/
|
|
6329
|
+
const NETWORK_ERROR_PATTERNS = [
|
|
6330
|
+
'fetch failed',
|
|
6331
|
+
'ECONNREFUSED',
|
|
6332
|
+
'ENOTFOUND',
|
|
6333
|
+
'ETIMEDOUT',
|
|
6334
|
+
'ECONNRESET',
|
|
6335
|
+
'ENETUNREACH',
|
|
6336
|
+
'EHOSTUNREACH',
|
|
6337
|
+
'socket hang up',
|
|
6338
|
+
'network request failed',
|
|
6339
|
+
'Failed to fetch',
|
|
6340
|
+
'getaddrinfo',
|
|
6341
|
+
'connect ECONNREFUSED',
|
|
6342
|
+
];
|
|
6343
|
+
/**
|
|
6344
|
+
* Checks if an error message indicates a network/connection failure
|
|
6345
|
+
*/
|
|
6346
|
+
function isNetworkError(error) {
|
|
6347
|
+
if (!(error instanceof Error))
|
|
6348
|
+
return false;
|
|
6349
|
+
const message = error.message.toLowerCase();
|
|
6350
|
+
const errorCause = error?.cause?.message?.toLowerCase() || '';
|
|
6351
|
+
return NETWORK_ERROR_PATTERNS.some(pattern => message.includes(pattern.toLowerCase()) ||
|
|
6352
|
+
errorCause.includes(pattern.toLowerCase()));
|
|
6353
|
+
}
|
|
6231
6354
|
/**
|
|
6232
6355
|
* Wraps errors with additional context and converts them to LangChain errors
|
|
6233
6356
|
*/
|
|
6234
6357
|
function handleLangChainError(error, context, additionalContext) {
|
|
6358
|
+
// Check for network errors first
|
|
6359
|
+
if (error instanceof Error && isNetworkError(error)) {
|
|
6360
|
+
const endpoint = additionalContext?.endpoint;
|
|
6361
|
+
const provider = additionalContext?.provider;
|
|
6362
|
+
throw new LangChainNetworkError(error.message, endpoint, provider, {
|
|
6363
|
+
originalError: error.name,
|
|
6364
|
+
originalMessage: error.message,
|
|
6365
|
+
stack: error.stack,
|
|
6366
|
+
context,
|
|
6367
|
+
...additionalContext
|
|
6368
|
+
});
|
|
6369
|
+
}
|
|
6235
6370
|
// If it's already a LangChain error, re-throw with additional context
|
|
6236
6371
|
if (error instanceof LangChainError) {
|
|
6237
6372
|
throw new LangChainExecutionError(`${context}: ${error.message}`, {
|
|
@@ -6302,11 +6437,22 @@ function getLlm(provider, model, config) {
|
|
|
6302
6437
|
model,
|
|
6303
6438
|
});
|
|
6304
6439
|
case 'openai':
|
|
6305
|
-
|
|
6440
|
+
const openaiConfig = {
|
|
6306
6441
|
apiKey: apiKey,
|
|
6307
6442
|
model,
|
|
6308
6443
|
temperature: config.service.temperature || 0.2,
|
|
6309
|
-
}
|
|
6444
|
+
};
|
|
6445
|
+
// Add custom base URL if specified (for OpenRouter, Azure OpenAI, etc.)
|
|
6446
|
+
if ('baseURL' in config.service && config.service.baseURL) {
|
|
6447
|
+
openaiConfig.configuration = {
|
|
6448
|
+
baseURL: config.service.baseURL,
|
|
6449
|
+
};
|
|
6450
|
+
}
|
|
6451
|
+
// Merge any additional fields from config
|
|
6452
|
+
if ('fields' in config.service && config.service.fields) {
|
|
6453
|
+
Object.assign(openaiConfig, config.service.fields);
|
|
6454
|
+
}
|
|
6455
|
+
return new ChatOpenAI(openaiConfig);
|
|
6310
6456
|
default:
|
|
6311
6457
|
throw new LangChainConfigurationError(`getLlm: Unsupported provider '${provider}'`, { provider, model, supportedProviders: ['openai', 'anthropic', 'ollama'] });
|
|
6312
6458
|
}
|
|
@@ -7004,21 +7150,55 @@ You must return ONLY valid JSON that matches the schema exactly. Do not include
|
|
|
7004
7150
|
}
|
|
7005
7151
|
}
|
|
7006
7152
|
|
|
7153
|
+
/**
|
|
7154
|
+
* Extracts provider and endpoint info from LLM instance if available
|
|
7155
|
+
*/
|
|
7156
|
+
function extractLlmInfo(llm) {
|
|
7157
|
+
const info = {};
|
|
7158
|
+
// Try to extract provider from class name
|
|
7159
|
+
const className = llm?.constructor?.name || '';
|
|
7160
|
+
if (className.includes('Ollama')) {
|
|
7161
|
+
info.provider = 'ollama';
|
|
7162
|
+
// Try to get baseUrl from ollama instance
|
|
7163
|
+
if ('lc_kwargs' in llm && typeof llm.lc_kwargs === 'object' && llm.lc_kwargs !== null) {
|
|
7164
|
+
const kwargs = llm.lc_kwargs;
|
|
7165
|
+
if (typeof kwargs.baseUrl === 'string') {
|
|
7166
|
+
info.endpoint = kwargs.baseUrl;
|
|
7167
|
+
}
|
|
7168
|
+
}
|
|
7169
|
+
}
|
|
7170
|
+
else if (className.includes('OpenAI')) {
|
|
7171
|
+
info.provider = 'openai';
|
|
7172
|
+
}
|
|
7173
|
+
else if (className.includes('Anthropic')) {
|
|
7174
|
+
info.provider = 'anthropic';
|
|
7175
|
+
}
|
|
7176
|
+
return info;
|
|
7177
|
+
}
|
|
7007
7178
|
/**
|
|
7008
7179
|
* Executes a LangChain pipeline with the provided LLM, prompt, variables, and parser.
|
|
7009
7180
|
* @param params - The execution parameters
|
|
7010
7181
|
* @returns The parsed result from the LLM chain
|
|
7011
7182
|
* @throws LangChainExecutionError if the chain execution fails or returns empty results
|
|
7183
|
+
* @throws LangChainNetworkError if a network/connection error occurs
|
|
7012
7184
|
*/
|
|
7013
|
-
const executeChain = async ({ llm, prompt, variables, parser }) => {
|
|
7185
|
+
const executeChain = async ({ llm, prompt, variables, parser, provider, endpoint, }) => {
|
|
7014
7186
|
validateRequired(llm, 'llm', 'executeChain');
|
|
7015
7187
|
validateRequired(prompt, 'prompt', 'executeChain');
|
|
7016
7188
|
validateRequired(variables, 'variables', 'executeChain');
|
|
7017
7189
|
validateRequired(parser, 'parser', 'executeChain');
|
|
7018
7190
|
// Validate that variables is an object
|
|
7019
7191
|
if (typeof variables !== 'object' || Array.isArray(variables)) {
|
|
7020
|
-
throw new LangChainExecutionError('executeChain: Variables must be a non-array object', {
|
|
7192
|
+
throw new LangChainExecutionError('executeChain: Variables must be a non-array object', {
|
|
7193
|
+
variables,
|
|
7194
|
+
type: typeof variables,
|
|
7195
|
+
isArray: Array.isArray(variables),
|
|
7196
|
+
});
|
|
7021
7197
|
}
|
|
7198
|
+
// Extract LLM info for error reporting if not provided
|
|
7199
|
+
const llmInfo = extractLlmInfo(llm);
|
|
7200
|
+
const effectiveProvider = provider || llmInfo.provider;
|
|
7201
|
+
const effectiveEndpoint = endpoint || llmInfo.endpoint;
|
|
7022
7202
|
try {
|
|
7023
7203
|
const chain = prompt.pipe(llm).pipe(parser);
|
|
7024
7204
|
const result = await chain.invoke(variables);
|
|
@@ -7029,14 +7209,27 @@ const executeChain = async ({ llm, prompt, variables, parser }) => {
|
|
|
7029
7209
|
}
|
|
7030
7210
|
catch (error) {
|
|
7031
7211
|
// Re-throw LangChain errors as-is
|
|
7032
|
-
if (error instanceof LangChainExecutionError) {
|
|
7212
|
+
if (error instanceof LangChainExecutionError || error instanceof LangChainNetworkError) {
|
|
7033
7213
|
throw error;
|
|
7034
7214
|
}
|
|
7215
|
+
// Check for network errors and throw specific error type
|
|
7216
|
+
if (error instanceof Error && isNetworkError(error)) {
|
|
7217
|
+
throw new LangChainNetworkError(error.message, effectiveEndpoint, effectiveProvider, {
|
|
7218
|
+
originalError: error.name,
|
|
7219
|
+
originalMessage: error.message,
|
|
7220
|
+
stack: error.stack,
|
|
7221
|
+
promptInputVariables: prompt.inputVariables,
|
|
7222
|
+
variableKeys: Object.keys(variables),
|
|
7223
|
+
parserType: parser.constructor.name,
|
|
7224
|
+
});
|
|
7225
|
+
}
|
|
7035
7226
|
// Wrap other errors with context
|
|
7036
7227
|
handleLangChainError(error, 'executeChain: Chain execution failed', {
|
|
7037
7228
|
promptInputVariables: prompt.inputVariables,
|
|
7038
7229
|
variableKeys: Object.keys(variables),
|
|
7039
|
-
parserType: parser.constructor.name
|
|
7230
|
+
parserType: parser.constructor.name,
|
|
7231
|
+
provider: effectiveProvider,
|
|
7232
|
+
endpoint: effectiveEndpoint,
|
|
7040
7233
|
});
|
|
7041
7234
|
}
|
|
7042
7235
|
};
|
package/dist/index.js
CHANGED
|
@@ -68,7 +68,7 @@ var readline__namespace = /*#__PURE__*/_interopNamespaceDefault(readline);
|
|
|
68
68
|
/**
|
|
69
69
|
* Current build version from package.json
|
|
70
70
|
*/
|
|
71
|
-
const BUILD_VERSION = "0.
|
|
71
|
+
const BUILD_VERSION = "0.27.1";
|
|
72
72
|
|
|
73
73
|
const isInteractive = (config) => {
|
|
74
74
|
return config?.mode === 'interactive' || !!config?.interactive;
|
|
@@ -248,6 +248,16 @@ class LangChainAuthenticationError extends LangChainError {
|
|
|
248
248
|
super(message, context);
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Network/connection errors (service unreachable, DNS failures, etc.)
|
|
253
|
+
*/
|
|
254
|
+
class LangChainNetworkError extends LangChainError {
|
|
255
|
+
constructor(message, endpoint, provider, context) {
|
|
256
|
+
super(message, { ...context, endpoint, provider });
|
|
257
|
+
this.endpoint = endpoint;
|
|
258
|
+
this.provider = provider;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
251
261
|
|
|
252
262
|
/**
|
|
253
263
|
* Validates that a required parameter is not null or undefined
|
|
@@ -501,6 +511,7 @@ function loadEnvConfig(config) {
|
|
|
501
511
|
'COCO_SERVICE_PROVIDER',
|
|
502
512
|
'COCO_SERVICE_MODEL',
|
|
503
513
|
'OPEN_AI_KEY',
|
|
514
|
+
'COCO_SERVICE_BASE_URL',
|
|
504
515
|
'COCO_SERVICE_ENDPOINT',
|
|
505
516
|
'COCO_SERVICE_REQUEST_OPTIONS_TIMEOUT',
|
|
506
517
|
'COCO_SERVICE_REQUEST_OPTIONS_MAX_RETRIES',
|
|
@@ -515,6 +526,7 @@ function loadEnvConfig(config) {
|
|
|
515
526
|
if (key === 'COCO_SERVICE_PROVIDER' ||
|
|
516
527
|
key === 'COCO_SERVICE_MODEL' ||
|
|
517
528
|
key === 'OPEN_AI_KEY' ||
|
|
529
|
+
key === 'COCO_SERVICE_BASE_URL' ||
|
|
518
530
|
key === 'COCO_SERVICE_ENDPOINT' ||
|
|
519
531
|
key === 'COCO_SERVICE_REQUEST_OPTIONS_TIMEOUT' ||
|
|
520
532
|
key === 'COCO_SERVICE_REQUEST_OPTIONS_MAX_RETRIES' ||
|
|
@@ -553,6 +565,12 @@ function handleServiceEnvVar(service, key, value) {
|
|
|
553
565
|
};
|
|
554
566
|
}
|
|
555
567
|
break;
|
|
568
|
+
case 'COCO_SERVICE_BASE_URL':
|
|
569
|
+
if (service.provider === 'openai') {
|
|
570
|
+
// Cast to OpenAILLMService to access baseURL property
|
|
571
|
+
service.baseURL = value;
|
|
572
|
+
}
|
|
573
|
+
break;
|
|
556
574
|
case 'COCO_SERVICE_ENDPOINT':
|
|
557
575
|
if (service.provider === 'ollama') {
|
|
558
576
|
service.endpoint = value;
|
|
@@ -681,6 +699,7 @@ function loadGitConfig(config) {
|
|
|
681
699
|
maxParsingAttempts: gitConfigParsed.coco?.serviceMaxParsingAttempts
|
|
682
700
|
? Number(gitConfigParsed.coco.serviceMaxParsingAttempts)
|
|
683
701
|
: undefined,
|
|
702
|
+
baseURL: gitConfigParsed.coco?.serviceBaseURL,
|
|
684
703
|
authentication: {
|
|
685
704
|
type: 'APIKey',
|
|
686
705
|
credentials: {
|
|
@@ -760,6 +779,11 @@ const appendToGitConfig = async (filePath, config) => {
|
|
|
760
779
|
if (service.requestOptions?.maxRetries) {
|
|
761
780
|
contentLines.push(` serviceRequestOptionsMaxRetries = ${service.requestOptions.maxRetries}`);
|
|
762
781
|
}
|
|
782
|
+
// Handle baseURL for OpenAI
|
|
783
|
+
if (service.provider === 'openai' && 'baseURL' in service && service.baseURL) {
|
|
784
|
+
contentLines.push(` serviceBaseURL = ${service.baseURL}`);
|
|
785
|
+
}
|
|
786
|
+
// Handle endpoint for Ollama
|
|
763
787
|
if (service.provider === 'ollama') {
|
|
764
788
|
const ollamaService = service;
|
|
765
789
|
if (ollamaService.endpoint) {
|
|
@@ -950,6 +974,14 @@ const schema$1 = {
|
|
|
950
974
|
"model": {
|
|
951
975
|
"$ref": "#/definitions/LLMModel"
|
|
952
976
|
},
|
|
977
|
+
"baseURL": {
|
|
978
|
+
"type": "string",
|
|
979
|
+
"description": "Custom base URL for OpenAI-compatible APIs (e.g., OpenRouter, Azure OpenAI). If not specified, uses the default OpenAI API endpoint.",
|
|
980
|
+
"examples": [
|
|
981
|
+
"https://openrouter.ai/api/v1",
|
|
982
|
+
"https://your-resource.openai.azure.com"
|
|
983
|
+
]
|
|
984
|
+
},
|
|
953
985
|
"fields": {
|
|
954
986
|
"type": "object",
|
|
955
987
|
"additionalProperties": false,
|
|
@@ -2224,12 +2256,14 @@ function parseServiceConfig(service) {
|
|
|
2224
2256
|
return {
|
|
2225
2257
|
provider: 'openai',
|
|
2226
2258
|
model: service.model,
|
|
2259
|
+
baseURL: service.baseURL,
|
|
2227
2260
|
authentication: {
|
|
2228
2261
|
type: 'APIKey',
|
|
2229
2262
|
credentials: {
|
|
2230
2263
|
apiKey: service.apiKey
|
|
2231
2264
|
}
|
|
2232
|
-
}
|
|
2265
|
+
},
|
|
2266
|
+
fields: service.fields
|
|
2233
2267
|
};
|
|
2234
2268
|
case 'anthropic':
|
|
2235
2269
|
return {
|
|
@@ -2349,6 +2383,59 @@ class Logger {
|
|
|
2349
2383
|
}
|
|
2350
2384
|
}
|
|
2351
2385
|
|
|
2386
|
+
/**
|
|
2387
|
+
* Formats a network error with helpful troubleshooting information
|
|
2388
|
+
*/
|
|
2389
|
+
function formatNetworkError(error, logger) {
|
|
2390
|
+
const endpoint = error.endpoint || 'unknown endpoint';
|
|
2391
|
+
const provider = error.provider || 'LLM service';
|
|
2392
|
+
logger.log('\nFailed to execute command', { color: 'yellow' });
|
|
2393
|
+
logger.log(`\nError: Unable to connect to ${provider}`, { color: 'red' });
|
|
2394
|
+
if (error.endpoint) {
|
|
2395
|
+
logger.log(` Endpoint: ${endpoint}`, { color: 'red' });
|
|
2396
|
+
}
|
|
2397
|
+
logger.log('\nTroubleshooting:', { color: 'cyan' });
|
|
2398
|
+
// Provider-specific troubleshooting
|
|
2399
|
+
if (provider === 'ollama' || endpoint.includes('11434')) {
|
|
2400
|
+
logger.log(' • Is Ollama running? Try: ollama serve', { color: 'white' });
|
|
2401
|
+
logger.log(' • Check if the endpoint is correct in your config', { color: 'white' });
|
|
2402
|
+
logger.log(` • Verify Ollama is accessible: curl ${endpoint}/api/version`, { color: 'white' });
|
|
2403
|
+
}
|
|
2404
|
+
else if (provider === 'openai' || endpoint.includes('openai')) {
|
|
2405
|
+
logger.log(' • Check your internet connection', { color: 'white' });
|
|
2406
|
+
logger.log(' • Verify the API endpoint is accessible', { color: 'white' });
|
|
2407
|
+
logger.log(' • If using a custom baseURL, verify it is correct', { color: 'white' });
|
|
2408
|
+
}
|
|
2409
|
+
else if (provider === 'anthropic') {
|
|
2410
|
+
logger.log(' • Check your internet connection', { color: 'white' });
|
|
2411
|
+
logger.log(' • Verify the Anthropic API is accessible', { color: 'white' });
|
|
2412
|
+
}
|
|
2413
|
+
else {
|
|
2414
|
+
logger.log(' • Check your internet connection', { color: 'white' });
|
|
2415
|
+
logger.log(' • Verify the service endpoint is correct', { color: 'white' });
|
|
2416
|
+
logger.log(' • Ensure the LLM service is running and accessible', { color: 'white' });
|
|
2417
|
+
}
|
|
2418
|
+
logger.verbose(`\nOriginal error: ${error.message}`, { color: 'gray' });
|
|
2419
|
+
}
|
|
2420
|
+
/**
|
|
2421
|
+
* Formats an authentication error with helpful information
|
|
2422
|
+
*/
|
|
2423
|
+
function formatAuthenticationError(error, logger) {
|
|
2424
|
+
logger.log('\nFailed to execute command', { color: 'yellow' });
|
|
2425
|
+
logger.log('\nError: Authentication failed', { color: 'red' });
|
|
2426
|
+
logger.log('\nTroubleshooting:', { color: 'cyan' });
|
|
2427
|
+
logger.log(' • Verify your API key is correct', { color: 'white' });
|
|
2428
|
+
logger.log(' • Check that your API key has not expired', { color: 'white' });
|
|
2429
|
+
logger.log(' • Ensure the API key is set in your environment or config', { color: 'white' });
|
|
2430
|
+
logger.verbose(`\nOriginal error: ${error.message}`, { color: 'gray' });
|
|
2431
|
+
}
|
|
2432
|
+
/**
|
|
2433
|
+
* Formats a generic error
|
|
2434
|
+
*/
|
|
2435
|
+
function formatGenericError(error, logger) {
|
|
2436
|
+
logger.log('\nFailed to execute command', { color: 'yellow' });
|
|
2437
|
+
logger.verbose(`\nError: "${error.message}"`, { color: 'red' });
|
|
2438
|
+
}
|
|
2352
2439
|
function commandExecutor(handler) {
|
|
2353
2440
|
return async (argv) => {
|
|
2354
2441
|
const options = loadConfig(argv);
|
|
@@ -2357,8 +2444,16 @@ function commandExecutor(handler) {
|
|
|
2357
2444
|
await handler(argv, logger);
|
|
2358
2445
|
}
|
|
2359
2446
|
catch (error) {
|
|
2360
|
-
|
|
2361
|
-
|
|
2447
|
+
// Handle specific error types with helpful messages
|
|
2448
|
+
if (error instanceof LangChainNetworkError) {
|
|
2449
|
+
formatNetworkError(error, logger);
|
|
2450
|
+
}
|
|
2451
|
+
else if (error instanceof LangChainAuthenticationError) {
|
|
2452
|
+
formatAuthenticationError(error, logger);
|
|
2453
|
+
}
|
|
2454
|
+
else {
|
|
2455
|
+
formatGenericError(error, logger);
|
|
2456
|
+
}
|
|
2362
2457
|
logger.log('\nThanks for using coco, make it a great day! 👋🤖', { color: 'blue' });
|
|
2363
2458
|
process.exit(0);
|
|
2364
2459
|
}
|
|
@@ -6250,10 +6345,50 @@ async function withRetry(operation, options = {}) {
|
|
|
6250
6345
|
throw new Error(`Operation failed after ${maxAttempts} attempts. Last error: ${lastError.message}`);
|
|
6251
6346
|
}
|
|
6252
6347
|
|
|
6348
|
+
/**
|
|
6349
|
+
* Network error patterns to detect connection failures
|
|
6350
|
+
*/
|
|
6351
|
+
const NETWORK_ERROR_PATTERNS = [
|
|
6352
|
+
'fetch failed',
|
|
6353
|
+
'ECONNREFUSED',
|
|
6354
|
+
'ENOTFOUND',
|
|
6355
|
+
'ETIMEDOUT',
|
|
6356
|
+
'ECONNRESET',
|
|
6357
|
+
'ENETUNREACH',
|
|
6358
|
+
'EHOSTUNREACH',
|
|
6359
|
+
'socket hang up',
|
|
6360
|
+
'network request failed',
|
|
6361
|
+
'Failed to fetch',
|
|
6362
|
+
'getaddrinfo',
|
|
6363
|
+
'connect ECONNREFUSED',
|
|
6364
|
+
];
|
|
6365
|
+
/**
|
|
6366
|
+
* Checks if an error message indicates a network/connection failure
|
|
6367
|
+
*/
|
|
6368
|
+
function isNetworkError(error) {
|
|
6369
|
+
if (!(error instanceof Error))
|
|
6370
|
+
return false;
|
|
6371
|
+
const message = error.message.toLowerCase();
|
|
6372
|
+
const errorCause = error?.cause?.message?.toLowerCase() || '';
|
|
6373
|
+
return NETWORK_ERROR_PATTERNS.some(pattern => message.includes(pattern.toLowerCase()) ||
|
|
6374
|
+
errorCause.includes(pattern.toLowerCase()));
|
|
6375
|
+
}
|
|
6253
6376
|
/**
|
|
6254
6377
|
* Wraps errors with additional context and converts them to LangChain errors
|
|
6255
6378
|
*/
|
|
6256
6379
|
function handleLangChainError(error, context, additionalContext) {
|
|
6380
|
+
// Check for network errors first
|
|
6381
|
+
if (error instanceof Error && isNetworkError(error)) {
|
|
6382
|
+
const endpoint = additionalContext?.endpoint;
|
|
6383
|
+
const provider = additionalContext?.provider;
|
|
6384
|
+
throw new LangChainNetworkError(error.message, endpoint, provider, {
|
|
6385
|
+
originalError: error.name,
|
|
6386
|
+
originalMessage: error.message,
|
|
6387
|
+
stack: error.stack,
|
|
6388
|
+
context,
|
|
6389
|
+
...additionalContext
|
|
6390
|
+
});
|
|
6391
|
+
}
|
|
6257
6392
|
// If it's already a LangChain error, re-throw with additional context
|
|
6258
6393
|
if (error instanceof LangChainError) {
|
|
6259
6394
|
throw new LangChainExecutionError(`${context}: ${error.message}`, {
|
|
@@ -6324,11 +6459,22 @@ function getLlm(provider, model, config) {
|
|
|
6324
6459
|
model,
|
|
6325
6460
|
});
|
|
6326
6461
|
case 'openai':
|
|
6327
|
-
|
|
6462
|
+
const openaiConfig = {
|
|
6328
6463
|
apiKey: apiKey,
|
|
6329
6464
|
model,
|
|
6330
6465
|
temperature: config.service.temperature || 0.2,
|
|
6331
|
-
}
|
|
6466
|
+
};
|
|
6467
|
+
// Add custom base URL if specified (for OpenRouter, Azure OpenAI, etc.)
|
|
6468
|
+
if ('baseURL' in config.service && config.service.baseURL) {
|
|
6469
|
+
openaiConfig.configuration = {
|
|
6470
|
+
baseURL: config.service.baseURL,
|
|
6471
|
+
};
|
|
6472
|
+
}
|
|
6473
|
+
// Merge any additional fields from config
|
|
6474
|
+
if ('fields' in config.service && config.service.fields) {
|
|
6475
|
+
Object.assign(openaiConfig, config.service.fields);
|
|
6476
|
+
}
|
|
6477
|
+
return new openai.ChatOpenAI(openaiConfig);
|
|
6332
6478
|
default:
|
|
6333
6479
|
throw new LangChainConfigurationError(`getLlm: Unsupported provider '${provider}'`, { provider, model, supportedProviders: ['openai', 'anthropic', 'ollama'] });
|
|
6334
6480
|
}
|
|
@@ -7026,21 +7172,55 @@ You must return ONLY valid JSON that matches the schema exactly. Do not include
|
|
|
7026
7172
|
}
|
|
7027
7173
|
}
|
|
7028
7174
|
|
|
7175
|
+
/**
|
|
7176
|
+
* Extracts provider and endpoint info from LLM instance if available
|
|
7177
|
+
*/
|
|
7178
|
+
function extractLlmInfo(llm) {
|
|
7179
|
+
const info = {};
|
|
7180
|
+
// Try to extract provider from class name
|
|
7181
|
+
const className = llm?.constructor?.name || '';
|
|
7182
|
+
if (className.includes('Ollama')) {
|
|
7183
|
+
info.provider = 'ollama';
|
|
7184
|
+
// Try to get baseUrl from ollama instance
|
|
7185
|
+
if ('lc_kwargs' in llm && typeof llm.lc_kwargs === 'object' && llm.lc_kwargs !== null) {
|
|
7186
|
+
const kwargs = llm.lc_kwargs;
|
|
7187
|
+
if (typeof kwargs.baseUrl === 'string') {
|
|
7188
|
+
info.endpoint = kwargs.baseUrl;
|
|
7189
|
+
}
|
|
7190
|
+
}
|
|
7191
|
+
}
|
|
7192
|
+
else if (className.includes('OpenAI')) {
|
|
7193
|
+
info.provider = 'openai';
|
|
7194
|
+
}
|
|
7195
|
+
else if (className.includes('Anthropic')) {
|
|
7196
|
+
info.provider = 'anthropic';
|
|
7197
|
+
}
|
|
7198
|
+
return info;
|
|
7199
|
+
}
|
|
7029
7200
|
/**
|
|
7030
7201
|
* Executes a LangChain pipeline with the provided LLM, prompt, variables, and parser.
|
|
7031
7202
|
* @param params - The execution parameters
|
|
7032
7203
|
* @returns The parsed result from the LLM chain
|
|
7033
7204
|
* @throws LangChainExecutionError if the chain execution fails or returns empty results
|
|
7205
|
+
* @throws LangChainNetworkError if a network/connection error occurs
|
|
7034
7206
|
*/
|
|
7035
|
-
const executeChain = async ({ llm, prompt, variables, parser }) => {
|
|
7207
|
+
const executeChain = async ({ llm, prompt, variables, parser, provider, endpoint, }) => {
|
|
7036
7208
|
validateRequired(llm, 'llm', 'executeChain');
|
|
7037
7209
|
validateRequired(prompt, 'prompt', 'executeChain');
|
|
7038
7210
|
validateRequired(variables, 'variables', 'executeChain');
|
|
7039
7211
|
validateRequired(parser, 'parser', 'executeChain');
|
|
7040
7212
|
// Validate that variables is an object
|
|
7041
7213
|
if (typeof variables !== 'object' || Array.isArray(variables)) {
|
|
7042
|
-
throw new LangChainExecutionError('executeChain: Variables must be a non-array object', {
|
|
7214
|
+
throw new LangChainExecutionError('executeChain: Variables must be a non-array object', {
|
|
7215
|
+
variables,
|
|
7216
|
+
type: typeof variables,
|
|
7217
|
+
isArray: Array.isArray(variables),
|
|
7218
|
+
});
|
|
7043
7219
|
}
|
|
7220
|
+
// Extract LLM info for error reporting if not provided
|
|
7221
|
+
const llmInfo = extractLlmInfo(llm);
|
|
7222
|
+
const effectiveProvider = provider || llmInfo.provider;
|
|
7223
|
+
const effectiveEndpoint = endpoint || llmInfo.endpoint;
|
|
7044
7224
|
try {
|
|
7045
7225
|
const chain = prompt.pipe(llm).pipe(parser);
|
|
7046
7226
|
const result = await chain.invoke(variables);
|
|
@@ -7051,14 +7231,27 @@ const executeChain = async ({ llm, prompt, variables, parser }) => {
|
|
|
7051
7231
|
}
|
|
7052
7232
|
catch (error) {
|
|
7053
7233
|
// Re-throw LangChain errors as-is
|
|
7054
|
-
if (error instanceof LangChainExecutionError) {
|
|
7234
|
+
if (error instanceof LangChainExecutionError || error instanceof LangChainNetworkError) {
|
|
7055
7235
|
throw error;
|
|
7056
7236
|
}
|
|
7237
|
+
// Check for network errors and throw specific error type
|
|
7238
|
+
if (error instanceof Error && isNetworkError(error)) {
|
|
7239
|
+
throw new LangChainNetworkError(error.message, effectiveEndpoint, effectiveProvider, {
|
|
7240
|
+
originalError: error.name,
|
|
7241
|
+
originalMessage: error.message,
|
|
7242
|
+
stack: error.stack,
|
|
7243
|
+
promptInputVariables: prompt.inputVariables,
|
|
7244
|
+
variableKeys: Object.keys(variables),
|
|
7245
|
+
parserType: parser.constructor.name,
|
|
7246
|
+
});
|
|
7247
|
+
}
|
|
7057
7248
|
// Wrap other errors with context
|
|
7058
7249
|
handleLangChainError(error, 'executeChain: Chain execution failed', {
|
|
7059
7250
|
promptInputVariables: prompt.inputVariables,
|
|
7060
7251
|
variableKeys: Object.keys(variables),
|
|
7061
|
-
parserType: parser.constructor.name
|
|
7252
|
+
parserType: parser.constructor.name,
|
|
7253
|
+
provider: effectiveProvider,
|
|
7254
|
+
endpoint: effectiveEndpoint,
|
|
7062
7255
|
});
|
|
7063
7256
|
}
|
|
7064
7257
|
};
|