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 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 & {
@@ -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.26.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
- logger.log('\nFailed to execute command', { color: 'yellow' });
2339
- logger.verbose(`\nError: "${error.message}"`, { color: 'red' });
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
- return new ChatOpenAI({
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', { variables, type: typeof variables, isArray: Array.isArray(variables) });
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.26.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
- logger.log('\nFailed to execute command', { color: 'yellow' });
2361
- logger.verbose(`\nError: "${error.message}"`, { color: 'red' });
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
- return new openai.ChatOpenAI({
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', { variables, type: typeof variables, isArray: Array.isArray(variables) });
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-coco",
3
- "version": "0.26.0",
3
+ "version": "0.27.1",
4
4
  "description": "zero-effort git commits with coco.",
5
5
  "author": "gfargo <ghfargo@gmail.com>",
6
6
  "license": "MIT",