From 42a2a39f6f645f480dadd7339c162977cb1725a4 Mon Sep 17 00:00:00 2001 From: Rocktim Date: Sat, 8 Apr 2023 17:54:47 +0530 Subject: [PATCH] feat: request timeout config (#191) Co-authored-by: Hiroki Osame --- README.md | 13 +++++++---- src/commands/aicommits.ts | 1 + src/commands/prepare-commit-msg-hook.ts | 1 + src/utils/config.ts | 12 ++++++++++ src/utils/openai.ts | 9 ++++++-- tests/specs/cli/commits.ts | 29 +++++++++++++++++++++++++ tests/specs/config.ts | 21 +++++++++++++++++- 7 files changed, 79 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4fe998e0..8cdcf43f 100644 --- a/README.md +++ b/README.md @@ -74,10 +74,6 @@ aicommits --generate # or -g > Warning: this uses more tokens, meaning it costs more. -```sh -aicommits --all -``` - ### Git hook You can also integrate _aicommits_ with Git via the [`prepare-commit-msg`](https://git-scm.com/docs/githooks#_prepare_commit_msg) hook. This lets you use Git like you normally would, and edit the commit message before committing. @@ -189,6 +185,15 @@ The Chat Completions (`/v1/chat/completions`) model to use. Consult the list of > Tip: If you have access, try upgrading to [`gpt-4`](https://platform.openai.com/docs/models/gpt-4) for next-level code analysis. It can handle double the input size, but comes at a higher cost. Check out OpenAI's website to learn more. +#### timeout +The timeout for network requests to the OpenAI API in milliseconds. + +Default: `10000` (10 seconds) + +```sh +aicommits config set timeout=20000 # 20s +``` + ## How it works This CLI tool runs `git diff` to grab all your latest code changes, sends them to OpenAI's GPT-3, then returns the AI generated commit message. diff --git a/src/commands/aicommits.ts b/src/commands/aicommits.ts index 06bda6db..0445be75 100644 --- a/src/commands/aicommits.ts +++ b/src/commands/aicommits.ts @@ -58,6 +58,7 @@ export default async ( config.locale, staged.diff, config.generate, + config.timeout, config.proxy, ); } finally { diff --git a/src/commands/prepare-commit-msg-hook.ts b/src/commands/prepare-commit-msg-hook.ts index b2d02caf..52b7cb2c 100644 --- a/src/commands/prepare-commit-msg-hook.ts +++ b/src/commands/prepare-commit-msg-hook.ts @@ -45,6 +45,7 @@ export default () => (async () => { config.locale, staged!.diff, config.generate, + config.timeout, config.proxy, ); } finally { diff --git a/src/utils/config.ts b/src/utils/config.ts index 94806dcc..28c609be 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -68,6 +68,18 @@ const configParsers = { return model as TiktokenModel; }, + timeout(timeout?: string) { + if (!timeout) { + return 10_000; + } + + parseAssert('timeout', /^\d+$/.test(timeout), 'Must be an integer'); + + const parsed = Number(timeout); + parseAssert('timeout', parsed >= 500, 'Must be greater than 500ms'); + + return parsed; + }, } as const; type ConfigKeys = keyof typeof configParsers; diff --git a/src/utils/openai.ts b/src/utils/openai.ts index a20bb244..435aa930 100644 --- a/src/utils/openai.ts +++ b/src/utils/openai.ts @@ -10,6 +10,7 @@ const httpsPost = async ( path: string, headers: Record, json: unknown, + timeout: number, proxy?: string, ) => new Promise<{ request: ClientRequest; @@ -28,7 +29,7 @@ const httpsPost = async ( 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postContent), }, - timeout: 10_000, // 10s + timeout, agent: ( proxy ? createHttpsProxyAgent(proxy) @@ -50,7 +51,7 @@ const httpsPost = async ( request.on('error', reject); request.on('timeout', () => { request.destroy(); - reject(new KnownError('Request timed out')); + reject(new KnownError(`Time out error: request took over ${timeout}ms. Try increasing the \`timeout\` config, or checking the OpenAI API status https://status.openai.com`)); }); request.write(postContent); @@ -60,6 +61,7 @@ const httpsPost = async ( const createChatCompletion = async ( apiKey: string, json: CreateChatCompletionRequest, + timeout: number, proxy?: string, ) => { const { response, data } = await httpsPost( @@ -69,6 +71,7 @@ const createChatCompletion = async ( Authorization: `Bearer ${apiKey}`, }, json, + timeout, proxy, ); @@ -105,6 +108,7 @@ export const generateCommitMessage = async ( locale: string, diff: string, completions: number, + timeout: number, proxy?: string, ) => { const prompt = getPrompt(locale, diff); @@ -126,6 +130,7 @@ export const generateCommitMessage = async ( stream: false, n: completions, }, + timeout, proxy, ); diff --git a/tests/specs/cli/commits.ts b/tests/specs/cli/commits.ts index 4662827a..6b6876f0 100644 --- a/tests/specs/cli/commits.ts +++ b/tests/specs/cli/commits.ts @@ -252,5 +252,34 @@ export default testSuite(({ describe }) => { await fixture.rm(); }); }); + + test('Fails on timeout', async () => { + const { fixture, aicommits } = await createFixture({ + ...files, + '.aicommits': `${files['.aicommits']}\ntimeout=500`, + }); + const git = await createGit(fixture.path); + + await git('add', ['data.json']); + + const committing = aicommits([], { + reject: false, + }); + + committing.stdout!.on('data', (buffer: Buffer) => { + const stdout = buffer.toString(); + if (stdout.match('└')) { + committing.stdin!.write('y'); + committing.stdin!.end(); + } + }); + + const { stdout, exitCode } = await committing; + + expect(exitCode).toBe(1); + expect(stdout).toMatch('Time out error: request took over 500ms.'); + + await fixture.rm(); + }); }); }); diff --git a/tests/specs/config.ts b/tests/specs/config.ts index d78b6c46..ec19f5f8 100644 --- a/tests/specs/config.ts +++ b/tests/specs/config.ts @@ -4,7 +4,7 @@ import { testSuite, expect } from 'manten'; import { createFixture } from '../utils.js'; export default testSuite(({ describe }) => { - describe('config', async ({ test }) => { + describe('config', async ({ test, describe }) => { const { fixture, aicommits } = await createFixture(); const configPath = path.join(fixture.path, '.aicommits'); const openAiToken = 'OPENAI_KEY=sk-abc'; @@ -49,6 +49,25 @@ export default testSuite(({ describe }) => { expect(stderr).toBe(''); }); + await describe('timeout', ({ test }) => { + test('setting invalid timeout config', async () => { + const { stderr } = await aicommits(['config', 'set', 'timeout=abc'], { + reject: false, + }); + + expect(stderr).toMatch('Must be an integer'); + }); + + test('setting valid timeout config', async () => { + const timeout = 'timeout=20000'; + await aicommits(['config', 'set', timeout]); + + const configFile = await fs.readFile(configPath, 'utf8'); + + expect(configFile).toMatch(timeout); + }); + }); + await fixture.rm(); }); });