add support for Azure OpenAI API - continue MR 167 (#324)

*  feat(api.ts): add support for Azure OpenAI API

The Azure OpenAI API is now supported in addition to the OpenAI API. The API type can be specified in the configuration file using the OPENAI_API_TYPE key. If the key is not specified, the default value is 'openai'. The AzureOpenAIApi class is added to the utils folder to handle the Azure OpenAI API calls. The createChatCompletion method is implemented in the AzureOpenAIApi class to handle the chat completion requests. The method is called in the generateCommitMessage method in the OpenAi class if the apiType is set to 'azure'.

* 🐛 fix(AzureOpenAI.ts): fix import path for AxiosRequestConfig to avoid conflicts with openai's axios dependency

In AzureOpenAI.ts, the import path for AxiosRequestConfig was changed to avoid conflicts with openai's axios dependency, which was causing lint errors.

* 🔧 fix(AzureOpenAI.ts): import RequiredError to fix error handling and remove commented out debug code

The RequiredError class was not being imported from the openai/dist/base module, causing errors to be thrown incorrectly. This has been fixed by importing the RequiredError class. Debug code has been removed and comments have been updated to reflect the changes made.

* 🔇 chore(AzureOpenAI.ts): remove console.log statement and translate Japanese comment

The commented console.log statement was removed to improve code cleanliness.

* 🔥 refactoring(api.ts, AzureOpenAI.ts): Leverage openai npm package
🐛 fix(config.ts): API Key string validation

*  (README.md): update opencommit command to set OCO_AI_PROVIDER instead of OPENAI_API_TYPE to improve consistency and clarity in configuration
♻️ (config.ts): update OCO_AI_PROVIDER enum in configValidators to include 'azure' and remove unnecessary conditionals to improve maintainability and extensibility
⬆️ (config.ts): add OCO_AZURE_API_VERSION to ConfigType and getConfig() to support new azure api version configuration
♻️ (engine/ollama.ts): add space between temperature and top_p properties to improve readability
♻️ (engine/openAi.ts): refactor OpenAi class to improve readability and maintainability by extracting configuration logic into separate switch statement
🔧 (generateCommitMessageFromGitDiff.ts): refactor MAX_TOKENS_INPUT and MAX_TOKENS_OUTPUT lines to improve readability
🔧 (generateCommitMessageFromGitDiff.ts): refactor generateCommitMessageByDiff and getMessagesPromisesByChangesInFile functions to use destructuring and improve readability
♻️ (generateCommitMessageFromGitDiff.ts): refactor getCommitMsgsPromisesFromFileDiffs function to use destructuring and improve readability
📝 (modules/commitlint/config.ts): add missing types to function parameters and improve readability by removing unnecessary comments and whitespace
📝 (modules/commitlint/utils.ts): fix indentation and add missing types to function parameters
📝 (prompts.ts): update INIT_MAIN_PROMPT description to include clarification on the use of present tense and line length
📝 (version.ts): fix import statements and add missing types to function parameters

*  (package.json): add @azure/openai dependency to support integration with Azure AI services
🔧 (config.ts): change CONFIG_KEYS.OCO_AZURE_API_VERSION to CONFIG_KEYS.OCO_AZURE_ENDPOINT to improve semantics and allow configuration of Azure endpoint URL
♻️ (config.ts): refactor configValidators to use OCO_AZURE_ENDPOINT instead of OCO_AZURE_API_VERSION and update validation message for OCO_AZURE_ENDPOINT
 (config.ts): add OCO_AZURE_ENDPOINT to getConfig function to retrieve Azure endpoint configuration from environment variables
 (azure.ts): introduce a new file azure.ts to implement Azure AI engine
 (azure.ts): implement generateCommitMessage function in Azure AI engine
 (prompts.ts): add a new line to INIT_MAIN_PROMPT to mention that changes within a single file should be described with a single commit message
♻️ (engine.ts): refactor getEngine function to add support for 'azure' as the AI provider and return the azure engine

* 📝 (prompts.ts): remove unnecessary information about crafting a concise commit message with a one single message for OCO_ONE_LINE_COMMIT configuration

* 3.0.14 (#333)

* test:  add the first E2E test and configuration to CI (#316)

* add tests

* Add push config (#220)

* feat: add instructions and support for configuring gpt-4-turbo (#320)

* 3.0.12

* build

* feat: add 'gpt-4-turbo' to supported models in README and config validation

---------

Co-authored-by: di-sukharev <dim.sukharev@gmail.com>

*  fix the broken E2E tests due to the addition of OCO_GITPUSH (#321)

* test(oneFile.test.ts): update test expectations to match new push prompt text

* build

* Feat: Add Claude 3 support (#318)

* 3.0.12

* build

* feat: anthropic claude 3 support

* fix: add system prompt

* fix: type check

* fix: package version

* fix: update anthropic for dependency bug fix

* feat: update build files

* feat: update version number

---------

Co-authored-by: di-sukharev <dim.sukharev@gmail.com>

* 🐛bug fix: enable to use the new format of OpenAI's project API Key (#328)

* fix(config.ts): remove validation for OCO_OPENAI_API_KEY length to accommodate variable key lengths

* build

* ♻️ refactor(config.ts): Addition of UnitTest environment and unittest for commands/config.ts#getConfig (#330)

* feat(jest.config.ts): update jest preset for TS ESM support and ignore patterns
feat(package.json): add test:unit script with NODE_OPTIONS for ESM
refactor(src/commands/config.ts): improve dotenv usage with dynamic paths
feat(src/commands/config.ts): allow custom config and env paths in getConfig
refactor(src/commands/config.ts): streamline environment variable access

feat(test/unit): add unit tests for config handling and utility functions

- Implement unit tests for `getConfig` function to ensure correct behavior
  in various scenarios including default values, global config, and local
  env file precedence.
- Add utility function `prepareFile` for creating temporary files during
  tests, facilitating testing of file-based configurations.

* feat(e2e.yml): add unit-test job to GitHub Actions for running unit tests on pull requests

* ci(test.yml): add GitHub Actions workflow for unit and e2e tests on pull requests

* refactor(config.ts): streamline environment variable access using process.env directly
test(config.test.ts): add setup and teardown for environment variables in tests to ensure test isolation

* feat(package.json): add `test:all` script to run all tests in Docker
refactor(package.json): consolidate Docker build steps into `test:docker-build` script for DRY principle
fix(package.json): ensure `test:unit:docker` and `test:e2e:docker` scripts use the same Docker image and remove container after run
chore(test/Dockerfile): remove default CMD to allow dynamic test script execution in Docker

* refactor(config.test.ts): anonymize API keys in tests for better security practices

* feat(config.test.ts): add tests for OCO_ANTHROPIC_API_KEY configuration

* refactor(config.ts): streamline path imports and remove unused DotenvParseOutput

- Simplify path module imports by removing default import and using named imports for `pathJoin` and `pathResolve`.
- Remove unused `DotenvParseOutput` import to clean up the code.

* refactor(config.test.ts): simplify API key mock values for clarity in tests

* test(config.test.ts): remove tests for default config values and redundant cases

- Removed tests that checked for default config values when no config or env files are present, as these scenarios are now handled differently.
- Eliminated tests for empty global config and local env files to streamline testing focus on actual config loading logic.
- Removed test for prioritizing local env over global config due to changes in config loading strategy, simplifying the configuration management.

* new version

---------

Co-authored-by: Takanori Matsumoto <matscube@gmail.com>
Co-authored-by: Moret84 <aurelienrivet@hotmail.fr>
Co-authored-by: yowatari <4982161+YOwatari@users.noreply.github.com>
Co-authored-by: metavind <94786679+metavind@users.noreply.github.com>

* 3.0.15

* build

* 🐛 (prepare-commit-msg-hook.ts): improve error message to cover missing OCO_ANTHROPIC_API_KEY and OCO_AZURE_API_KEY in addition to OCO_OPENAI_API_KEY

* 🐛 (azure.ts): fix check for OCO_AI_PROVIDER to properly assign provider variable
🐛 (azure.ts): initialize OpenAIClient only if provider is 'azure'
🔧 (Dockerfile): rearrange instructions to optimize caching by copying package.json and package-lock.json first before running npm ci and copying the rest of the files
🔧 (e2e/noChanges.test.ts): remove unnecessary line break
🔧 (e2e/oneFile.test.ts): remove unnecessary line break

---------

Co-authored-by: Takuya Ono <takuya-o@users.osdn.me>
Co-authored-by: GPT10 <57486732+di-sukharev@users.noreply.github.com>
Co-authored-by: Takanori Matsumoto <matscube@gmail.com>
Co-authored-by: Moret84 <aurelienrivet@hotmail.fr>
Co-authored-by: yowatari <4982161+YOwatari@users.noreply.github.com>
Co-authored-by: metavind <94786679+metavind@users.noreply.github.com>
Co-authored-by: di-sukharev <dim.sukharev@gmail.com>
This commit is contained in:
Jakub Rosa
2024-05-09 10:23:00 +02:00
committed by GitHub
parent 74024a4997
commit f814c6b89d
19 changed files with 55035 additions and 5253 deletions
+14
View File
@@ -150,6 +150,20 @@ oco config set OCO_MODEL=gpt-4-0125-preview
Make sure that you spell it `gpt-4` (lowercase) and that you have API access to the 4th model. Even if you have ChatGPT+, that doesn't necessarily mean that you have API access to GPT-4.
### Switch to Azure OpenAI
By default OpenCommit uses [OpenAI](https://openai.com).
You could switch to [Azure OpenAI Service](https://learn.microsoft.com/azure/cognitive-services/openai/)🚀
```sh
opencommit config set OCO_AI_PROVIDER=azure
```
Of course need to set 'OPENAI_API_KEY'. And also need to set the
'OPENAI_BASE_PATH' for the endpoint and set the deployment name to
'model'.
### Locale configuration
To globally specify the language used to generate commit messages:
+20531 -2461
View File
File diff suppressed because one or more lines are too long
+34062 -2684
View File
File diff suppressed because one or more lines are too long
+159 -3
View File
@@ -1,17 +1,18 @@
{
"name": "opencommit",
"version": "3.0.14",
"version": "3.0.15",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "opencommit",
"version": "3.0.14",
"version": "3.0.15",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/exec": "^1.1.1",
"@actions/github": "^5.1.1",
"@azure/openai": "^1.0.0-beta.12",
"@anthropic-ai/sdk": "^0.19.2",
"@clack/prompts": "^0.6.1",
"@dqbd/tiktoken": "^1.0.2",
@@ -114,6 +115,126 @@
"node": ">=6.0.0"
}
},
"node_modules/@azure-rest/core-client": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-1.4.0.tgz",
"integrity": "sha512-ozTDPBVUDR5eOnMIwhggbnVmOrka4fXCs8n8mvUo4WLLc38kki6bAOByDoVZZPz/pZy2jMt2kwfpvy/UjALj6w==",
"dependencies": {
"@azure/abort-controller": "^2.0.0",
"@azure/core-auth": "^1.3.0",
"@azure/core-rest-pipeline": "^1.5.0",
"@azure/core-tracing": "^1.0.1",
"@azure/core-util": "^1.0.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/abort-controller": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
"integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
"dependencies": {
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/core-auth": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.7.2.tgz",
"integrity": "sha512-Igm/S3fDYmnMq1uKS38Ae1/m37B3zigdlZw+kocwEhh5GjyKjPrXKO2J6rzpC1wAxrNil/jX9BJRqBshyjnF3g==",
"dependencies": {
"@azure/abort-controller": "^2.0.0",
"@azure/core-util": "^1.1.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/core-rest-pipeline": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.15.2.tgz",
"integrity": "sha512-BmWfpjc/QXc2ipHOh6LbUzp3ONCaa6xzIssTU0DwH9bbYNXJlGUL6tujx5TrbVd/QQknmS+vlQJGrCq2oL1gZA==",
"dependencies": {
"@azure/abort-controller": "^2.0.0",
"@azure/core-auth": "^1.4.0",
"@azure/core-tracing": "^1.0.1",
"@azure/core-util": "^1.3.0",
"@azure/logger": "^1.0.0",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/core-sse": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@azure/core-sse/-/core-sse-2.1.2.tgz",
"integrity": "sha512-yf+pFIu8yCzXu9RbH2+8kp9vITIKJLHgkLgFNA6hxiDHK3fxeP596cHUj4c8Cm8JlooaUnYdHmF84KCZt3jbmw==",
"dependencies": {
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/core-tracing": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.1.2.tgz",
"integrity": "sha512-dawW9ifvWAWmUm9/h+/UQ2jrdvjCJ7VJEuCJ6XVNudzcOwm53BFZH4Q845vjfgoUAM8ZxokvVNxNxAITc502YA==",
"dependencies": {
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/core-util": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.9.0.tgz",
"integrity": "sha512-AfalUQ1ZppaKuxPPMsFEUdX6GZPB3d9paR9d/TTL7Ow2De8cJaC7ibi7kWVlFAVPCYo31OcnGymc0R89DX8Oaw==",
"dependencies": {
"@azure/abort-controller": "^2.0.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/logger": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.1.2.tgz",
"integrity": "sha512-l170uE7bsKpIU6B/giRc9i4NI0Mj+tANMMMxf7Zi/5cKzEqPayP7+X1WPrG7e+91JgY8N+7K7nF2WOi7iVhXvg==",
"dependencies": {
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/openai": {
"version": "1.0.0-beta.12",
"resolved": "https://registry.npmjs.org/@azure/openai/-/openai-1.0.0-beta.12.tgz",
"integrity": "sha512-qKblxr6oVa8GsyNzY+/Ub9VmEsPYKhBrUrPaNEQiM+qrxnBPVm9kaeqGFFb/U78Q2zOabmhF9ctYt3xBW0nWnQ==",
"dependencies": {
"@azure-rest/core-client": "^1.1.7",
"@azure/core-auth": "^1.4.0",
"@azure/core-rest-pipeline": "^1.13.0",
"@azure/core-sse": "^2.0.0",
"@azure/core-util": "^1.4.0",
"@azure/logger": "^1.0.3",
"tslib": "^2.4.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@anthropic-ai/sdk": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.19.2.tgz",
@@ -737,6 +858,7 @@
},
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
"version": "1.3.0",
"extraneous": true,
"inBundle": true,
"license": "MIT",
"engines": {
@@ -2349,6 +2471,17 @@
"node": ">=0.4.0"
}
},
"node_modules/agent-base": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
"integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
"dependencies": {
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/agentkeepalive": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
@@ -3285,7 +3418,6 @@
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
@@ -4692,6 +4824,30 @@
"node": ">=8"
}
},
"node_modules/http-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"dependencies": {
"agent-base": "^7.1.0",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz",
"integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==",
"dependencies": {
"agent-base": "^7.0.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/has-property-descriptors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "opencommit",
"version": "3.0.14",
"version": "3.0.15",
"description": "Auto-generate impressive commits in 1 second. Killing lame commits with AI 🤯🔫",
"keywords": [
"git",
@@ -77,6 +77,7 @@
"@actions/core": "^1.10.0",
"@actions/exec": "^1.1.1",
"@actions/github": "^5.1.1",
"@azure/openai": "^1.0.0-beta.12",
"@anthropic-ai/sdk": "^0.19.2",
"@clack/prompts": "^0.6.1",
"@dqbd/tiktoken": "^1.0.2",
+12
View File
@@ -154,6 +154,18 @@ ${chalk.grey('——————————————————')}`
} else outro(`${chalk.gray('✖')} process cancelled`);
}
}
if (!isCommitConfirmedByUser && !isCancel(isCommitConfirmedByUser)) {
const regenerateMessage = await confirm({
message: 'Do you want to regenerate the message ?'
});
if (regenerateMessage && !isCancel(isCommitConfirmedByUser)) {
await generateCommitMessageFromGitDiff(
diff,
extraArgs,
fullGitMojiSpec
)
}
}
} catch (error) {
commitSpinner.stop('📝 Commit message generated');
+43 -11
View File
@@ -14,6 +14,7 @@ import { getI18nLocal } from '../i18n';
export enum CONFIG_KEYS {
OCO_OPENAI_API_KEY = 'OCO_OPENAI_API_KEY',
OCO_ANTHROPIC_API_KEY = 'OCO_ANTHROPIC_API_KEY',
OCO_AZURE_API_KEY = 'OCO_AZURE_API_KEY',
OCO_TOKENS_MAX_INPUT = 'OCO_TOKENS_MAX_INPUT',
OCO_TOKENS_MAX_OUTPUT = 'OCO_TOKENS_MAX_OUTPUT',
OCO_OPENAI_BASE_PATH = 'OCO_OPENAI_BASE_PATH',
@@ -25,7 +26,8 @@ export enum CONFIG_KEYS {
OCO_PROMPT_MODULE = 'OCO_PROMPT_MODULE',
OCO_AI_PROVIDER = 'OCO_AI_PROVIDER',
OCO_GITPUSH = 'OCO_GITPUSH',
OCO_ONE_LINE_COMMIT = 'OCO_ONE_LINE_COMMIT'
OCO_ONE_LINE_COMMIT = 'OCO_ONE_LINE_COMMIT',
OCO_AZURE_ENDPOINT = 'OCO_AZURE_ENDPOINT'
}
export enum CONFIG_MODES {
@@ -82,13 +84,23 @@ export const configValidators = {
//need api key unless running locally with ollama
validateConfig(
'OpenAI API_KEY',
value || config.OCO_ANTHROPIC_API_KEY || config.OCO_AI_PROVIDER.startsWith('ollama') || config.OCO_AI_PROVIDER == 'test',
'You need to provide an OpenAI/Anthropic API key'
value || config.OCO_ANTHROPIC_API_KEY || config.OCO_AI_PROVIDER.startsWith('ollama') || config.OCO_AZURE_API_KEY || config.OCO_AI_PROVIDER == 'test' ,
'You need to provide an OpenAI/Anthropic/Azure API key'
);
validateConfig(
CONFIG_KEYS.OCO_OPENAI_API_KEY,
value.startsWith('sk-'),
'Must start with "sk-"'
value.startsWith('sk-') || config.OCO_AI_PROVIDER != 'openai',
'Must start with "sk-" for openai provider'
);
return value;
},
[CONFIG_KEYS.OCO_AZURE_API_KEY](value: any, config: any = {}) {
validateConfig(
'ANTHROPIC_API_KEY',
value || config.OCO_OPENAI_API_KEY || config.OCO_AZURE_API_KEY || config.OCO_AI_PROVIDER == 'ollama' || config.OCO_AI_PROVIDER == 'test',
'You need to provide an OpenAI/Anthropic/Azure API key'
);
return value;
@@ -98,7 +110,7 @@ export const configValidators = {
validateConfig(
'ANTHROPIC_API_KEY',
value || config.OCO_OPENAI_API_KEY || config.OCO_AI_PROVIDER == 'ollama' || config.OCO_AI_PROVIDER == 'test',
'You need to provide an OpenAI/Anthropic API key'
'You need to provide an OpenAI/Anthropic/Azure API key'
);
return value;
@@ -180,12 +192,19 @@ export const configValidators = {
return value;
},
[CONFIG_KEYS.OCO_MODEL](value: any) {
[CONFIG_KEYS.OCO_MODEL](value: any, config: any = {}) {
validateConfig(
CONFIG_KEYS.OCO_MODEL,
[...MODEL_LIST.openai, ...MODEL_LIST.anthropic].includes(value),
[...MODEL_LIST.openai, ...MODEL_LIST.anthropic].includes(value) || config.OCO_AI_PROVIDER == 'azure',
`${value} is not supported yet, use 'gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo' (default), 'gpt-3.5-turbo-0125', 'gpt-4-1106-preview', 'gpt-4-turbo-preview', 'gpt-4-0125-preview', 'claude-3-opus-20240229', 'claude-3-sonnet-20240229' or 'claude-3-haiku-20240307'`
);
validateConfig(
CONFIG_KEYS.OCO_MODEL,
typeof value === 'string' &&
value.match(/^[a-zA-Z0-9~\-]{1,63}[a-zA-Z0-9]$/) ||
config.OCO_AI_PROVIDER != 'azure',
`${value} is not model deployed name.`
);
return value;
},
@@ -223,9 +242,10 @@ export const configValidators = {
'',
'openai',
'anthropic',
'azure',
'test'
].includes(value) || value.startsWith('ollama'),
`${value} is not supported yet, use 'ollama/{model}' 'anthropic' or 'openai' (default)`
`${value} is not supported yet, use 'ollama/{model}', 'azure', 'anthropic' or 'openai' (default)`
);
return value;
},
@@ -237,6 +257,15 @@ export const configValidators = {
'Must be true or false'
);
return value;
},
[CONFIG_KEYS.OCO_AZURE_ENDPOINT](value: any) {
validateConfig(
CONFIG_KEYS.OCO_AZURE_ENDPOINT,
value.includes('openai.azure.com'),
'Must be in format "https://<resource name>.openai.azure.com/"'
);
return value;
},
};
@@ -259,6 +288,7 @@ export const getConfig = ({
const configFromEnv = {
OCO_OPENAI_API_KEY: process.env.OCO_OPENAI_API_KEY,
OCO_ANTHROPIC_API_KEY: process.env.OCO_ANTHROPIC_API_KEY,
OCO_AZURE_API_KEY: process.env.OCO_AZURE_API_KEY,
OCO_TOKENS_MAX_INPUT: process.env.OCO_TOKENS_MAX_INPUT
? Number(process.env.OCO_TOKENS_MAX_INPUT)
: undefined,
@@ -275,7 +305,9 @@ export const getConfig = ({
OCO_PROMPT_MODULE: process.env.OCO_PROMPT_MODULE || 'conventional-commit',
OCO_AI_PROVIDER: process.env.OCO_AI_PROVIDER || 'openai',
OCO_GITPUSH: process.env.OCO_GITPUSH === 'false' ? false : true,
OCO_ONE_LINE_COMMIT: process.env.OCO_ONE_LINE_COMMIT === 'true' ? true : false
OCO_ONE_LINE_COMMIT:
process.env.OCO_ONE_LINE_COMMIT === 'true' ? true : false,
OCO_AZURE_ENDPOINT: process.env.OCO_AZURE_ENDPOINT || '',
};
const configExists = existsSync(configPath);
@@ -300,7 +332,7 @@ export const getConfig = ({
config[configKey] = validValue;
} catch (error) {
outro(`Unknown '${configKey}' config option.`);
outro(`Unknown '${configKey}' config option or missing validator.`);
outro(
`Manually fix the '.env' file or global '~/.opencommit' config file.`
);
+2 -2
View File
@@ -39,9 +39,9 @@ export const prepareCommitMessageHook = async (
const config = getConfig();
if (!config?.OCO_OPENAI_API_KEY) {
if (!config?.OCO_OPENAI_API_KEY && !config?.OCO_ANTHROPIC_API_KEY && !config?.OCO_AZURE_API_KEY) {
throw new Error(
'No OPEN_AI_API exists. Set your OPEN_AI_API=<key> in ~/.opencommit'
'No OPEN_AI_API or OCO_ANTHROPIC_API_KEY or OCO_AZURE_API_KEY exists. Set your key in ~/.opencommit'
);
}
+109
View File
@@ -0,0 +1,109 @@
import axios from 'axios';
import chalk from 'chalk';
import { execa } from 'execa';
import {
ChatCompletionRequestMessage,
} from 'openai';
import { OpenAIClient, AzureKeyCredential } from '@azure/openai';
import { intro, outro } from '@clack/prompts';
import {
CONFIG_MODES,
DEFAULT_TOKEN_LIMITS,
getConfig
} from '../commands/config';
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
import { tokenCount } from '../utils/tokenCount';
import { AiEngine } from './Engine';
const config = getConfig();
const MAX_TOKENS_OUTPUT =
config?.OCO_TOKENS_MAX_OUTPUT ||
DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_OUTPUT;
const MAX_TOKENS_INPUT =
config?.OCO_TOKENS_MAX_INPUT || DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_INPUT;
let basePath = config?.OCO_OPENAI_BASE_PATH;
let apiKey = config?.OCO_AZURE_API_KEY;
let apiEndpoint = config?.OCO_AZURE_ENDPOINT;
const [command, mode] = process.argv.slice(2);
const provider = config?.OCO_AI_PROVIDER;
if (
provider === 'azure' &&
!apiKey &&
!apiEndpoint &&
command !== 'config' &&
mode !== CONFIG_MODES.set
) {
intro('opencommit');
outro(
'OCO_AZURE_API_KEY or OCO_AZURE_ENDPOINT are not set, please run `oco config set OCO_AZURE_API_KEY=<your token> . If you are using GPT, make sure you add payment details, so API works.`'
);
outro(
'For help look into README https://github.com/di-sukharev/opencommit#setup'
);
process.exit(1);
}
const MODEL = config?.OCO_MODEL || 'gpt-3.5-turbo';
class Azure implements AiEngine {
private openAI!: OpenAIClient;
constructor() {
if (provider === 'azure') {
this.openAI = new OpenAIClient(apiEndpoint, new AzureKeyCredential(apiKey));
}
}
public generateCommitMessage = async (
messages: Array<ChatCompletionRequestMessage>
): Promise<string | undefined> => {
try {
const REQUEST_TOKENS = messages
.map((msg) => tokenCount(msg.content) + 4)
.reduce((a, b) => a + b, 0);
if (REQUEST_TOKENS > MAX_TOKENS_INPUT - MAX_TOKENS_OUTPUT) {
throw new Error(GenerateCommitMessageErrorEnum.tooMuchTokens);
}
const data = await this.openAI.getChatCompletions(MODEL, messages);
const message = data.choices[0].message;
if (message?.content === null) {
return undefined;
}
return message?.content;
} catch (error) {
outro(`${chalk.red('✖')} ${MODEL}`);
const err = error as Error;
outro(`${chalk.red('✖')} ${err?.message || err}`);
if (
axios.isAxiosError<{ error?: { message: string } }>(error) &&
error.response?.status === 401
) {
const openAiError = error.response.data.error;
if (openAiError?.message) outro(openAiError.message);
outro(
'For help look into README https://github.com/di-sukharev/opencommit#setup'
);
}
throw err;
}
};
}
export const azure = new Azure();
+12 -6
View File
@@ -10,8 +10,11 @@ import { tokenCount } from './utils/tokenCount';
import { getEngine } from './utils/engine';
const config = getConfig();
const MAX_TOKENS_INPUT = config?.OCO_TOKENS_MAX_INPUT || DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_INPUT;
const MAX_TOKENS_OUTPUT = config?.OCO_TOKENS_MAX_OUTPUT || DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_OUTPUT;
const MAX_TOKENS_INPUT =
config?.OCO_TOKENS_MAX_INPUT || DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_INPUT;
const MAX_TOKENS_OUTPUT =
config?.OCO_TOKENS_MAX_OUTPUT ||
DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_OUTPUT;
const generateCommitMessageChatCompletionPrompt = async (
diff: string,
@@ -71,9 +74,12 @@ export const generateCommitMessageByDiff = async (
return commitMessages.join('\n\n');
}
const messages = await generateCommitMessageChatCompletionPrompt(diff, fullGitMojiSpec);
const messages = await generateCommitMessageChatCompletionPrompt(
diff,
fullGitMojiSpec
);
const engine = getEngine()
const engine = getEngine();
const commitMessage = await engine.generateCommitMessage(messages);
if (!commitMessage)
@@ -112,7 +118,7 @@ function getMessagesPromisesByChangesInFile(
}
}
const engine = getEngine()
const engine = getEngine();
const commitMsgsFromFileLineDiffs = lineDiffsWithHeader.map(
async (lineDiff) => {
const messages = await generateCommitMessageChatCompletionPrompt(
@@ -194,7 +200,7 @@ export const getCommitMsgsPromisesFromFileDiffs = async (
fullGitMojiSpec
);
const engine = getEngine()
const engine = getEngine();
commitMessagePromises.push(engine.generateCommitMessage(messages));
}
}
+1 -1
View File
@@ -55,7 +55,7 @@ export const configureCommitlintIntegration = async (force = false) => {
// consistencyPrompts.map((p) => p.content)
// );
const engine = getEngine()
const engine = getEngine();
let consistency =
(await engine.generateCommitMessage(consistencyPrompts)) || '{}';
+1 -1
View File
@@ -118,7 +118,7 @@ const INIT_MAIN_PROMPT = (
${
config?.OCO_ONE_LINE_COMMIT
? 'Craft a concise commit message that encapsulates all changes made, with an emphasis on the primary updates. If the modifications share a common theme or scope, mention it succinctly; otherwise, leave the scope out to maintain focus. The goal is to provide a clear and unified overview of the changes in a one single message, without diverging into a list of commit per file change.'
: ""
: ''
}
Use the present tense. Lines must not be longer than 74 characters. Use ${language} for the commit message.`
});
+3
View File
@@ -2,6 +2,7 @@ import { AiEngine } from '../engine/Engine';
import { api } from '../engine/openAi';
import { getConfig } from '../commands/config';
import { ollamaAi } from '../engine/ollama';
import { azure } from '../engine/azure';
import { anthropicAi } from '../engine/anthropic'
import { testAi } from '../engine/testAi';
@@ -18,6 +19,8 @@ export function getEngine(): AiEngine {
return anthropicAi;
} else if (config?.OCO_AI_PROVIDER == 'test') {
return testAi;
} else if (config?.OCO_AI_PROVIDER == 'azure') {
return azure;
}
// open ai gpt by default
return api;
+2 -2
View File
@@ -1,5 +1,5 @@
import { outro } from "@clack/prompts";
import { execa } from "execa";
import { outro } from '@clack/prompts';
import { execa } from 'execa';
export const getOpenCommitLatestVersion = async (): Promise<
string | undefined
+5 -2
View File
@@ -10,10 +10,13 @@ RUN apt-get install -y nodejs
RUN git config --global user.email "test@example.com"
RUN git config --global user.name "Test User"
COPY . /app
WORKDIR /app
COPY package.json /app/
COPY package-lock.json /app/
RUN ls -la
RUN npm install
RUN npm ci
COPY . /app
RUN ls -la
RUN npm run build
-1
View File
@@ -5,7 +5,6 @@ import { prepareEnvironment } from './utils';
it('cli flow when there are no changes', async () => {
const { gitDir, cleanup } = await prepareEnvironment();
const { findByText } = await render(`OCO_AI_PROVIDER='test' node`, [resolve('./out/cli.cjs')], { cwd: gitDir });
expect(await findByText('No changes detected')).toBeInTheConsole();
-1
View File
@@ -10,7 +10,6 @@ it('cli flow to generate commit message for 1 new file (staged)', async () => {
await render('git' ,['add index.ts'], { cwd: gitDir });
const { queryByText, findByText, userEvent } = await render(`OCO_AI_PROVIDER='test' node`, [resolve('./out/cli.cjs')], { cwd: gitDir });
expect(await queryByText('No files are staged')).not.toBeInTheConsole();
expect(await queryByText('Do you want to stage all files and generate commit message?')).not.toBeInTheConsole();