git-coco 0.17.0 → 0.18.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
@@ -11,9 +11,10 @@ import { Color } from 'chalk';
11
11
  import { TiktokenModel as TiktokenModel$1 } from 'tiktoken';
12
12
 
13
13
  type LLMProvider = 'openai' | 'ollama' | 'anthropic';
14
+ type OpenAIModel = TiktokenModel | 'gpt-4o-mini' | 'gpt-4o' | 'gpt-4.1' | 'gpt-4.1-mini' | 'gpt-4.1-nano';
14
15
  type AnthropicModel = 'claude-sonnet-4-0' | 'claude-3-7-sonnet-latest' | 'claude-3-5-haiku-latest' | 'claude-3-5-sonnet-latest' | 'claude-3-5-sonnet-20241022' | 'claude-3-5-sonnet-20240620' | 'claude-3-opus-20240229' | 'claude-3-sonnet-20240229' | 'claude-3-haiku-20240307';
15
16
  type OllamaModel = 'deepseek-r1:1.5b' | 'deepseek-r1:8b' | 'deepseek-r1:32b' | 'codegemma:2b' | 'codegemma:7b-code' | 'codegemma' | 'codellama:13b' | 'codellama:34b' | 'codellama:70b' | 'codellama:7b' | 'codellama:instruct' | 'codellama:latest' | 'codellama' | 'gemma:2b' | 'gemma:7b' | 'gemma:latest' | 'gemma' | 'llama2:13b' | 'llama2:70b' | 'llama2:chat' | 'llama2:latest' | 'llama2:text' | 'llama2' | 'llama3:70b-text' | 'llama3:70b' | 'llama3:latest' | 'llama3:text' | 'llama3.1:70b' | 'llama3.1:8b' | 'llama3.1:latest' | 'llama3.2' | 'llama3.2:latest' | 'llama3.2:1b' | 'llama3.2:3b' | 'llama3' | 'llava-llama3:latest' | 'dolphin-llama3:latest' | 'dolphin-llama3:8b' | 'dolphin-llama3:70b' | 'mistral:7b' | 'mistral:latest' | 'mistral:text' | 'mistral' | 'phi3:14b' | 'phi3:3.8b' | 'phi3:instruct' | 'phi3:medium-128k' | 'phi3:medium-4k' | 'phi3:medium' | 'phi3' | 'qwen2:0.5b' | 'qwen2:1.5b' | 'qwen2:72b-text' | 'qwen2:72b' | 'qwen2' | 'qwen2.5-coder:latest' | 'qwen2.5-coder:0.5b' | 'qwen2.5-coder:1.5b' | 'qwen2.5-coder:3b' | 'qwen2.5-coder:7b' | 'qwen2.5-coder:14b' | 'qwen2.5-coder:32b';
