From b6ded162753ee041015775b9d1655f515f8db479 Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Wed, 13 Mar 2024 11:13:44 +0200 Subject: [PATCH 1/2] Add support for custom OpenAI URL Allows to run local OpenAI-compatible server. Since these local instances most likely lack proper certificates, allow using non-HTTPS endpoints as well. Signed-off-by: Alexander Bokovoy --- README.md | 8 ++++++++ src/commands/aicommits.ts | 1 + src/utils/config.ts | 11 ++++++++++- src/utils/openai.ts | 22 +++++++++++++++++----- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c07a9f2e..1ea9b84f 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,14 @@ Required The OpenAI API key. You can retrieve it from [OpenAI API Keys page](https://platform.openai.com/account/api-keys). +For local OpenAI-compatible API server an empty key should be specified as `no_api_key`. + +#### OPENAI_URL + +Optional + +The OpenAI server URL. Defaults to `https://api.openai.com`. Both `https` and `http` protocols supported. + #### locale Default: `en` diff --git a/src/commands/aicommits.ts b/src/commands/aicommits.ts index e76739cb..80b83d7d 100644 --- a/src/commands/aicommits.ts +++ b/src/commands/aicommits.ts @@ -65,6 +65,7 @@ export default async ( let messages: string[]; try { messages = await generateCommitMessage( + config.OPENAI_URL, config.OPENAI_KEY, config.model, config.locale, diff --git a/src/utils/config.ts b/src/utils/config.ts index 0e07a59f..33df5236 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -27,7 +27,7 @@ const configParsers = { 'Please set your OpenAI API key via `aicommits config set OPENAI_KEY=`' ); } - parseAssert('OPENAI_KEY', key.startsWith('sk-'), 'Must start with "sk-"'); + parseAssert('OPENAI_KEY', /^((sk-|no_api_key){1}[a-zA-Z0-9]*)$/.test(key), 'Must start with "sk-" or "no_api_key"'); // Key can range from 43~51 characters. There's no spec to assert this. return key; @@ -115,6 +115,15 @@ const configParsers = { return parsed; }, + OPENAI_URL(url?: string) { + if (!url || url.length === 0) { + return undefined; + } + + parseAssert('OPENAI_URL', /^https?:\/\//.test(url), 'Must be a valid URL'); + + return url; + }, } as const; type ConfigKeys = keyof typeof configParsers; diff --git a/src/utils/openai.ts b/src/utils/openai.ts index d8c767ed..ac39e4c0 100644 --- a/src/utils/openai.ts +++ b/src/utils/openai.ts @@ -1,4 +1,5 @@ import https from 'https'; +import http from 'http'; import type { ClientRequest, IncomingMessage } from 'http'; import type { CreateChatCompletionRequest, @@ -14,7 +15,7 @@ import type { CommitType } from './config.js'; import { generatePrompt } from './prompt.js'; const httpsPost = async ( - hostname: string, + url: URL, path: string, headers: Record, json: unknown, @@ -27,10 +28,14 @@ const httpsPost = async ( data: string; }>((resolve, reject) => { const postContent = JSON.stringify(json); - const request = https.request( + var connector = https; + if (url.protocol != 'https') { + connector = http; + } + const request = connector.request( { - port: 443, - hostname, + hostname: url.hostname, + port: Number(url.port || '443'), path, method: 'POST', headers: { @@ -68,13 +73,18 @@ const httpsPost = async ( }); const createChatCompletion = async ( + openai_url: string, apiKey: string, json: CreateChatCompletionRequest, timeout: number, proxy?: string ) => { + if (!openai_url || openai_url.length === 0) { + openai_url = 'https://api.openai.com/'; + } + const url = new URL(openai_url); const { response, data } = await httpsPost( - 'api.openai.com', + url, '/v1/chat/completions', { Authorization: `Bearer ${apiKey}`, @@ -131,6 +141,7 @@ const deduplicateMessages = (array: string[]) => Array.from(new Set(array)); // }; export const generateCommitMessage = async ( + openai_url: string, apiKey: string, model: TiktokenModel, locale: string, @@ -143,6 +154,7 @@ export const generateCommitMessage = async ( ) => { try { const completion = await createChatCompletion( + openai_url, apiKey, { model, From 42ffb62a704e4825aaf0038770b90c696023b917 Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Mon, 18 Mar 2024 12:17:48 +0200 Subject: [PATCH 2/2] Remove check for OPENAI_KEY format The API key format is specific to a particular implementation and deployment of OPENAI API. There is no way to do common checks for it, so better to remove it at all and add some general note to the documentation instead. Signed-off-by: Alexander Bokovoy --- README.md | 2 +- src/utils/config.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 1ea9b84f..4e0e0c24 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ Required The OpenAI API key. You can retrieve it from [OpenAI API Keys page](https://platform.openai.com/account/api-keys). -For local OpenAI-compatible API server an empty key should be specified as `no_api_key`. +For local OpenAI-compatible API server an empty key can be specified as `no_api_key`. Different OpenAI-compatible API servers might enforce the key structure differently. Please consult your OpenAI-compatible API provider documentation for more details. #### OPENAI_URL diff --git a/src/utils/config.ts b/src/utils/config.ts index 33df5236..fdee94bf 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -27,7 +27,6 @@ const configParsers = { 'Please set your OpenAI API key via `aicommits config set OPENAI_KEY=`' ); } - parseAssert('OPENAI_KEY', /^((sk-|no_api_key){1}[a-zA-Z0-9]*)$/.test(key), 'Must start with "sk-" or "no_api_key"'); // Key can range from 43~51 characters. There's no spec to assert this. return key;