Merge remote-tracking branch 'origin/dev'

This commit is contained in:
di-sukharev
2024-05-25 19:14:41 +03:00
18 changed files with 13662 additions and 3429 deletions
+23 -1
View File
@@ -84,6 +84,14 @@ This is due to limit the number of tokens sent in each request. However, if you
oco --fgm oco --fgm
``` ```
#### Skip Commit Confirmation
This flag allows users to automatically commit the changes without having to manually confirm the commit message. This is useful for users who want to streamline the commit process and avoid additional steps. To use this flag, you can run the following command:
```
oco --yes
```
## Configuration ## Configuration
### Local per repo configuration ### Local per repo configuration
@@ -97,7 +105,7 @@ OCO_TOKENS_MAX_OUTPUT=<max response tokens (default: 500)>
OCO_OPENAI_BASE_PATH=<may be used to set proxy path to OpenAI api> OCO_OPENAI_BASE_PATH=<may be used to set proxy path to OpenAI api>
OCO_DESCRIPTION=<postface a message with ~3 sentences description of the changes> OCO_DESCRIPTION=<postface a message with ~3 sentences description of the changes>
OCO_EMOJI=<boolean, add GitMoji> OCO_EMOJI=<boolean, add GitMoji>
OCO_MODEL=<either 'gpt-4o', 'gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo' (default), 'gpt-3.5-turbo-0125', 'gpt-4-1106-preview', 'gpt-4-turbo-preview' or 'gpt-4-0125-preview'> OCO_MODEL=<either 'gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo' (default), 'gpt-3.5-turbo-0125', 'gpt-4-1106-preview', 'gpt-4-turbo-preview' or 'gpt-4-0125-preview'>
OCO_LANGUAGE=<locale, scroll to the bottom to see options> OCO_LANGUAGE=<locale, scroll to the bottom to see options>
OCO_MESSAGE_TEMPLATE_PLACEHOLDER=<message template placeholder, default: '$msg'> OCO_MESSAGE_TEMPLATE_PLACEHOLDER=<message template placeholder, default: '$msg'>
OCO_PROMPT_MODULE=<either conventional-commit or @commitlint, default: conventional-commit> OCO_PROMPT_MODULE=<either conventional-commit or @commitlint, default: conventional-commit>
@@ -150,6 +158,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. 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 ### Locale configuration
To globally specify the language used to generate commit messages: To globally specify the language used to generate commit messages:
+6198 -1252
View File
File diff suppressed because it is too large Load Diff
+7057 -2131
View File
File diff suppressed because it is too large Load Diff
+156 -1
View File
@@ -13,6 +13,7 @@
"@actions/exec": "^1.1.1", "@actions/exec": "^1.1.1",
"@actions/github": "^5.1.1", "@actions/github": "^5.1.1",
"@anthropic-ai/sdk": "^0.19.2", "@anthropic-ai/sdk": "^0.19.2",
"@azure/openai": "^1.0.0-beta.12",
"@clack/prompts": "^0.6.1", "@clack/prompts": "^0.6.1",
"@dqbd/tiktoken": "^1.0.2", "@dqbd/tiktoken": "^1.0.2",
"@octokit/webhooks-schemas": "^6.11.0", "@octokit/webhooks-schemas": "^6.11.0",
@@ -137,6 +138,126 @@
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
} }
}, },
"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/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.24.2", "version": "7.24.2",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz",
@@ -2349,6 +2470,17 @@
"node": ">=0.4.0" "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": { "node_modules/agentkeepalive": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
@@ -3285,7 +3417,6 @@
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "2.1.2"
}, },
@@ -4742,6 +4873,30 @@
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true "dev": true
}, },
"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/human-signals": { "node_modules/human-signals": {
"version": "4.3.1", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
+1
View File
@@ -77,6 +77,7 @@
"@actions/core": "^1.10.0", "@actions/core": "^1.10.0",
"@actions/exec": "^1.1.1", "@actions/exec": "^1.1.1",
"@actions/github": "^5.1.1", "@actions/github": "^5.1.1",
"@azure/openai": "^1.0.0-beta.12",
"@anthropic-ai/sdk": "^0.19.2", "@anthropic-ai/sdk": "^0.19.2",
"@clack/prompts": "^0.6.1", "@clack/prompts": "^0.6.1",
"@dqbd/tiktoken": "^1.0.2", "@dqbd/tiktoken": "^1.0.2",
+8 -2
View File
@@ -18,7 +18,13 @@ cli(
name: 'opencommit', name: 'opencommit',
commands: [configCommand, hookCommand, commitlintConfigCommand], commands: [configCommand, hookCommand, commitlintConfigCommand],
flags: { flags: {
fgm: Boolean fgm: Boolean,
yes: {
type: Boolean,
alias: 'y',
description: 'Skip commit confirmation prompt',
default: false
}
}, },
ignoreArgv: (type) => type === 'unknown-flag' || type === 'argument', ignoreArgv: (type) => type === 'unknown-flag' || type === 'argument',
help: { description: packageJSON.description } help: { description: packageJSON.description }
@@ -29,7 +35,7 @@ cli(
if (await isHookCalled()) { if (await isHookCalled()) {
prepareCommitMessageHook(); prepareCommitMessageHook();
} else { } else {
commit(extraArgs, false, flags.fgm); commit(extraArgs, false, flags.fgm, flags.yes);
} }
}, },
extraArgs extraArgs
+19 -4
View File
@@ -41,7 +41,8 @@ const checkMessageTemplate = (extraArgs: string[]): string | false => {
const generateCommitMessageFromGitDiff = async ( const generateCommitMessageFromGitDiff = async (
diff: string, diff: string,
extraArgs: string[], extraArgs: string[],
fullGitMojiSpec: boolean fullGitMojiSpec: boolean,
skipCommitConfirmation: boolean
): Promise<void> => { ): Promise<void> => {
await assertGitRepo(); await assertGitRepo();
const commitSpinner = spinner(); const commitSpinner = spinner();
@@ -76,7 +77,7 @@ ${commitMessage}
${chalk.grey('——————————————————')}` ${chalk.grey('——————————————————')}`
); );
const isCommitConfirmedByUser = await confirm({ const isCommitConfirmedByUser = skipCommitConfirmation || await confirm({
message: 'Confirm the commit message?' message: 'Confirm the commit message?'
}); });
@@ -154,6 +155,18 @@ ${chalk.grey('——————————————————')}`
} else outro(`${chalk.gray('✖')} process cancelled`); } 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) { } catch (error) {
commitSpinner.stop('📝 Commit message generated'); commitSpinner.stop('📝 Commit message generated');
@@ -166,7 +179,8 @@ ${chalk.grey('——————————————————')}`
export async function commit( export async function commit(
extraArgs: string[] = [], extraArgs: string[] = [],
isStageAllFlag: Boolean = false, isStageAllFlag: Boolean = false,
fullGitMojiSpec: boolean = false fullGitMojiSpec: boolean = false,
skipCommitConfirmation: boolean = false
) { ) {
if (isStageAllFlag) { if (isStageAllFlag) {
const changedFiles = await getChangedFiles(); const changedFiles = await getChangedFiles();
@@ -238,7 +252,8 @@ export async function commit(
generateCommitMessageFromGitDiff( generateCommitMessageFromGitDiff(
await getDiff({ files: stagedFiles }), await getDiff({ files: stagedFiles }),
extraArgs, extraArgs,
fullGitMojiSpec fullGitMojiSpec,
skipCommitConfirmation
) )
); );
+43 -11
View File
@@ -14,6 +14,7 @@ import { getI18nLocal } from '../i18n';
export enum CONFIG_KEYS { export enum CONFIG_KEYS {
OCO_OPENAI_API_KEY = 'OCO_OPENAI_API_KEY', OCO_OPENAI_API_KEY = 'OCO_OPENAI_API_KEY',
OCO_ANTHROPIC_API_KEY = 'OCO_ANTHROPIC_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_INPUT = 'OCO_TOKENS_MAX_INPUT',
OCO_TOKENS_MAX_OUTPUT = 'OCO_TOKENS_MAX_OUTPUT', OCO_TOKENS_MAX_OUTPUT = 'OCO_TOKENS_MAX_OUTPUT',
OCO_OPENAI_BASE_PATH = 'OCO_OPENAI_BASE_PATH', OCO_OPENAI_BASE_PATH = 'OCO_OPENAI_BASE_PATH',
@@ -25,7 +26,8 @@ export enum CONFIG_KEYS {
OCO_PROMPT_MODULE = 'OCO_PROMPT_MODULE', OCO_PROMPT_MODULE = 'OCO_PROMPT_MODULE',
OCO_AI_PROVIDER = 'OCO_AI_PROVIDER', OCO_AI_PROVIDER = 'OCO_AI_PROVIDER',
OCO_GITPUSH = 'OCO_GITPUSH', 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 { export enum CONFIG_MODES {
@@ -83,13 +85,23 @@ export const configValidators = {
//need api key unless running locally with ollama //need api key unless running locally with ollama
validateConfig( validateConfig(
'OpenAI API_KEY', 'OpenAI API_KEY',
value || config.OCO_ANTHROPIC_API_KEY || config.OCO_AI_PROVIDER == 'ollama' || config.OCO_AI_PROVIDER == 'test', 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 API key' 'You need to provide an OpenAI/Anthropic/Azure API key'
); );
validateConfig( validateConfig(
CONFIG_KEYS.OCO_OPENAI_API_KEY, CONFIG_KEYS.OCO_OPENAI_API_KEY,
value.startsWith('sk-'), value.startsWith('sk-') || config.OCO_AI_PROVIDER != 'openai',
'Must start with "sk-"' '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; return value;
@@ -99,7 +111,7 @@ export const configValidators = {
validateConfig( validateConfig(
'ANTHROPIC_API_KEY', 'ANTHROPIC_API_KEY',
value || config.OCO_OPENAI_API_KEY || config.OCO_AI_PROVIDER == 'ollama' || config.OCO_AI_PROVIDER == 'test', 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; return value;
@@ -184,9 +196,16 @@ export const configValidators = {
[CONFIG_KEYS.OCO_MODEL](value: any, config: any = {}) { [CONFIG_KEYS.OCO_MODEL](value: any, config: any = {}) {
validateConfig( validateConfig(
CONFIG_KEYS.OCO_MODEL, CONFIG_KEYS.OCO_MODEL,
[...MODEL_LIST.openai, ...MODEL_LIST.anthropic].includes(value) || config.OCO_AI_PROVIDER == 'ollama' || config.OCO_AI_PROVIDER == 'test', [...MODEL_LIST.openai, ...MODEL_LIST.anthropic].includes(value) || config.OCO_AI_PROVIDER == 'ollama' || config.OCO_AI_PROVIDER == 'test'|| config.OCO_AI_PROVIDER == 'azure',
`${value} is not supported yet, use 'gpt-4o', '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'` `${value} is not supported yet, use 'gpt-4o', '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; return value;
}, },
@@ -224,10 +243,11 @@ export const configValidators = {
'', '',
'openai', 'openai',
'anthropic', 'anthropic',
'azure',
'ollama', 'ollama',
'test' 'test'
].includes(value), ].includes(value) || value.startsWith('ollama'),
`${value} is not supported yet, use 'ollama' 'anthropic' or 'openai' (default)` `${value} is not supported yet, use 'ollama/{model}', 'azure', 'anthropic' or 'openai' (default)`
); );
return value; return value;
}, },
@@ -239,6 +259,15 @@ export const configValidators = {
'Must be true or false' '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; return value;
}, },
}; };
@@ -261,6 +290,7 @@ export const getConfig = ({
const configFromEnv = { const configFromEnv = {
OCO_OPENAI_API_KEY: process.env.OCO_OPENAI_API_KEY, OCO_OPENAI_API_KEY: process.env.OCO_OPENAI_API_KEY,
OCO_ANTHROPIC_API_KEY: process.env.OCO_ANTHROPIC_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 OCO_TOKENS_MAX_INPUT: process.env.OCO_TOKENS_MAX_INPUT
? Number(process.env.OCO_TOKENS_MAX_INPUT) ? Number(process.env.OCO_TOKENS_MAX_INPUT)
: undefined, : undefined,
@@ -277,7 +307,9 @@ export const getConfig = ({
OCO_PROMPT_MODULE: process.env.OCO_PROMPT_MODULE || 'conventional-commit', OCO_PROMPT_MODULE: process.env.OCO_PROMPT_MODULE || 'conventional-commit',
OCO_AI_PROVIDER: process.env.OCO_AI_PROVIDER || 'openai', OCO_AI_PROVIDER: process.env.OCO_AI_PROVIDER || 'openai',
OCO_GITPUSH: process.env.OCO_GITPUSH === 'false' ? false : true, 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); const configExists = existsSync(configPath);
@@ -302,7 +334,7 @@ export const getConfig = ({
config[configKey] = validValue; config[configKey] = validValue;
} catch (error) { } catch (error) {
outro(`Unknown '${configKey}' config option.`); outro(`Unknown '${configKey}' config option or missing validator.`);
outro( outro(
`Manually fix the '.env' file or global '~/.opencommit' config file.` `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(); 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( 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();
+7 -2
View File
@@ -9,10 +9,15 @@ import {
const config = getConfig(); const config = getConfig();
export class OllamaAi implements AiEngine { export class OllamaAi implements AiEngine {
private model = "mistral"; // as default model of Ollama
setModel(model: string) {
this.model = model ?? config?.OCO_MODEL ?? 'mistral';
}
async generateCommitMessage( async generateCommitMessage(
messages: Array<ChatCompletionRequestMessage> messages: Array<ChatCompletionRequestMessage>
): Promise<string | undefined> { ): Promise<string | undefined> {
const model = config?.OCO_MODEL || 'mistral'; const model = this.model;
//console.log(messages); //console.log(messages);
//process.exit() //process.exit()
@@ -21,7 +26,7 @@ export class OllamaAi implements AiEngine {
const p = { const p = {
model, model,
messages, messages,
options: {temperature: 0, top_p: 0.1}, options: { temperature: 0, top_p: 0.1 },
stream: false stream: false
}; };
try { try {
+12 -6
View File
@@ -10,8 +10,11 @@ import { tokenCount } from './utils/tokenCount';
import { getEngine } from './utils/engine'; import { getEngine } from './utils/engine';
const config = getConfig(); const config = getConfig();
const MAX_TOKENS_INPUT = config?.OCO_TOKENS_MAX_INPUT || DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_INPUT; const MAX_TOKENS_INPUT =
const MAX_TOKENS_OUTPUT = config?.OCO_TOKENS_MAX_OUTPUT || DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_OUTPUT; 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 ( const generateCommitMessageChatCompletionPrompt = async (
diff: string, diff: string,
@@ -71,9 +74,12 @@ export const generateCommitMessageByDiff = async (
return commitMessages.join('\n\n'); 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); const commitMessage = await engine.generateCommitMessage(messages);
if (!commitMessage) if (!commitMessage)
@@ -112,7 +118,7 @@ function getMessagesPromisesByChangesInFile(
} }
} }
const engine = getEngine() const engine = getEngine();
const commitMsgsFromFileLineDiffs = lineDiffsWithHeader.map( const commitMsgsFromFileLineDiffs = lineDiffsWithHeader.map(
async (lineDiff) => { async (lineDiff) => {
const messages = await generateCommitMessageChatCompletionPrompt( const messages = await generateCommitMessageChatCompletionPrompt(
@@ -194,7 +200,7 @@ export const getCommitMsgsPromisesFromFileDiffs = async (
fullGitMojiSpec fullGitMojiSpec
); );
const engine = getEngine() const engine = getEngine();
commitMessagePromises.push(engine.generateCommitMessage(messages)); commitMessagePromises.push(engine.generateCommitMessage(messages));
} }
} }
+1 -1
View File
@@ -55,7 +55,7 @@ export const configureCommitlintIntegration = async (force = false) => {
// consistencyPrompts.map((p) => p.content) // consistencyPrompts.map((p) => p.content)
// ); // );
const engine = getEngine() const engine = getEngine();
let consistency = let consistency =
(await engine.generateCommitMessage(consistencyPrompts)) || '{}'; (await engine.generateCommitMessage(consistencyPrompts)) || '{}';
+2 -2
View File
@@ -18,7 +18,7 @@ export const removeDoubleNewlines = (input: string): string => {
export const getJSONBlock = (input: string): string => { export const getJSONBlock = (input: string): string => {
const jsonIndex = input.search('```json'); const jsonIndex = input.search('```json');
if(jsonIndex > -1) { if (jsonIndex > -1) {
input = input.slice(jsonIndex + 8); input = input.slice(jsonIndex + 8);
const endJsonIndex = consistency.search('```'); const endJsonIndex = consistency.search('```');
input = input.slice(0, endJsonIndex); input = input.slice(0, endJsonIndex);
@@ -54,4 +54,4 @@ export const getCommitlintLLMConfig =
content.toString() content.toString()
) as CommitlintLLMConfig; ) as CommitlintLLMConfig;
return commitLintLLMConfig; return commitLintLLMConfig;
}; };
+1 -1
View File
@@ -118,7 +118,7 @@ const INIT_MAIN_PROMPT = (
${ ${
config?.OCO_ONE_LINE_COMMIT 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.' ? '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.` Use the present tense. Lines must not be longer than 74 characters. Use ${language} for the commit message.`
}); });
+9 -2
View File
@@ -2,18 +2,25 @@ import { AiEngine } from '../engine/Engine';
import { api } from '../engine/openAi'; import { api } from '../engine/openAi';
import { getConfig } from '../commands/config'; import { getConfig } from '../commands/config';
import { ollamaAi } from '../engine/ollama'; import { ollamaAi } from '../engine/ollama';
import { azure } from '../engine/azure';
import { anthropicAi } from '../engine/anthropic' import { anthropicAi } from '../engine/anthropic'
import { testAi } from '../engine/testAi'; import { testAi } from '../engine/testAi';
export function getEngine(): AiEngine { export function getEngine(): AiEngine {
const config = getConfig(); const config = getConfig();
if (config?.OCO_AI_PROVIDER == 'ollama') { const provider = config?.OCO_AI_PROVIDER;
if (provider?.startsWith('ollama')) {
const model = provider.split('/')[1];
if (model) ollamaAi.setModel(model);
return ollamaAi; return ollamaAi;
} else if (config?.OCO_AI_PROVIDER == 'anthropic') { } else if (config?.OCO_AI_PROVIDER == 'anthropic') {
return anthropicAi; return anthropicAi;
} else if (config?.OCO_AI_PROVIDER == 'test') { } else if (config?.OCO_AI_PROVIDER == 'test') {
return testAi; return testAi;
} else if (config?.OCO_AI_PROVIDER == 'azure') {
return azure;
} }
//open ai gpt by default // open ai gpt by default
return api; return api;
} }
+2 -2
View File
@@ -1,5 +1,5 @@
import { outro } from "@clack/prompts"; import { outro } from '@clack/prompts';
import { execa } from "execa"; import { execa } from 'execa';
export const getOpenCommitLatestVersion = async (): Promise< export const getOpenCommitLatestVersion = async (): Promise<
string | undefined 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.email "test@example.com"
RUN git config --global user.name "Test User" RUN git config --global user.name "Test User"
COPY . /app
WORKDIR /app WORKDIR /app
COPY package.json /app/
COPY package-lock.json /app/
RUN ls -la RUN ls -la
RUN npm install RUN npm ci
COPY . /app
RUN ls -la
RUN npm run build RUN npm run build