Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26,514 changes: 19,810 additions & 6,704 deletions out/cli.cjs

Large diffs are not rendered by default.

57,269 changes: 41,854 additions & 15,415 deletions out/github-action.cjs

Large diffs are not rendered by default.

Binary file modified out/tiktoken_bg.wasm
Binary file not shown.
3,090 changes: 1,777 additions & 1,313 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "opencommit",
"version": "3.0.12",
"version": "3.0.13",
"description": "Auto-generate impressive commits in 1 second. Killing lame commits with AI 🤯🔫",
"keywords": [
"git",
Expand Down Expand Up @@ -73,6 +73,7 @@
"@actions/core": "^1.10.0",
"@actions/exec": "^1.1.1",
"@actions/github": "^5.1.1",
"@anthropic-ai/sdk": "^0.19.2",
"@clack/prompts": "^0.6.1",
"@dqbd/tiktoken": "^1.0.2",
"@octokit/webhooks-schemas": "^6.11.0",
Expand Down
61 changes: 46 additions & 15 deletions src/commands/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dotenv.config();

export enum CONFIG_KEYS {
OCO_OPENAI_API_KEY = 'OCO_OPENAI_API_KEY',
OCO_ANTHROPIC_API_KEY = 'OCO_ANTHROPIC_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',
Expand All @@ -34,6 +35,31 @@ export enum CONFIG_MODES {
set = 'set'
}

export const MODEL_LIST = {
openai: ['gpt-3.5-turbo',
'gpt-3.5-turbo-0125',
'gpt-4',
'gpt-4-turbo',
'gpt-4-1106-preview',
'gpt-4-turbo-preview',
'gpt-4-0125-preview'],

anthropic: ['claude-3-haiku-20240307',
'claude-3-sonnet-20240229',
'claude-3-opus-20240229']
}

const getDefaultModel = (provider: string | undefined): string => {
switch (provider) {
case 'ollama':
return '';
case 'anthropic':
return MODEL_LIST.anthropic[0];
default:
return MODEL_LIST.openai[0];
}
};

export enum DEFAULT_TOKEN_LIMITS {
DEFAULT_MAX_TOKENS_INPUT = 4096,
DEFAULT_MAX_TOKENS_OUTPUT = 500
Expand All @@ -57,9 +83,9 @@ export const configValidators = {
[CONFIG_KEYS.OCO_OPENAI_API_KEY](value: any, config: any = {}) {
//need api key unless running locally with ollama
validateConfig(
'API_KEY',
value || config.OCO_AI_PROVIDER == 'ollama' || config.OCO_AI_PROVIDER == 'test',
'You need to provide an API key'
'OpenAI API_KEY',
value || config.OCO_ANTHROPIC_API_KEY || config.OCO_AI_PROVIDER == 'ollama' || config.OCO_AI_PROVIDER == 'test',
'You need to provide an OpenAI/Anthropic API key'
);
validateConfig(
CONFIG_KEYS.OCO_OPENAI_API_KEY,
Expand All @@ -75,6 +101,16 @@ export const configValidators = {
return value;
},

[CONFIG_KEYS.OCO_ANTHROPIC_API_KEY](value: any, config: any = {}) {
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'
);

return value;
},

[CONFIG_KEYS.OCO_DESCRIPTION](value: any) {
validateConfig(
CONFIG_KEYS.OCO_DESCRIPTION,
Expand Down Expand Up @@ -154,19 +190,12 @@ export const configValidators = {
[CONFIG_KEYS.OCO_MODEL](value: any) {
validateConfig(
CONFIG_KEYS.OCO_MODEL,
[
'gpt-3.5-turbo',
'gpt-3.5-turbo-0125',
'gpt-4',
'gpt-4-1106-preview',
'gpt-4-0125-preview',
'gpt-4-turbo',
'gpt-4-turbo-preview'
].includes(value),
`${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-0125-preview' or 'gpt-4-turbo-preview'`
[...MODEL_LIST.openai, ...MODEL_LIST.anthropic].includes(value),
`${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'`
);
return value;
},

[CONFIG_KEYS.OCO_MESSAGE_TEMPLATE_PLACEHOLDER](value: any) {
validateConfig(
CONFIG_KEYS.OCO_MESSAGE_TEMPLATE_PLACEHOLDER,
Expand Down Expand Up @@ -200,10 +229,11 @@ export const configValidators = {
[
'',
'openai',
'anthropic',
'ollama',
'test'
].includes(value),
`${value} is not supported yet, use 'ollama' or 'openai' (default)`
`${value} is not supported yet, use 'ollama' 'anthropic' or 'openai' (default)`
);
return value;
},
Expand All @@ -228,6 +258,7 @@ const configPath = pathJoin(homedir(), '.opencommit');
export const getConfig = (): ConfigType | null => {
const configFromEnv = {
OCO_OPENAI_API_KEY: process.env.OCO_OPENAI_API_KEY,
OCO_ANTHROPIC_API_KEY: process.env.OCO_ANTHROPIC_API_KEY,
OCO_TOKENS_MAX_INPUT: process.env.OCO_TOKENS_MAX_INPUT
? Number(process.env.OCO_TOKENS_MAX_INPUT)
: undefined,
Expand All @@ -237,7 +268,7 @@ export const getConfig = (): ConfigType | null => {
OCO_OPENAI_BASE_PATH: process.env.OCO_OPENAI_BASE_PATH,
OCO_DESCRIPTION: process.env.OCO_DESCRIPTION === 'true' ? true : false,
OCO_EMOJI: process.env.OCO_EMOJI === 'true' ? true : false,
OCO_MODEL: process.env.OCO_MODEL || 'gpt-3.5-turbo',
OCO_MODEL: process.env.OCO_MODEL || getDefaultModel(process.env.OCO_AI_PROVIDER),
OCO_LANGUAGE: process.env.OCO_LANGUAGE || 'en',
OCO_MESSAGE_TEMPLATE_PLACEHOLDER:
process.env.OCO_MESSAGE_TEMPLATE_PLACEHOLDER || '$msg',
Expand Down
124 changes: 124 additions & 0 deletions src/engine/anthropic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import axios from 'axios';
import chalk from 'chalk';

import Anthropic from '@anthropic-ai/sdk';
import {ChatCompletionRequestMessage} from 'openai'
import { MessageCreateParamsNonStreaming, MessageParam } from '@anthropic-ai/sdk/resources';

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';
import { MODEL_LIST } from '../commands/config';

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 provider = config?.OCO_AI_PROVIDER;
let apiKey = config?.OCO_ANTHROPIC_API_KEY;
const [command, mode] = process.argv.slice(2);
if (
provider === 'anthropic' &&
!apiKey &&
command !== 'config' &&
mode !== CONFIG_MODES.set
) {
intro('opencommit');

outro(
'OCO_ANTHROPIC_API_KEY is not set, please run `oco config set OCO_ANTHROPIC_API_KEY=<your token> . If you are using Claude, 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;
if (provider === 'anthropic' &&
!MODEL_LIST.anthropic.includes(MODEL) &&
command !== 'config' &&
mode !== CONFIG_MODES.set) {
outro(
`${chalk.red('✖')} Unsupported model ${MODEL} for Anthropic. Supported models are: ${MODEL_LIST.anthropic.join(
', '
)}`
);
process.exit(1);
}

class AnthropicAi implements AiEngine {
private anthropicAiApiConfiguration = {
apiKey: apiKey
};
private anthropicAI!: Anthropic;

constructor() {
this.anthropicAI = new Anthropic(this.anthropicAiApiConfiguration);
}

public generateCommitMessage = async (
messages: Array<ChatCompletionRequestMessage>
): Promise<string | undefined> => {

const systemMessage = messages.find(msg => msg.role === 'system')?.content as string;
const restMessages = messages.filter((msg) => msg.role !== 'system') as MessageParam[];

const params: MessageCreateParamsNonStreaming = {
model: MODEL,
system: systemMessage,
messages: restMessages,
temperature: 0,
top_p: 0.1,
max_tokens: MAX_TOKENS_OUTPUT
};
try {
const REQUEST_TOKENS = messages
.map((msg) => tokenCount(msg.content as string) + 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.anthropicAI.messages.create(params);

const message = data?.content[0].text;

return message;
} catch (error) {
outro(`${chalk.red('✖')} ${JSON.stringify(params)}`);

const err = error as Error;
outro(`${chalk.red('✖')} ${err?.message || err}`);

if (
axios.isAxiosError<{ error?: { message: string } }>(error) &&
error.response?.status === 401
) {
const anthropicAiError = error.response.data.error;

if (anthropicAiError?.message) outro(anthropicAiError.message);
outro(
'For help look into README https://github.com/di-sukharev/opencommit#setup'
);
}

throw err;
}
};
}

export const anthropicAi = new AnthropicAi();
35 changes: 27 additions & 8 deletions src/engine/openAi.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import axios from 'axios';
import chalk from 'chalk';
import { execa } from 'execa';

import {
ChatCompletionRequestMessage,
Configuration as OpenAiApiConfiguration,
Expand All @@ -17,20 +18,28 @@ import {
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
import { tokenCount } from '../utils/tokenCount';
import { AiEngine } from './Engine';
import { MODEL_LIST } from '../commands/config';

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;
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_OPENAI_API_KEY
let apiKey = config?.OCO_OPENAI_API_KEY;

const [command, mode] = process.argv.slice(2);

const isLocalModel = config?.OCO_AI_PROVIDER == 'ollama' || config?.OCO_AI_PROVIDER == 'test';

const provider = config?.OCO_AI_PROVIDER;

if (!apiKey && command !== 'config' && mode !== CONFIG_MODES.set && !isLocalModel) {
if (
provider === 'openai' &&
!apiKey &&
command !== 'config' &&
mode !== CONFIG_MODES.set
) {
intro('opencommit');

outro(
Expand All @@ -44,6 +53,18 @@ if (!apiKey && command !== 'config' && mode !== CONFIG_MODES.set && !isLocalMode
}

const MODEL = config?.OCO_MODEL || 'gpt-3.5-turbo';
if (provider === 'openai' &&
!MODEL_LIST.openai.includes(MODEL) &&
command !== 'config' &&
mode !== CONFIG_MODES.set) {
outro(
`${chalk.red('✖')} Unsupported model ${MODEL} for OpenAI. Supported models are: ${MODEL_LIST.openai.join(
', '
)}`
);

process.exit(1);
}

class OpenAi implements AiEngine {
private openAiApiConfiguration = new OpenAiApiConfiguration({
Expand Down Expand Up @@ -105,6 +126,4 @@ class OpenAi implements AiEngine {
};
}



export const api = new OpenAi();
3 changes: 3 additions & 0 deletions src/utils/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import { AiEngine } from '../engine/Engine';
import { api } from '../engine/openAi';
import { getConfig } from '../commands/config';
import { ollamaAi } from '../engine/ollama';
import { anthropicAi } from '../engine/anthropic'
import { testAi } from '../engine/testAi';

export function getEngine(): AiEngine {
const config = getConfig();
if (config?.OCO_AI_PROVIDER == 'ollama') {
return ollamaAi;
} else if (config?.OCO_AI_PROVIDER == 'anthropic') {
return anthropicAi;
} else if (config?.OCO_AI_PROVIDER == 'test') {
return testAi;
}
Expand Down