16
- type LLMModel = TiktokenModel | OllamaModel | AnthropicModel;
17
+ type LLMModel = OpenAIModel | OllamaModel | AnthropicModel;
17
18
  type BaseLLMService = {
18
19
  provider: LLMProvider;
19
20
  model: LLMModel;
@@ -68,7 +69,7 @@ type OpenAIFields = Partial<OpenAIInput> & BaseLLMParams;
68
69
  type OllamaFields = Partial<OllamaInput> & BaseLLMParams;
69
70
  type OpenAILLMService = BaseLLMService & {
70
71
  provider: 'openai';
71
- model: TiktokenModel;
72
+ model: OpenAIModel;
72
73
  fields?: OpenAIFields;
73
74
  };
74
75
  type OllamaLLMService = BaseLLMService & {
@@ -46,7 +46,7 @@ import * as readline from 'readline';
46
46
  /**
47
47
  * Current build version from package.json
48
48
  */
49
- const BUILD_VERSION = "0.17.0";
49
+ const BUILD_VERSION = "0.18.1";
50
50
 
51
51
  const isInteractive = (config) => {
52
52
  return config?.mode === 'interactive' || !!config?.interactive;
@@ -345,7 +345,7 @@ function getDefaultServiceApiKey(config) {
345
345
  }
346
346
  const DEFAULT_OPENAI_LLM_SERVICE = {
347
347
  provider: 'openai',
348
- model: 'gpt-4o',
348
+ model: 'gpt-4o-mini',
349
349
  tokenLimit: 2024,
350
350
  temperature: 0.32,
351
351
  authentication: {
@@ -454,14 +454,29 @@ const CONFIG_KEYS = Object.keys({
454
454
  **/
455
455
  function loadEnvConfig(config) {
456
456
  const envConfig = {};
457
- const envKeys = [...CONFIG_KEYS, 'COCO_SERVICE_PROVIDER', 'COCO_SERVICE_MODEL', 'OPEN_AI_KEY'];
457
+ const envKeys = [
458
+ ...CONFIG_KEYS,
459
+ 'COCO_SERVICE_PROVIDER',
460
+ 'COCO_SERVICE_MODEL',
461
+ 'OPEN_AI_KEY',
462
+ 'COCO_SERVICE_ENDPOINT',
463
+ 'COCO_SERVICE_REQUEST_OPTIONS_TIMEOUT',
464
+ 'COCO_SERVICE_REQUEST_OPTIONS_MAX_RETRIES',
465
+ 'COCO_SERVICE_FIELDS',
466
+ ];
458
467
  envKeys.forEach((key) => {
459
468
  const envVarName = toEnvVarName(key);
460
469
  const envValue = parseEnvValue(key, process.env[envVarName]);
461
470
  if (envValue === undefined) {
462
471
  return;
463
472
  }
464
- if (key === 'COCO_SERVICE_PROVIDER' || key === 'COCO_SERVICE_MODEL' || key === 'OPEN_AI_KEY') {
473
+ if (key === 'COCO_SERVICE_PROVIDER' ||
474
+ key === 'COCO_SERVICE_MODEL' ||
475
+ key === 'OPEN_AI_KEY' ||
476
+ key === 'COCO_SERVICE_ENDPOINT' ||
477
+ key === 'COCO_SERVICE_REQUEST_OPTIONS_TIMEOUT' ||
478
+ key === 'COCO_SERVICE_REQUEST_OPTIONS_MAX_RETRIES' ||
479
+ key === 'COCO_SERVICE_FIELDS') {
465
480
  // NOTE: We want to ensure that the service object is always defined
466
481
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
467
482
  // @ts-ignore
@@ -488,9 +503,28 @@ function handleServiceEnvVar(service, key, value) {
488
503
  break;
489
504
  case 'OPEN_AI_KEY':
490
505
  if (service.provider === 'openai') {
491
- service.fields = { apiKey: value };
506
+ service.authentication = {
507
+ type: 'APIKey',
508
+ credentials: {
509
+ apiKey: value,
510
+ },
511
+ };
492
512
  }
493
513
  break;
514
+ case 'COCO_SERVICE_ENDPOINT':
515
+ if (service.provider === 'ollama') {
516
+ service.endpoint = value;
517
+ }
518
+ break;
519
+ case 'COCO_SERVICE_REQUEST_OPTIONS_TIMEOUT':
520
+ service.requestOptions = { ...service.requestOptions, timeout: value };
521
+ break;
522
+ case 'COCO_SERVICE_REQUEST_OPTIONS_MAX_RETRIES':
523
+ service.requestOptions = { ...service.requestOptions, maxRetries: value };
524
+ break;
525
+ case 'COCO_SERVICE_FIELDS':
526
+ service.fields = value;
527
+ break;
494
528
  }
495
529
  }
496
530
  function parseEnvValue(key, value) {
@@ -509,6 +543,9 @@ function parseEnvValue(key, value) {
509
543
  // Handle number values
510
544
  case typeof value === 'string' && !isNaN(Number(value)):
511
545
  return Number(value);
546
+ // Handle JSON strings
547
+ case typeof value === 'string' && value.startsWith('{'):
548
+ return JSON.parse(value);
512
549
  default:
513
550
  return value;
514
551
  }
@@ -522,32 +559,40 @@ function toEnvVarName(key) {
522
559
  }
523
560
  return `COCO_${key.replace(/([A-Z])/g, '_$1').toLocaleUpperCase()}`;
524
561
  }
525
- function formatEnvValue(value) {
526
- if (typeof value === 'number') {
527
- return `${value}`;
528
- }
529
- else if (Array.isArray(value)) {
530
- return `${value.join(',')}`;
531
- }
532
- else if (typeof value === 'string') {
533
- // Escape newlines and tabs in strings
534
- return `${value.replace(/\n/g, '\\n').replace(/\t/g, '\\t')}`;
562
+ const flattenObject = (obj, prefix = '') => {
563
+ let flattened = {};
564
+ for (const key in obj) {
565
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
566
+ const propName = prefix ? `${prefix}_${key}` : key;
567
+ const value = obj[key];
568
+ // Skip undefined or null values
569
+ if (value === undefined || value === null) {
570
+ continue;
571
+ }
572
+ if (typeof value === 'object' && !Array.isArray(value)) {
573
+ // Handle nested objects, but specifically handle 'fields' as JSON string
574
+ if (key === 'fields') {
575
+ flattened[propName.toUpperCase()] = JSON.stringify(value);
576
+ }
577
+ else {
578
+ flattened = { ...flattened, ...flattenObject(value, propName) };
579
+ }
580
+ }
581
+ else {
582
+ // For primitive types (string, number, boolean, symbol, bigint) and arrays
583
+ flattened[propName.toUpperCase()] = String(value);
584
+ }
585
+ }
535
586
  }
536
- return `${value}`;
537
- }
587
+ return flattened;
588
+ };
538
589
  const appendToEnvFile = async (filePath, config) => {
539
590
  const getNewContent = async () => {
540
- return Object.entries(config)
591
+ const flattenedConfig = flattenObject(config);
592
+ return Object.entries(flattenedConfig)
541
593
  .map(([key, value]) => {
542
- if (key === 'service') {
543
- const service = value;
544
- return `${service.provider ? `COCO_SERVICE_PROVIDER=${service.provider}` : ''}\n${service.model ? `COCO_SERVICE_MODEL=${service.model}` : ''}\n${service.authentication.type === 'APIKey'
545
- ? `OPEN_AI_KEY=${service.authentication.credentials.apiKey}`
546
- : ''}`;
547
- }
548
594
  const envVarName = toEnvVarName(key);
549
- const envValue = formatEnvValue(value);
550
- return `${envVarName}=${envValue}`;
595
+ return `${envVarName}=${value}`;
551
596
  })
552
597
  .join('\n');
553
598
  };
@@ -571,22 +616,31 @@ function loadGitConfig(config) {
571
616
  if (fs.existsSync(gitConfigPath)) {
572
617
  const gitConfigRaw = fs.readFileSync(gitConfigPath, 'utf-8');
573
618
  const gitConfigParsed = ini.parse(gitConfigRaw);
574
- const gitConfigServiceObject = gitConfigParsed.coco?.service;
575
619
  let service = config.service;
576
- if (gitConfigServiceObject) {
577
- const gitServiceConfig = JSON.parse(gitConfigServiceObject);
578
- service = gitServiceConfig || config?.service;
620
+ if (gitConfigParsed.coco) {
621
+ service = {
622
+ provider: gitConfigParsed.coco?.serviceProvider,
623
+ model: gitConfigParsed.coco?.serviceModel,
624
+ authentication: {
625
+ type: 'APIKey',
626
+ credentials: {
627
+ apiKey: gitConfigParsed.coco?.serviceApiKey,
628
+ },
629
+ },
630
+ requestOptions: {
631
+ timeout: Number(gitConfigParsed.coco?.serviceRequestOptionsTimeout),
632
+ maxRetries: Number(gitConfigParsed.coco?.serviceRequestOptionsMaxRetries),
633
+ },
634
+ endpoint: gitConfigParsed.coco?.serviceEndpoint,
635
+ fields: gitConfigParsed.coco?.serviceFields
636
+ ? JSON.parse(gitConfigParsed.coco?.serviceFields)
637
+ : undefined,
638
+ };
579
639
  }
580
640
  config = {
581
641
  ...config,
642
+ ...gitConfigParsed.coco,
582
643
  service: service,
583
- prompt: gitConfigParsed.coco?.prompt || config.prompt,
584
- mode: gitConfigParsed.coco?.mode || config.mode,
585
- summarizePrompt: gitConfigParsed.coco?.summarizePrompt || config.summarizePrompt,
586
- ignoredFiles: gitConfigParsed.coco?.ignoredFiles || config.ignoredFiles,
587
- ignoredExtensions: gitConfigParsed.coco?.ignoredExtensions || config.ignoredExtensions,
588
- defaultBranch: gitConfigParsed.coco?.defaultBranch || config.defaultBranch,
589
- verbose: gitConfigParsed.coco?.verbose || config.verbose,
590
644
  };
591
645
  }
592
646
  return removeUndefined(config);
@@ -606,9 +660,28 @@ const appendToGitConfig = async (filePath, config) => {
606
660
  const contentLines = [header];
607
661
  for (const key in config) {
608
662
  const value = config[key];
609
- if (typeof value === 'object') {
610
- // Serialize object to JSON string
611
- contentLines.push(`\t${key} = ${JSON.stringify(value)}`);
663
+ if (key === 'service') {
664
+ const service = value;
665
+ contentLines.push(` serviceProvider = ${service.provider}`);
666
+ contentLines.push(` serviceModel = ${service.model}`);
667
+ if (service.authentication.type === 'APIKey') {
668
+ contentLines.push(` serviceApiKey = ${service.authentication.credentials.apiKey}`);
669
+ }
670
+ if (service.requestOptions?.timeout) {
671
+ contentLines.push(` serviceRequestOptionsTimeout = ${service.requestOptions.timeout}`);
672
+ }
673
+ if (service.requestOptions?.maxRetries) {
674
+ contentLines.push(` serviceRequestOptionsMaxRetries = ${service.requestOptions.maxRetries}`);
675
+ }
676
+ if (service.provider === 'ollama') {
677
+ const ollamaService = service;
678
+ if (ollamaService.endpoint) {
679
+ contentLines.push(` serviceEndpoint = ${ollamaService.endpoint}`);
680
+ }
681
+ }
682
+ if (service.fields) {
683
+ contentLines.push(` serviceFields = ${JSON.stringify(service.fields)}`);
684
+ }
612
685
  }
613
686
  else if (typeof value === 'string' && value.includes('\n')) {
614
687
  // Wrap strings with new lines in quotes
@@ -1062,65 +1135,7 @@ const schema$1 = {
1062
1135
  "LLMModel": {
1063
1136
  "anyOf": [
1064
1137
  {
1065
- "type": "string",
1066
- "enum": [
1067
- "davinci-002",
1068
- "babbage-002",
1069
- "text-davinci-003",
1070
- "text-davinci-002",
1071
- "text-davinci-001",
1072
- "text-curie-001",
1073
- "text-babbage-001",
1074
- "text-ada-001",
1075
- "davinci",
1076
- "curie",
1077
- "babbage",
1078
- "ada",
1079
- "code-davinci-002",
1080
- "code-davinci-001",
1081
- "code-cushman-002",
1082
- "code-cushman-001",
1083
- "davinci-codex",
1084
- "cushman-codex",
1085
- "text-davinci-edit-001",
1086
- "code-davinci-edit-001",
1087
- "text-embedding-ada-002",
1088
- "text-similarity-davinci-001",
1089
- "text-similarity-curie-001",
1090
- "text-similarity-babbage-001",
1091
- "text-similarity-ada-001",
1092
- "text-search-davinci-doc-001",
1093
- "text-search-curie-doc-001",
1094
- "text-search-babbage-doc-001",
1095
- "text-search-ada-doc-001",
1096
- "code-search-babbage-code-001",
1097
- "code-search-ada-code-001",
1098
- "gpt2",
1099
- "gpt-3.5-turbo",
1100
- "gpt-35-turbo",
1101
- "gpt-3.5-turbo-0301",
1102
- "gpt-3.5-turbo-0613",
1103
- "gpt-3.5-turbo-1106",
1104
- "gpt-3.5-turbo-0125",
1105
- "gpt-3.5-turbo-16k",
1106
- "gpt-3.5-turbo-16k-0613",
1107
- "gpt-3.5-turbo-instruct",
1108
- "gpt-3.5-turbo-instruct-0914",
1109
- "gpt-4",
1110
- "gpt-4-0314",
1111
- "gpt-4-0613",
1112
- "gpt-4-32k",
1113
- "gpt-4-32k-0314",
1114
- "gpt-4-32k-0613",
1115
- "gpt-4-turbo",
1116
- "gpt-4-turbo-2024-04-09",
1117
- "gpt-4-turbo-preview",
1118
- "gpt-4-1106-preview",
1119
- "gpt-4-0125-preview",
1120
- "gpt-4-vision-preview",
1121
- "gpt-4o",
1122
- "gpt-4o-2024-05-13"
1123
- ]
1138
+ "$ref": "#/definitions/OpenAIModel"
1124
1139
  },
1125
1140
  {
1126
1141
  "$ref": "#/definitions/OllamaModel"
@@ -1130,6 +1145,71 @@ const schema$1 = {
1130
1145
  }
1131
1146
  ]
1132
1147
  },
1148
+ "OpenAIModel": {
1149
+ "type": "string",
1150
+ "enum": [
1151
+ "davinci-002",
1152
+ "babbage-002",
1153
+ "text-davinci-003",
1154
+ "text-davinci-002",
1155
+ "text-davinci-001",
1156
+ "text-curie-001",
1157
+ "text-babbage-001",
1158
+ "text-ada-001",
1159
+ "davinci",
1160
+ "curie",
1161
+ "babbage",
1162
+ "ada",
1163
+ "code-davinci-002",
1164
+ "code-davinci-001",
1165
+ "code-cushman-002",
1166
+ "code-cushman-001",
1167
+ "davinci-codex",
1168
+ "cushman-codex",
1169
+ "text-davinci-edit-001",
1170
+ "code-davinci-edit-001",
1171
+ "text-embedding-ada-002",
1172
+ "text-similarity-davinci-001",
1173
+ "text-similarity-curie-001",
1174
+ "text-similarity-babbage-001",
1175
+ "text-similarity-ada-001",
1176
+ "text-search-davinci-doc-001",
1177
+ "text-search-curie-doc-001",
1178
+ "text-search-babbage-doc-001",
1179
+ "text-search-ada-doc-001",
1180
+ "code-search-babbage-code-001",
1181
+ "code-search-ada-code-001",
1182
+ "gpt2",
1183
+ "gpt-3.5-turbo",
1184
+ "gpt-35-turbo",
1185
+ "gpt-3.5-turbo-0301",
1186
+ "gpt-3.5-turbo-0613",
1187
+ "gpt-3.5-turbo-1106",
1188
+ "gpt-3.5-turbo-0125",
1189
+ "gpt-3.5-turbo-16k",
1190
+ "gpt-3.5-turbo-16k-0613",
1191
+ "gpt-3.5-turbo-instruct",
1192
+ "gpt-3.5-turbo-instruct-0914",
1193
+ "gpt-4",
1194
+ "gpt-4-0314",
1195
+ "gpt-4-0613",
1196
+ "gpt-4-32k",
1197
+ "gpt-4-32k-0314",
1198
+ "gpt-4-32k-0613",
1199
+ "gpt-4-turbo",
1200
+ "gpt-4-turbo-2024-04-09",
1201
+ "gpt-4-turbo-preview",
1202
+ "gpt-4-1106-preview",
1203
+ "gpt-4-0125-preview",
1204
+ "gpt-4-vision-preview",
1205
+ "gpt-4o",
1206
+ "gpt-4o-2024-05-13",
1207
+ "gpt-4o-mini",
1208
+ "gpt-4.1",
1209
+ "gpt-4.1-mini",
1210
+ "gpt-4.1-nano"
1211
+ ]
1212
+ },
1133
1213
  "OllamaModel": {
1134
1214
  "type": "string",
1135
1215
  "enum": [
@@ -6046,7 +6126,6 @@ function getPrompt({ template, variables, fallback }) {
6046
6126
  * @throws LangChainExecutionError if the chain execution fails or returns empty results
6047
6127
  */
6048
6128
  const executeChain = async ({ llm, prompt, variables, parser }) => {
6049
- // Validate all required parameters
6050
6129
  validateRequired(llm, 'llm', 'executeChain');
6051
6130
  validateRequired(prompt, 'prompt', 'executeChain');
6052
6131
  validateRequired(variables, 'variables', 'executeChain');
@@ -6058,7 +6137,6 @@ const executeChain = async ({ llm, prompt, variables, parser }) => {
6058
6137
  try {
6059
6138
  const chain = prompt.pipe(llm).pipe(parser);
6060
6139
  const result = await chain.invoke(variables);
6061
- console.debug('LLMChain call result:', result);
6062
6140
  if (result === null || result === undefined) {
6063
6141
  throw new LangChainExecutionError('executeChain: Chain execution returned null or undefined result', { variables, promptInputVariables: prompt.inputVariables });
6064
6142
  }
@@ -7406,7 +7484,6 @@ class OutputFixingParser extends BaseOutputParser {
7406
7484
  * @throws LangChainExecutionError if parser creation fails
7407
7485
  */
7408
7486
  function createSchemaParser(schema, llm, options = {}) {
7409
- // Validate required parameters
7410
7487
  validateRequired(schema, 'schema', 'createSchemaParser');
7411
7488
  validateRequired(llm, 'llm', 'createSchemaParser');
7412
7489
  validateRequired(options, 'options', 'createSchemaParser');
@@ -7466,7 +7543,6 @@ You must return ONLY valid JSON that matches the schema exactly. Do not include
7466
7543
  async function executeChainWithSchema(schema, llm, prompt, variables, options = {}) {
7467
7544
  const { retryOptions = { maxAttempts: 3 }, fallbackParser, onFallback, ...parserOptions } = options;
7468
7545
  const parser = createSchemaParser(schema, llm, parserOptions);
7469
- // Define the operation to retry
7470
7546
  const operation = async () => {
7471
7547
  const result = await executeChain({
7472
7548
  llm,
@@ -7477,16 +7553,13 @@ async function executeChainWithSchema(schema, llm, prompt, variables, options =
7477
7553
  return result;
7478
7554
  };
7479
7555
  try {
7480
- // Use the general retry utility
7481
7556
  return await withRetry(operation, retryOptions);
7482
7557
  }
7483
7558
  catch (error) {
7484
- // If all retries failed and we have a fallback parser, use it
7485
7559
  if (fallbackParser) {
7486
7560
  if (onFallback) {
7487
7561
  onFallback();
7488
7562
  }
7489
- // Generate without structured parsing as fallback
7490
7563
  const fallbackResult = await executeChain({
7491
7564
  llm,
7492
7565
  prompt,
@@ -10870,6 +10943,11 @@ const handler$3 = async (argv, logger) => {
10870
10943
  else {
10871
10944
  logger.setConfig({ silent: true });
10872
10945
  }
10946
+ if (config.service.provider === 'ollama') {
10947
+ logger.verbose('⚠️ Ollama models may not strictly adhere to the output format instructions.', {
10948
+ color: 'yellow',
10949
+ });
10950
+ }
10873
10951
  async function factory() {
10874
10952
  const changes = await getChanges({
10875
10953
  git,
@@ -10925,11 +11003,6 @@ const handler$3 = async (argv, logger) => {
10925
11003
  variables: promptTemplate.inputVariables,
10926
11004
  fallback: promptTemplate,
10927
11005
  });
10928
- if (config.service.provider === 'ollama') {
10929
- logger.log('Note: Ollama models may not strictly adhere to the output format instructions.', {
10930
- color: 'yellow',
10931
- });
10932
- }
10933
11006
  // Get additional context if provided
10934
11007
  let additional_context = '';
10935
11008
  if (argv.additional) {
@@ -10970,7 +11043,7 @@ const handler$3 = async (argv, logger) => {
10970
11043
  maxAttempts,
10971
11044
  onRetry: (attempt, error) => {
10972
11045
  logger.verbose(`Failed to parse commit message (attempt ${attempt}/${maxAttempts}): ${error.message}`, { color: 'yellow' });
10973
- }
11046
+ },
10974
11047
  },
10975
11048
  fallbackParser: (text) => ({
10976
11049
  title: text.split('\n')[0] || 'Auto-generated commit',
@@ -11191,11 +11264,22 @@ async function getProjectConfigFilePath(configFileName) {
11191
11264
  }
11192
11265
 
11193
11266
  const OPEN_AI_MODELS = [
11267
+ 'gpt-4.5',
11194
11268
  'gpt-4o',
11269
+ 'gpt-4o-mini',
11270
+ 'gpt-4.1',
11271
+ 'gpt-4.1',
11272
+ 'gpt-4.1-mini',
11273
+ 'gpt-4.1-nano',
11195
11274
  'gpt-4-32k',
11196
11275
  'gpt-4-turbo',
11197
11276
  'gpt-4',
11198
11277
  'gpt-3.5-turbo',
11278
+ 'o1',
11279
+ 'o1-mini',
11280
+ '03-mini',
11281
+ '03',
11282
+ 'o4-mini',
11199
11283
  ];
11200
11284
  const ANTHROPIC_MODELS = [
11201
11285
  'claude-sonnet-4-0',
@@ -11340,6 +11424,32 @@ const questions = {
11340
11424
  });
11341
11425
  return parseFloat(temperature);
11342
11426
  },
11427
+ inputOllamaEndpoint: async () => {
11428
+ return await input({
11429
+ message: 'Ollama endpoint (e.g., http://localhost:11434):',
11430
+ default: 'http://localhost:11434',
11431
+ });
11432
+ },
11433
+ inputRequestTimeout: async () => {
11434
+ const timeout = await input({
11435
+ message: 'Request timeout in milliseconds:',
11436
+ default: '30000',
11437
+ });
11438
+ return parseInt(timeout);
11439
+ },
11440
+ inputRequestMaxRetries: async () => {
11441
+ const maxRetries = await input({
11442
+ message: 'Maximum number of request retries:',
11443
+ default: '3',
11444
+ });
11445
+ return parseInt(maxRetries);
11446
+ },
11447
+ inputServiceFields: async () => {
11448
+ return await editor({
11449
+ message: 'Enter additional service fields as a JSON string (optional):',
11450
+ default: '{}',
11451
+ });
11452
+ },
11343
11453
  selectDefaultGitBranch: async () => (await input({
11344
11454
  message: 'default branch for the repository:',
11345
11455
  default: 'main',
@@ -11433,6 +11543,29 @@ const handler$2 = async (argv, logger) => {
11433
11543
  tokenLimit: await questions.inputTokenLimit(),
11434
11544
  };
11435
11545
  config.verbose = await questions.enableVerboseMode();
11546
+ if (llmProvider === 'ollama') {
11547
+ config.service.endpoint = await questions.inputOllamaEndpoint();
11548
+ }
11549
+ config.service.requestOptions = {
11550
+ timeout: await questions.inputRequestTimeout(),
11551
+ maxRetries: await questions.inputRequestMaxRetries(),
11552
+ };
11553
+ const promptForServiceFields = await confirm({
11554
+ message: 'would you like to configure additional service fields (advanced)?',
11555
+ default: false,
11556
+ });
11557
+ if (promptForServiceFields) {
11558
+ const fieldsJson = await questions.inputServiceFields();
11559
+ try {
11560
+ config.service.fields = JSON.parse(fieldsJson);
11561
+ }
11562
+ catch (e) {
11563
+ logger.log('Invalid JSON for service fields. Skipping.', { color: 'red' });
11564
+ logger.verbose(`Error parsing service fields: ${e.message}`, {
11565
+ color: 'red',
11566
+ });
11567
+ }
11568
+ }
11436
11569
  const promptForIgnores = await confirm({
11437
11570
  message: 'would you like to configure ignored files and extensions?',
11438
11571
  default: false,
package/dist/index.js CHANGED
@@ -68,7 +68,7 @@ var readline__namespace = /*#__PURE__*/_interopNamespaceDefault(readline$1);
68
68
  /**
69
69
  * Current build version from package.json
70
70
  */
71
- const BUILD_VERSION = "0.17.0";
71
+ const BUILD_VERSION = "0.18.1";
72
72
 
73
73
  const isInteractive = (config) => {
74
74
  return config?.mode === 'interactive' || !!config?.interactive;
@@ -367,7 +367,7 @@ function getDefaultServiceApiKey(config) {
367
367
  }
368
368
  const DEFAULT_OPENAI_LLM_SERVICE = {
369
369
  provider: 'openai',
370
- model: 'gpt-4o',
370
+ model: 'gpt-4o-mini',
371
371
  tokenLimit: 2024,
372
372
  temperature: 0.32,
373
373
  authentication: {
@@ -476,14 +476,29 @@ const CONFIG_KEYS = Object.keys({
476
476
  **/
477
477
  function loadEnvConfig(config) {
478
478
  const envConfig = {};
479
- const envKeys = [...CONFIG_KEYS, 'COCO_SERVICE_PROVIDER', 'COCO_SERVICE_MODEL', 'OPEN_AI_KEY'];
479
+ const envKeys = [
480
+ ...CONFIG_KEYS,
481
+ 'COCO_SERVICE_PROVIDER',
482
+ 'COCO_SERVICE_MODEL',
483
+ 'OPEN_AI_KEY',
484
+ 'COCO_SERVICE_ENDPOINT',
485
+ 'COCO_SERVICE_REQUEST_OPTIONS_TIMEOUT',
486
+ 'COCO_SERVICE_REQUEST_OPTIONS_MAX_RETRIES',
487
+ 'COCO_SERVICE_FIELDS',
488
+ ];
480
489
  envKeys.forEach((key) => {
481
490
  const envVarName = toEnvVarName(key);
482
491
  const envValue = parseEnvValue(key, process.env[envVarName]);
483
492
  if (envValue === undefined) {
484
493
  return;
485
494
  }
486
- if (key === 'COCO_SERVICE_PROVIDER' || key === 'COCO_SERVICE_MODEL' || key === 'OPEN_AI_KEY') {
495
+ if (key === 'COCO_SERVICE_PROVIDER' ||
496
+ key === 'COCO_SERVICE_MODEL' ||
497
+ key === 'OPEN_AI_KEY' ||
498
+ key === 'COCO_SERVICE_ENDPOINT' ||
499
+ key === 'COCO_SERVICE_REQUEST_OPTIONS_TIMEOUT' ||
500
+ key === 'COCO_SERVICE_REQUEST_OPTIONS_MAX_RETRIES' ||
501
+ key === 'COCO_SERVICE_FIELDS') {
487
502
  // NOTE: We want to ensure that the service object is always defined
488
503
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
489
504
  // @ts-ignore
@@ -510,9 +525,28 @@ function handleServiceEnvVar(service, key, value) {
510
525
  break;
511
526
  case 'OPEN_AI_KEY':
512
527
  if (service.provider === 'openai') {
513
- service.fields = { apiKey: value };
528
+ service.authentication = {
529
+ type: 'APIKey',
530
+ credentials: {
531
+ apiKey: value,
532
+ },
533
+ };
514
534
  }
515
535
  break;
536
+ case 'COCO_SERVICE_ENDPOINT':
537
+ if (service.provider === 'ollama') {
538
+ service.endpoint = value;
539
+ }
540
+ break;
541
+ case 'COCO_SERVICE_REQUEST_OPTIONS_TIMEOUT':
542
+ service.requestOptions = { ...service.requestOptions, timeout: value };
543
+ break;
544
+ case 'COCO_SERVICE_REQUEST_OPTIONS_MAX_RETRIES':
545
+ service.requestOptions = { ...service.requestOptions, maxRetries: value };
546
+ break;
547
+ case 'COCO_SERVICE_FIELDS':
548
+ service.fields = value;
549
+ break;
516
550
  }
517
551
  }
518
552
  function parseEnvValue(key, value) {
@@ -531,6 +565,9 @@ function parseEnvValue(key, value) {
531
565
  // Handle number values
532
566
  case typeof value === 'string' && !isNaN(Number(value)):
533
567
  return Number(value);
568
+ // Handle JSON strings
569
+ case typeof value === 'string' && value.startsWith('{'):
570
+ return JSON.parse(value);
534
571
  default:
535
572
  return value;
536
573
  }
@@ -544,32 +581,40 @@ function toEnvVarName(key) {
544
581
  }
545
582
  return `COCO_${key.replace(/([A-Z])/g, '_$1').toLocaleUpperCase()}`;
546
583
  }
547
- function formatEnvValue(value) {
548
- if (typeof value === 'number') {
549
- return `${value}`;
550
- }
551
- else if (Array.isArray(value)) {
552
- return `${value.join(',')}`;
553
- }
554
- else if (typeof value === 'string') {
555
- // Escape newlines and tabs in strings
556
- return `${value.replace(/\n/g, '\\n').replace(/\t/g, '\\t')}`;
584
+ const flattenObject = (obj, prefix = '') => {
585
+ let flattened = {};
586
+ for (const key in obj) {
587
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
588
+ const propName = prefix ? `${prefix}_${key}` : key;
589
+ const value = obj[key];
590
+ // Skip undefined or null values
591
+ if (value === undefined || value === null) {
592
+ continue;
593
+ }
594
+ if (typeof value === 'object' && !Array.isArray(value)) {
595
+ // Handle nested objects, but specifically handle 'fields' as JSON string
596
+ if (key === 'fields') {
597
+ flattened[propName.toUpperCase()] = JSON.stringify(value);
598
+ }
599
+ else {
600
+ flattened = { ...flattened, ...flattenObject(value, propName) };
601
+ }
602
+ }
603
+ else {
604
+ // For primitive types (string, number, boolean, symbol, bigint) and arrays
605
+ flattened[propName.toUpperCase()] = String(value);
606
+ }
607
+ }
557
608
  }
558
- return `${value}`;
559
- }
609
+ return flattened;
610
+ };
560
611
  const appendToEnvFile = async (filePath, config) => {
561
612
  const getNewContent = async () => {
562
- return Object.entries(config)
613
+ const flattenedConfig = flattenObject(config);
614
+ return Object.entries(flattenedConfig)
563
615
  .map(([key, value]) => {
564
- if (key === 'service') {
565
- const service = value;
566
- return `${service.provider ? `COCO_SERVICE_PROVIDER=${service.provider}` : ''}\n${service.model ? `COCO_SERVICE_MODEL=${service.model}` : ''}\n${service.authentication.type === 'APIKey'
567
- ? `OPEN_AI_KEY=${service.authentication.credentials.apiKey}`
568
- : ''}`;
569
- }
570
616
  const envVarName = toEnvVarName(key);
571
- const envValue = formatEnvValue(value);
572
- return `${envVarName}=${envValue}`;
617
+ return `${envVarName}=${value}`;
573
618
  })
574
619
  .join('\n');
575
620
  };
@@ -593,22 +638,31 @@ function loadGitConfig(config) {
593
638
  if (fs__namespace.existsSync(gitConfigPath)) {
594
639
  const gitConfigRaw = fs__namespace.readFileSync(gitConfigPath, 'utf-8');
595
640
  const gitConfigParsed = ini__namespace.parse(gitConfigRaw);
596
- const gitConfigServiceObject = gitConfigParsed.coco?.service;
597
641
  let service = config.service;
598
- if (gitConfigServiceObject) {
599
- const gitServiceConfig = JSON.parse(gitConfigServiceObject);
600
- service = gitServiceConfig || config?.service;
642
+ if (gitConfigParsed.coco) {
643
+ service = {
644
+ provider: gitConfigParsed.coco?.serviceProvider,
645
+ model: gitConfigParsed.coco?.serviceModel,
646
+ authentication: {
647
+ type: 'APIKey',
648
+ credentials: {
649
+ apiKey: gitConfigParsed.coco?.serviceApiKey,
650
+ },
651
+ },
652
+ requestOptions: {
653
+ timeout: Number(gitConfigParsed.coco?.serviceRequestOptionsTimeout),
654
+ maxRetries: Number(gitConfigParsed.coco?.serviceRequestOptionsMaxRetries),
655
+ },
656
+ endpoint: gitConfigParsed.coco?.serviceEndpoint,
657
+ fields: gitConfigParsed.coco?.serviceFields
658
+ ? JSON.parse(gitConfigParsed.coco?.serviceFields)
659
+ : undefined,
660
+ };
601
661
  }
602
662
  config = {
603
663
  ...config,
664
+ ...gitConfigParsed.coco,
604
665
  service: service,
605
- prompt: gitConfigParsed.coco?.prompt || config.prompt,
606
- mode: gitConfigParsed.coco?.mode || config.mode,
607
- summarizePrompt: gitConfigParsed.coco?.summarizePrompt || config.summarizePrompt,
608
- ignoredFiles: gitConfigParsed.coco?.ignoredFiles || config.ignoredFiles,
609
- ignoredExtensions: gitConfigParsed.coco?.ignoredExtensions || config.ignoredExtensions,
610
- defaultBranch: gitConfigParsed.coco?.defaultBranch || config.defaultBranch,
611
- verbose: gitConfigParsed.coco?.verbose || config.verbose,
612
666
  };
613
667
  }
614
668
  return removeUndefined(config);
@@ -628,9 +682,28 @@ const appendToGitConfig = async (filePath, config) => {
628
682
  const contentLines = [header];
629
683
  for (const key in config) {
630
684
  const value = config[key];
631
- if (typeof value === 'object') {
632
- // Serialize object to JSON string
633
- contentLines.push(`\t${key} = ${JSON.stringify(value)}`);
685
+ if (key === 'service') {
686
+ const service = value;
687
+ contentLines.push(` serviceProvider = ${service.provider}`);
688
+ contentLines.push(` serviceModel = ${service.model}`);
689
+ if (service.authentication.type === 'APIKey') {
690
+ contentLines.push(` serviceApiKey = ${service.authentication.credentials.apiKey}`);
691
+ }
692
+ if (service.requestOptions?.timeout) {
693
+ contentLines.push(` serviceRequestOptionsTimeout = ${service.requestOptions.timeout}`);
694
+ }
695
+ if (service.requestOptions?.maxRetries) {
696
+ contentLines.push(` serviceRequestOptionsMaxRetries = ${service.requestOptions.maxRetries}`);
697
+ }
698
+ if (service.provider === 'ollama') {
699
+ const ollamaService = service;
700
+ if (ollamaService.endpoint) {
701
+ contentLines.push(` serviceEndpoint = ${ollamaService.endpoint}`);
702
+ }
703
+ }
704
+ if (service.fields) {
705
+ contentLines.push(` serviceFields = ${JSON.stringify(service.fields)}`);
706
+ }
634
707
  }
635
708
  else if (typeof value === 'string' && value.includes('\n')) {
636
709
  // Wrap strings with new lines in quotes
@@ -1084,65 +1157,7 @@ const schema$1 = {
1084
1157
  "LLMModel": {
1085
1158
  "anyOf": [
1086
1159
  {
1087
- "type": "string",
1088
- "enum": [
1089
- "davinci-002",
1090
- "babbage-002",
1091
- "text-davinci-003",
1092
- "text-davinci-002",
1093
- "text-davinci-001",
1094
- "text-curie-001",
1095
- "text-babbage-001",
1096
- "text-ada-001",
1097
- "davinci",
1098
- "curie",
1099
- "babbage",
1100
- "ada",
1101
- "code-davinci-002",
1102
- "code-davinci-001",
1103
- "code-cushman-002",
1104
- "code-cushman-001",
1105
- "davinci-codex",
1106
- "cushman-codex",
1107
- "text-davinci-edit-001",
1108
- "code-davinci-edit-001",
1109
- "text-embedding-ada-002",
1110
- "text-similarity-davinci-001",
1111
- "text-similarity-curie-001",
1112
- "text-similarity-babbage-001",
1113
- "text-similarity-ada-001",
1114
- "text-search-davinci-doc-001",
1115
- "text-search-curie-doc-001",
1116
- "text-search-babbage-doc-001",
1117
- "text-search-ada-doc-001",
1118
- "code-search-babbage-code-001",
1119
- "code-search-ada-code-001",
1120
- "gpt2",
1121
- "gpt-3.5-turbo",
1122
- "gpt-35-turbo",
1123
- "gpt-3.5-turbo-0301",
1124
- "gpt-3.5-turbo-0613",
1125
- "gpt-3.5-turbo-1106",
1126
- "gpt-3.5-turbo-0125",
1127
- "gpt-3.5-turbo-16k",
1128
- "gpt-3.5-turbo-16k-0613",
1129
- "gpt-3.5-turbo-instruct",
1130
- "gpt-3.5-turbo-instruct-0914",
1131
- "gpt-4",
1132
- "gpt-4-0314",
1133
- "gpt-4-0613",
1134
- "gpt-4-32k",
1135
- "gpt-4-32k-0314",
1136
- "gpt-4-32k-0613",
1137
- "gpt-4-turbo",
1138
- "gpt-4-turbo-2024-04-09",
1139
- "gpt-4-turbo-preview",
1140
- "gpt-4-1106-preview",
1141
- "gpt-4-0125-preview",
1142
- "gpt-4-vision-preview",
1143
- "gpt-4o",
1144
- "gpt-4o-2024-05-13"
1145
- ]
1160
+ "$ref": "#/definitions/OpenAIModel"
1146
1161
  },
1147
1162
  {
1148
1163
  "$ref": "#/definitions/OllamaModel"
@@ -1152,6 +1167,71 @@ const schema$1 = {
1152
1167
  }
1153
1168
  ]
1154
1169
  },
1170
+ "OpenAIModel": {
1171
+ "type": "string",
1172
+ "enum": [
1173
+ "davinci-002",
1174
+ "babbage-002",
1175
+ "text-davinci-003",
1176
+ "text-davinci-002",
1177
+ "text-davinci-001",
1178
+ "text-curie-001",
1179
+ "text-babbage-001",
1180
+ "text-ada-001",
1181
+ "davinci",
1182
+ "curie",
1183
+ "babbage",
1184
+ "ada",
1185
+ "code-davinci-002",
1186
+ "code-davinci-001",
1187
+ "code-cushman-002",
1188
+ "code-cushman-001",
1189
+ "davinci-codex",
1190
+ "cushman-codex",
1191
+ "text-davinci-edit-001",
1192
+ "code-davinci-edit-001",
1193
+ "text-embedding-ada-002",
1194
+ "text-similarity-davinci-001",
1195
+ "text-similarity-curie-001",
1196
+ "text-similarity-babbage-001",
1197
+ "text-similarity-ada-001",
1198
+ "text-search-davinci-doc-001",
1199
+ "text-search-curie-doc-001",
1200
+ "text-search-babbage-doc-001",
1201
+ "text-search-ada-doc-001",
1202
+ "code-search-babbage-code-001",
1203
+ "code-search-ada-code-001",
1204
+ "gpt2",
1205
+ "gpt-3.5-turbo",
1206
+ "gpt-35-turbo",
1207
+ "gpt-3.5-turbo-0301",
1208
+ "gpt-3.5-turbo-0613",
1209
+ "gpt-3.5-turbo-1106",
1210
+ "gpt-3.5-turbo-0125",
1211
+ "gpt-3.5-turbo-16k",
1212
+ "gpt-3.5-turbo-16k-0613",
1213
+ "gpt-3.5-turbo-instruct",
1214
+ "gpt-3.5-turbo-instruct-0914",
1215
+ "gpt-4",
1216
+ "gpt-4-0314",
1217
+ "gpt-4-0613",
1218
+ "gpt-4-32k",
1219
+ "gpt-4-32k-0314",
1220
+ "gpt-4-32k-0613",
1221
+ "gpt-4-turbo",
1222
+ "gpt-4-turbo-2024-04-09",
1223
+ "gpt-4-turbo-preview",
1224
+ "gpt-4-1106-preview",
1225
+ "gpt-4-0125-preview",
1226
+ "gpt-4-vision-preview",
1227
+ "gpt-4o",
1228
+ "gpt-4o-2024-05-13",
1229
+ "gpt-4o-mini",
1230
+ "gpt-4.1",
1231
+ "gpt-4.1-mini",
1232
+ "gpt-4.1-nano"
1233
+ ]
1234
+ },
1155
1235
  "OllamaModel": {
1156
1236
  "type": "string",
1157
1237
  "enum": [
@@ -6068,7 +6148,6 @@ function getPrompt({ template, variables, fallback }) {
6068
6148
  * @throws LangChainExecutionError if the chain execution fails or returns empty results
6069
6149
  */
6070
6150
  const executeChain = async ({ llm, prompt, variables, parser }) => {
6071
- // Validate all required parameters
6072
6151
  validateRequired(llm, 'llm', 'executeChain');
6073
6152
  validateRequired(prompt, 'prompt', 'executeChain');
6074
6153
  validateRequired(variables, 'variables', 'executeChain');
@@ -6080,7 +6159,6 @@ const executeChain = async ({ llm, prompt, variables, parser }) => {
6080
6159
  try {
6081
6160
  const chain = prompt.pipe(llm).pipe(parser);
6082
6161
  const result = await chain.invoke(variables);
6083
- console.debug('LLMChain call result:', result);
6084
6162
  if (result === null || result === undefined) {
6085
6163
  throw new LangChainExecutionError('executeChain: Chain execution returned null or undefined result', { variables, promptInputVariables: prompt.inputVariables });
6086
6164
  }
@@ -7428,7 +7506,6 @@ class OutputFixingParser extends output_parsers.BaseOutputParser {
7428
7506
  * @throws LangChainExecutionError if parser creation fails
7429
7507
  */
7430
7508
  function createSchemaParser(schema, llm, options = {}) {
7431
- // Validate required parameters
7432
7509
  validateRequired(schema, 'schema', 'createSchemaParser');
7433
7510
  validateRequired(llm, 'llm', 'createSchemaParser');
7434
7511
  validateRequired(options, 'options', 'createSchemaParser');
@@ -7488,7 +7565,6 @@ You must return ONLY valid JSON that matches the schema exactly. Do not include
7488
7565
  async function executeChainWithSchema(schema, llm, prompt, variables, options = {}) {
7489
7566
  const { retryOptions = { maxAttempts: 3 }, fallbackParser, onFallback, ...parserOptions } = options;
7490
7567
  const parser = createSchemaParser(schema, llm, parserOptions);
7491
- // Define the operation to retry
7492
7568
  const operation = async () => {
7493
7569
  const result = await executeChain({
7494
7570
  llm,
@@ -7499,16 +7575,13 @@ async function executeChainWithSchema(schema, llm, prompt, variables, options =
7499
7575
  return result;
7500
7576
  };
7501
7577
  try {
7502
- // Use the general retry utility
7503
7578
  return await withRetry(operation, retryOptions);
7504
7579
  }
7505
7580
  catch (error) {
7506
- // If all retries failed and we have a fallback parser, use it
7507
7581
  if (fallbackParser) {
7508
7582
  if (onFallback) {
7509
7583
  onFallback();
7510
7584
  }
7511
- // Generate without structured parsing as fallback
7512
7585
  const fallbackResult = await executeChain({
7513
7586
  llm,
7514
7587
  prompt,
@@ -10892,6 +10965,11 @@ const handler$3 = async (argv, logger) => {
10892
10965
  else {
10893
10966
  logger.setConfig({ silent: true });
10894
10967
  }
10968
+ if (config.service.provider === 'ollama') {
10969
+ logger.verbose('⚠️ Ollama models may not strictly adhere to the output format instructions.', {
10970
+ color: 'yellow',
10971
+ });
10972
+ }
10895
10973
  async function factory() {
10896
10974
  const changes = await getChanges({
10897
10975
  git,
@@ -10947,11 +11025,6 @@ const handler$3 = async (argv, logger) => {
10947
11025
  variables: promptTemplate.inputVariables,
10948
11026
  fallback: promptTemplate,
10949
11027
  });
10950
- if (config.service.provider === 'ollama') {
10951
- logger.log('Note: Ollama models may not strictly adhere to the output format instructions.', {
10952
- color: 'yellow',
10953
- });
10954
- }
10955
11028
  // Get additional context if provided
10956
11029
  let additional_context = '';
10957
11030
  if (argv.additional) {
@@ -10992,7 +11065,7 @@ const handler$3 = async (argv, logger) => {
10992
11065
  maxAttempts,
10993
11066
  onRetry: (attempt, error) => {
10994
11067
  logger.verbose(`Failed to parse commit message (attempt ${attempt}/${maxAttempts}): ${error.message}`, { color: 'yellow' });
10995
- }
11068
+ },
10996
11069
  },
10997
11070
  fallbackParser: (text) => ({
10998
11071
  title: text.split('\n')[0] || 'Auto-generated commit',
@@ -11213,11 +11286,22 @@ async function getProjectConfigFilePath(configFileName) {
11213
11286
  }
11214
11287
 
11215
11288
  const OPEN_AI_MODELS = [
11289
+ 'gpt-4.5',
11216
11290
  'gpt-4o',
11291
+ 'gpt-4o-mini',
11292
+ 'gpt-4.1',
11293
+ 'gpt-4.1',
11294
+ 'gpt-4.1-mini',
11295
+ 'gpt-4.1-nano',
11217
11296
  'gpt-4-32k',
11218
11297
  'gpt-4-turbo',
11219
11298
  'gpt-4',
11220
11299
  'gpt-3.5-turbo',
11300
+ 'o1',
11301
+ 'o1-mini',
11302
+ '03-mini',
11303
+ '03',
11304
+ 'o4-mini',
11221
11305
  ];
11222
11306
  const ANTHROPIC_MODELS = [
11223
11307
  'claude-sonnet-4-0',
@@ -11362,6 +11446,32 @@ const questions = {
11362
11446
  });
11363
11447
  return parseFloat(temperature);
11364
11448
  },
11449
+ inputOllamaEndpoint: async () => {
11450
+ return await prompts.input({
11451
+ message: 'Ollama endpoint (e.g., http://localhost:11434):',
11452
+ default: 'http://localhost:11434',
11453
+ });
11454
+ },
11455
+ inputRequestTimeout: async () => {
11456
+ const timeout = await prompts.input({
11457
+ message: 'Request timeout in milliseconds:',
11458
+ default: '30000',
11459
+ });
11460
+ return parseInt(timeout);
11461
+ },
11462
+ inputRequestMaxRetries: async () => {
11463
+ const maxRetries = await prompts.input({
11464
+ message: 'Maximum number of request retries:',
11465
+ default: '3',
11466
+ });
11467
+ return parseInt(maxRetries);
11468
+ },
11469
+ inputServiceFields: async () => {
11470
+ return await prompts.editor({
11471
+ message: 'Enter additional service fields as a JSON string (optional):',
11472
+ default: '{}',
11473
+ });
11474
+ },
11365
11475
  selectDefaultGitBranch: async () => (await prompts.input({
11366
11476
  message: 'default branch for the repository:',
11367
11477
  default: 'main',
@@ -11455,6 +11565,29 @@ const handler$2 = async (argv, logger) => {
11455
11565
  tokenLimit: await questions.inputTokenLimit(),
11456
11566
  };
11457
11567
  config.verbose = await questions.enableVerboseMode();
11568
+ if (llmProvider === 'ollama') {
11569
+ config.service.endpoint = await questions.inputOllamaEndpoint();
11570
+ }
11571
+ config.service.requestOptions = {
11572
+ timeout: await questions.inputRequestTimeout(),
11573
+ maxRetries: await questions.inputRequestMaxRetries(),
11574
+ };
11575
+ const promptForServiceFields = await prompts.confirm({
11576
+ message: 'would you like to configure additional service fields (advanced)?',
11577
+ default: false,
11578
+ });
11579
+ if (promptForServiceFields) {
11580
+ const fieldsJson = await questions.inputServiceFields();
11581
+ try {
11582
+ config.service.fields = JSON.parse(fieldsJson);
11583
+ }
11584
+ catch (e) {
11585
+ logger.log('Invalid JSON for service fields. Skipping.', { color: 'red' });
11586
+ logger.verbose(`Error parsing service fields: ${e.message}`, {
11587
+ color: 'red',
11588
+ });
11589
+ }
11590
+ }
11458
11591
  const promptForIgnores = await prompts.confirm({
11459
11592
  message: 'would you like to configure ignored files and extensions?',
11460
11593
  default: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-coco",
3
- "version": "0.17.0",
3
+ "version": "0.18.1",
4
4
  "description": "zero-effort git commits with coco.",
5
5
  "author": "gfargo <ghfargo@gmail.com>",
6
6
  "license": "MIT",
@@ -105,7 +105,7 @@
105
105
  "performance-now": "2.1.0",
106
106
  "pretty-ms": "7.0.1",
107
107
  "simple-git": "3.27.0",
108
- "tiktoken": "^1.0.17",
108
+ "tiktoken": "^1.0.21",
109
109
  "yargs": "17.7.2"
110
110
  },
111
111
  "resolutions": {