From a313cf38791f48bed545ff214ba904e1bf5ebb5b Mon Sep 17 00:00:00 2001 From: Ted Olsen Date: Tue, 13 Feb 2024 17:01:12 -0500 Subject: [PATCH 1/6] Remove hostname configuration from README.md (+3 squashed commits) Squashed commits: [781aaf3] allow equals in path [296b7f0] trying to add path [0a127ab] Add hostname configuration in API calls and tests --- src/commands/aicommits.ts | 2 ++ src/commands/config.ts | 5 ++- src/commands/prepare-commit-msg-hook.ts | 2 ++ src/utils/config.ts | 16 ++++++++-- src/utils/openai.ts | 23 ++++++++++++-- tests/specs/config.ts | 37 ++++++++++++++++------ tests/specs/openai/conventional-commits.ts | 3 +- 7 files changed, 71 insertions(+), 17 deletions(-) diff --git a/src/commands/aicommits.ts b/src/commands/aicommits.ts index e76739cb..442870ca 100644 --- a/src/commands/aicommits.ts +++ b/src/commands/aicommits.ts @@ -73,6 +73,8 @@ export default async ( config['max-length'], config.type, config.timeout, + config.hostname, + config.path, config.proxy ); } finally { diff --git a/src/commands/config.ts b/src/commands/config.ts index 408cf64c..a87be992 100644 --- a/src/commands/config.ts +++ b/src/commands/config.ts @@ -25,7 +25,10 @@ export default command( if (mode === 'set') { await setConfigs( - keyValues.map((keyValue) => keyValue.split('=') as [string, string]) + keyValues.map((keyValue) => { + const equals = keyValue.indexOf('=') + return [keyValue.substring(0,equals), keyValue.substring(equals+1)] + }) ); return; } diff --git a/src/commands/prepare-commit-msg-hook.ts b/src/commands/prepare-commit-msg-hook.ts index 234b6872..43807191 100644 --- a/src/commands/prepare-commit-msg-hook.ts +++ b/src/commands/prepare-commit-msg-hook.ts @@ -48,6 +48,8 @@ export default () => config['max-length'], config.type, config.timeout, + config.hostname, + config.path, config.proxy ); } finally { diff --git a/src/utils/config.ts b/src/utils/config.ts index 0e07a59f..54f05c73 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -27,8 +27,6 @@ const configParsers = { 'Please set your OpenAI API key via `aicommits config set OPENAI_KEY=`' ); } - parseAssert('OPENAI_KEY', key.startsWith('sk-'), 'Must start with "sk-"'); - // Key can range from 43~51 characters. There's no spec to assert this. return key; }, @@ -115,6 +113,20 @@ const configParsers = { return parsed; }, + hostname(hostname?: string) { + if (!hostname) { + return 'api.openai.com'; + } + + return hostname; + }, + path(path?: string) { + if (!path) { + return '/v1/chat/completions'; + } + + return path; + }, } as const; type ConfigKeys = keyof typeof configParsers; diff --git a/src/utils/openai.ts b/src/utils/openai.ts index d8c767ed..6ea08501 100644 --- a/src/utils/openai.ts +++ b/src/utils/openai.ts @@ -71,13 +71,26 @@ const createChatCompletion = async ( apiKey: string, json: CreateChatCompletionRequest, timeout: number, + hostname: string, + path: string, proxy?: string ) => { + + console.log({ + hostname:hostname, + path:path, + headers:{ + "api-key": `${apiKey}`, + }, + json:json, + timeout:timeout, + proxy:proxy}); + const { response, data } = await httpsPost( - 'api.openai.com', - '/v1/chat/completions', + hostname, + path, { - Authorization: `Bearer ${apiKey}`, + "api-key": `${apiKey}`, }, json, timeout, @@ -139,6 +152,8 @@ export const generateCommitMessage = async ( maxLength: number, type: CommitType, timeout: number, + hostname: string, + path: string, proxy?: string ) => { try { @@ -165,6 +180,8 @@ export const generateCommitMessage = async ( n: completions, }, timeout, + hostname, + path, proxy ); diff --git a/tests/specs/config.ts b/tests/specs/config.ts index 9bfe45fc..6619f553 100644 --- a/tests/specs/config.ts +++ b/tests/specs/config.ts @@ -17,16 +17,6 @@ export default testSuite(({ describe }) => { expect(stderr).toMatch('Invalid config property: UNKNOWN'); }); - test('set invalid OPENAI_KEY', async () => { - const { stderr } = await aicommits(['config', 'set', 'OPENAI_KEY=abc'], { - reject: false, - }); - - expect(stderr).toMatch( - 'Invalid config property OPENAI_KEY: Must start with "sk-"' - ); - }); - await test('set config file', async () => { await aicommits(['config', 'set', openAiToken]); @@ -106,6 +96,33 @@ export default testSuite(({ describe }) => { }); }); + await describe('hostname', ({ test }) => { + test('must be an hostname', async () => { + const { stderr } = await aicommits( + ['config', 'set', 'hostname=https://api.openai.com'], + { + reject: false, + } + ); + + expect(stderr).toMatch('Must be an hostname'); + }); + + test('updates config', async () => { + const defaultConfig = await aicommits(['config', 'get', 'hostname']); + expect(defaultConfig.stdout).toBe('hostname=api.openai.com'); + + const hostname = 'hostname=api.chatanywhere.com.cn'; + await aicommits(['config', 'set', hostname]); + + const configFile = await fs.readFile(configPath, 'utf8'); + expect(configFile).toMatch(hostname); + + const get = await aicommits(['config', 'get', 'hostname']); + expect(get.stdout).toBe(hostname); + }); + }); + await test('set config file', async () => { await aicommits(['config', 'set', openAiToken]); diff --git a/tests/specs/openai/conventional-commits.ts b/tests/specs/openai/conventional-commits.ts index 7a3e43e6..c27fd695 100644 --- a/tests/specs/openai/conventional-commits.ts +++ b/tests/specs/openai/conventional-commits.ts @@ -146,7 +146,8 @@ export default testSuite(({ describe }) => { config.generate, config['max-length'], config.type, - 7000 + 7000, + config.hostname ); return commitMessages[0]; From 51d14f02caae53ff475069d9066f1ef387c850c3 Mon Sep 17 00:00:00 2001 From: Ted Olsen Date: Tue, 13 Feb 2024 19:30:20 -0500 Subject: [PATCH 2/6] Add authHeaderName to aicommits config --- src/commands/aicommits.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commands/aicommits.ts b/src/commands/aicommits.ts index 442870ca..e3d3bef6 100644 --- a/src/commands/aicommits.ts +++ b/src/commands/aicommits.ts @@ -73,6 +73,7 @@ export default async ( config['max-length'], config.type, config.timeout, + config.authHeaderName, config.hostname, config.path, config.proxy From c2d936f3457e0c911e8395a7f907c9b22a0fc834 Mon Sep 17 00:00:00 2001 From: Ted Olsen Date: Wed, 21 Feb 2024 09:25:02 -0500 Subject: [PATCH 3/6] test(config): Update hostname validation test case --- tests/specs/config.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/specs/config.ts b/tests/specs/config.ts index 6619f553..f577f25a 100644 --- a/tests/specs/config.ts +++ b/tests/specs/config.ts @@ -97,15 +97,17 @@ export default testSuite(({ describe }) => { }); await describe('hostname', ({ test }) => { - test('must be an hostname', async () => { + test('must be a hostname', async () => { const { stderr } = await aicommits( - ['config', 'set', 'hostname=https://api.openai.com'], + ['config', 'set', 'hostname=https://api.openai.com/'], { reject: false, } ); - expect(stderr).toMatch('Must be an hostname'); + expect(stderr).toMatch( + 'Do not include protocol or path in hostname, only hostname' + ); }); test('updates config', async () => { From ad7997ce5a06a225acbca6b9c017d5b4442e6912 Mon Sep 17 00:00:00 2001 From: Ted Olsen Date: Wed, 21 Feb 2024 14:39:20 -0500 Subject: [PATCH 4/6] Update config handling for API paths and headers --- src/commands/aicommits.ts | 28 +++++++++++++------- src/commands/prepare-commit-msg-hook.ts | 3 ++- src/utils/config.ts | 19 +++++++++++--- src/utils/openai.ts | 30 +++++++++------------- tests/specs/openai/conventional-commits.ts | 6 +++-- 5 files changed, 53 insertions(+), 33 deletions(-) diff --git a/src/commands/aicommits.ts b/src/commands/aicommits.ts index e3d3bef6..baaef4d7 100644 --- a/src/commands/aicommits.ts +++ b/src/commands/aicommits.ts @@ -52,19 +52,30 @@ export default async ( ); const { env } = process; - const config = await getConfig({ - OPENAI_KEY: env.OPENAI_KEY || env.OPENAI_API_KEY, - proxy: - env.https_proxy || env.HTTPS_PROXY || env.http_proxy || env.HTTP_PROXY, - generate: generate?.toString(), - type: commitType?.toString(), - }); + const config = await getConfig( + { + OPENAI_KEY: env.OPENAI_KEY || env.OPENAI_API_KEY, + authHeaderName: env.authHeaderName, + hostname: env.hostname, + apipath: env.apipath, + proxy: + env.https_proxy || + env.HTTPS_PROXY || + env.http_proxy || + env.HTTP_PROXY, + generate: generate?.toString(), + type: commitType?.toString(), + }, + false + ); + const s = spinner(); s.start('The AI is analyzing your changes'); let messages: string[]; try { messages = await generateCommitMessage( + config.authHeaderName, config.OPENAI_KEY, config.model, config.locale, @@ -73,9 +84,8 @@ export default async ( config['max-length'], config.type, config.timeout, - config.authHeaderName, config.hostname, - config.path, + config.apipath, config.proxy ); } finally { diff --git a/src/commands/prepare-commit-msg-hook.ts b/src/commands/prepare-commit-msg-hook.ts index 43807191..e40b2db6 100644 --- a/src/commands/prepare-commit-msg-hook.ts +++ b/src/commands/prepare-commit-msg-hook.ts @@ -40,6 +40,7 @@ export default () => let messages: string[]; try { messages = await generateCommitMessage( + config.authHeaderName, config.OPENAI_KEY, config.model, config.locale, @@ -49,7 +50,7 @@ export default () => config.type, config.timeout, config.hostname, - config.path, + config.apipath, config.proxy ); } finally { diff --git a/src/utils/config.ts b/src/utils/config.ts index 54f05c73..3f4aba5a 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -113,19 +113,32 @@ const configParsers = { return parsed; }, + authHeaderName(authHeaderName?: string) { + if (!authHeaderName) { + return 'Authorization'; + } + + return authHeaderName; + }, hostname(hostname?: string) { if (!hostname) { return 'api.openai.com'; } + parseAssert( + 'hostname', + !/^http/i.test(hostname) && !/\/$/.test(hostname), + 'Do not include protocol or path in hostname, only hostname' + ); + return hostname; }, - path(path?: string) { - if (!path) { + apipath(apipath?: string) { + if (!apipath) { return '/v1/chat/completions'; } - return path; + return apipath; }, } as const; diff --git a/src/utils/openai.ts b/src/utils/openai.ts index 6ea08501..17931b6f 100644 --- a/src/utils/openai.ts +++ b/src/utils/openai.ts @@ -15,7 +15,7 @@ import { generatePrompt } from './prompt.js'; const httpsPost = async ( hostname: string, - path: string, + apipath: string, headers: Record, json: unknown, timeout: number, @@ -31,7 +31,7 @@ const httpsPost = async ( { port: 443, hostname, - path, + path: apipath, method: 'POST', headers: { ...headers, @@ -68,30 +68,22 @@ const httpsPost = async ( }); const createChatCompletion = async ( + authHeaderName: string, apiKey: string, json: CreateChatCompletionRequest, timeout: number, hostname: string, - path: string, + apipath: string, proxy?: string ) => { + let headers: any = {}; + headers[authHeaderName] = `${apiKey}`; - console.log({ - hostname:hostname, - path:path, - headers:{ - "api-key": `${apiKey}`, - }, - json:json, - timeout:timeout, - proxy:proxy}); const { response, data } = await httpsPost( hostname, - path, - { - "api-key": `${apiKey}`, - }, + apipath, + headers, json, timeout, proxy @@ -144,6 +136,7 @@ const deduplicateMessages = (array: string[]) => Array.from(new Set(array)); // }; export const generateCommitMessage = async ( + authHeaderName: string, apiKey: string, model: TiktokenModel, locale: string, @@ -153,11 +146,12 @@ export const generateCommitMessage = async ( type: CommitType, timeout: number, hostname: string, - path: string, + apipath: string, proxy?: string ) => { try { const completion = await createChatCompletion( + authHeaderName, apiKey, { model, @@ -181,7 +175,7 @@ export const generateCommitMessage = async ( }, timeout, hostname, - path, + apipath, proxy ); diff --git a/tests/specs/openai/conventional-commits.ts b/tests/specs/openai/conventional-commits.ts index c27fd695..c539c0f7 100644 --- a/tests/specs/openai/conventional-commits.ts +++ b/tests/specs/openai/conventional-commits.ts @@ -3,7 +3,7 @@ import { generateCommitMessage } from '../../../src/utils/openai.js'; import type { ValidConfig } from '../../../src/utils/config.js'; import { getDiff } from '../../utils.js'; -const { OPENAI_KEY } = process.env; +const { OPENAI_KEY, authHeaderName, hostname, apipath } = process.env; export default testSuite(({ describe }) => { if (!OPENAI_KEY) { @@ -139,6 +139,7 @@ export default testSuite(({ describe }) => { ...configOverrides, } as ValidConfig; const commitMessages = await generateCommitMessage( + authHeaderName ?? 'Authorization', OPENAI_KEY!, 'gpt-3.5-turbo', config.locale, @@ -147,7 +148,8 @@ export default testSuite(({ describe }) => { config['max-length'], config.type, 7000, - config.hostname + hostname ?? 'api.openai.com', + apipath ?? '/v1/completions' ); return commitMessages[0]; From 1702d28f45b860ece0215a0e7ef2f0e617bc304c Mon Sep 17 00:00:00 2001 From: Ted Olsen Date: Wed, 21 Feb 2024 14:52:19 -0500 Subject: [PATCH 5/6] Remove unnecessary blank lines and refactor code --- src/commands/aicommits.ts | 1 - src/utils/openai.ts | 1 - tests/specs/config.ts | 4 +--- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/commands/aicommits.ts b/src/commands/aicommits.ts index baaef4d7..2cc88f33 100644 --- a/src/commands/aicommits.ts +++ b/src/commands/aicommits.ts @@ -69,7 +69,6 @@ export default async ( false ); - const s = spinner(); s.start('The AI is analyzing your changes'); let messages: string[]; diff --git a/src/utils/openai.ts b/src/utils/openai.ts index 17931b6f..3963bc23 100644 --- a/src/utils/openai.ts +++ b/src/utils/openai.ts @@ -79,7 +79,6 @@ const createChatCompletion = async ( let headers: any = {}; headers[authHeaderName] = `${apiKey}`; - const { response, data } = await httpsPost( hostname, apipath, diff --git a/tests/specs/config.ts b/tests/specs/config.ts index f577f25a..78bfa8d8 100644 --- a/tests/specs/config.ts +++ b/tests/specs/config.ts @@ -100,9 +100,7 @@ export default testSuite(({ describe }) => { test('must be a hostname', async () => { const { stderr } = await aicommits( ['config', 'set', 'hostname=https://api.openai.com/'], - { - reject: false, - } + { reject: false } ); expect(stderr).toMatch( From 7de43b8c0c5b6ac528e90304a33f55562294f6ae Mon Sep 17 00:00:00 2001 From: Ted Olsen Date: Tue, 27 Feb 2024 09:33:24 -0500 Subject: [PATCH 6/6] tomato tomato --- tests/specs/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/specs/config.ts b/tests/specs/config.ts index 78bfa8d8..bd48ea34 100644 --- a/tests/specs/config.ts +++ b/tests/specs/config.ts @@ -97,7 +97,7 @@ export default testSuite(({ describe }) => { }); await describe('hostname', ({ test }) => { - test('must be a hostname', async () => { + test('must be an hostname', async () => { const { stderr } = await aicommits( ['config', 'set', 'hostname=https://api.openai.com/'], { reject: false }