feat: HTTP + HTTPS proxy support (#139)
Co-authored-by: Hiroki Osame <hiroki.osame@gmail.com>
This commit is contained in:
10
README.md
10
README.md
@@ -165,7 +165,15 @@ The number of commit messages to generate to pick from.
|
|||||||
|
|
||||||
Note, this will use more tokens as it generates more results.
|
Note, this will use more tokens as it generates more results.
|
||||||
|
|
||||||
This can also be configured with the CLI flag `--generate`.
|
#### proxy
|
||||||
|
|
||||||
|
Set a HTTP/HTTPS proxy to use for requests.
|
||||||
|
|
||||||
|
To clear the proxy option, you can use the command (note the empty value after the equals sign):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
aicommits config set proxy=
|
||||||
|
```
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
"eslint": "^8.35.0",
|
"eslint": "^8.35.0",
|
||||||
"execa": "^7.0.0",
|
"execa": "^7.0.0",
|
||||||
"fs-fixture": "^1.2.0",
|
"fs-fixture": "^1.2.0",
|
||||||
|
"https-proxy-agent": "^5.0.1",
|
||||||
"ini": "^3.0.1",
|
"ini": "^3.0.1",
|
||||||
"kolorist": "^1.7.0",
|
"kolorist": "^1.7.0",
|
||||||
"lint-staged": "^13.1.2",
|
"lint-staged": "^13.1.2",
|
||||||
|
|||||||
21
pnpm-lock.yaml
generated
21
pnpm-lock.yaml
generated
@@ -17,6 +17,7 @@ specifiers:
|
|||||||
eslint: ^8.35.0
|
eslint: ^8.35.0
|
||||||
execa: ^7.0.0
|
execa: ^7.0.0
|
||||||
fs-fixture: ^1.2.0
|
fs-fixture: ^1.2.0
|
||||||
|
https-proxy-agent: ^5.0.1
|
||||||
ini: ^3.0.1
|
ini: ^3.0.1
|
||||||
kolorist: ^1.7.0
|
kolorist: ^1.7.0
|
||||||
lint-staged: ^13.1.2
|
lint-staged: ^13.1.2
|
||||||
@@ -41,6 +42,7 @@ devDependencies:
|
|||||||
eslint: 8.35.0
|
eslint: 8.35.0
|
||||||
execa: 7.0.0
|
execa: 7.0.0
|
||||||
fs-fixture: 1.2.0
|
fs-fixture: 1.2.0
|
||||||
|
https-proxy-agent: 5.0.1
|
||||||
ini: 3.0.1
|
ini: 3.0.1
|
||||||
kolorist: 1.7.0
|
kolorist: 1.7.0
|
||||||
lint-staged: 13.1.2
|
lint-staged: 13.1.2
|
||||||
@@ -787,6 +789,15 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/agent-base/6.0.2:
|
||||||
|
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||||
|
engines: {node: '>= 6.0.0'}
|
||||||
|
dependencies:
|
||||||
|
debug: 4.3.4
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: true
|
||||||
|
|
||||||
/aggregate-error/3.1.0:
|
/aggregate-error/3.1.0:
|
||||||
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
|
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -2074,6 +2085,16 @@ packages:
|
|||||||
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
|
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/https-proxy-agent/5.0.1:
|
||||||
|
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
dependencies:
|
||||||
|
agent-base: 6.0.2
|
||||||
|
debug: 4.3.4
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: true
|
||||||
|
|
||||||
/human-signals/3.0.1:
|
/human-signals/3.0.1:
|
||||||
resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==}
|
resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==}
|
||||||
engines: {node: '>=12.20.0'}
|
engines: {node: '>=12.20.0'}
|
||||||
|
|||||||
@@ -35,8 +35,10 @@ export default async (
|
|||||||
staged.files.map(file => ` ${file}`).join('\n')
|
staged.files.map(file => ` ${file}`).join('\n')
|
||||||
}`);
|
}`);
|
||||||
|
|
||||||
|
const { env } = process;
|
||||||
const config = await getConfig({
|
const config = await getConfig({
|
||||||
OPENAI_KEY: process.env.OPENAI_KEY ?? process.env.OPENAI_API_KEY,
|
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(),
|
generate: generate?.toString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -49,6 +51,7 @@ export default async (
|
|||||||
config.locale,
|
config.locale,
|
||||||
staged.diff,
|
staged.diff,
|
||||||
config.generate,
|
config.generate,
|
||||||
|
config.proxy,
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
s.stop('Changes analyzed');
|
s.stop('Changes analyzed');
|
||||||
|
|||||||
@@ -30,7 +30,10 @@ export default () => (async () => {
|
|||||||
|
|
||||||
intro(bgCyan(black(' aicommits ')));
|
intro(bgCyan(black(' aicommits ')));
|
||||||
|
|
||||||
const config = await getConfig();
|
const { env } = process;
|
||||||
|
const config = await getConfig({
|
||||||
|
proxy: env.https_proxy || env.HTTPS_PROXY || env.http_proxy || env.HTTP_PROXY,
|
||||||
|
});
|
||||||
|
|
||||||
const s = spinner();
|
const s = spinner();
|
||||||
s.start('The AI is analyzing your changes');
|
s.start('The AI is analyzing your changes');
|
||||||
@@ -41,6 +44,7 @@ export default () => (async () => {
|
|||||||
config.locale,
|
config.locale,
|
||||||
staged!.diff,
|
staged!.diff,
|
||||||
config.generate,
|
config.generate,
|
||||||
|
config.proxy,
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
s.stop('Changes analyzed');
|
s.stop('Changes analyzed');
|
||||||
|
|||||||
@@ -51,6 +51,15 @@ const configParsers = {
|
|||||||
|
|
||||||
return parsed;
|
return parsed;
|
||||||
},
|
},
|
||||||
|
proxy(url?: string) {
|
||||||
|
if (!url || url.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
parseAssert('proxy', /^https?:\/\//.test(url), 'Must be a valid URL');
|
||||||
|
|
||||||
|
return url;
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
type ConfigKeys = keyof typeof configParsers;
|
type ConfigKeys = keyof typeof configParsers;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import https from 'https';
|
|||||||
import type { ClientRequest, IncomingMessage } from 'http';
|
import type { ClientRequest, IncomingMessage } from 'http';
|
||||||
import type { CreateChatCompletionRequest, CreateChatCompletionResponse } from 'openai';
|
import type { CreateChatCompletionRequest, CreateChatCompletionResponse } from 'openai';
|
||||||
import { encoding_for_model as encodingForModel } from '@dqbd/tiktoken';
|
import { encoding_for_model as encodingForModel } from '@dqbd/tiktoken';
|
||||||
|
import createHttpsProxyAgent from 'https-proxy-agent';
|
||||||
import { KnownError } from './error.js';
|
import { KnownError } from './error.js';
|
||||||
|
|
||||||
const httpsPost = async (
|
const httpsPost = async (
|
||||||
@@ -9,6 +10,7 @@ const httpsPost = async (
|
|||||||
path: string,
|
path: string,
|
||||||
headers: Record<string, string>,
|
headers: Record<string, string>,
|
||||||
json: unknown,
|
json: unknown,
|
||||||
|
proxy?: string,
|
||||||
) => new Promise<{
|
) => new Promise<{
|
||||||
request: ClientRequest;
|
request: ClientRequest;
|
||||||
response: IncomingMessage;
|
response: IncomingMessage;
|
||||||
@@ -27,6 +29,11 @@ const httpsPost = async (
|
|||||||
'Content-Length': Buffer.byteLength(postContent),
|
'Content-Length': Buffer.byteLength(postContent),
|
||||||
},
|
},
|
||||||
timeout: 10_000, // 10s
|
timeout: 10_000, // 10s
|
||||||
|
agent: (
|
||||||
|
proxy
|
||||||
|
? createHttpsProxyAgent(proxy)
|
||||||
|
: undefined
|
||||||
|
),
|
||||||
},
|
},
|
||||||
(response) => {
|
(response) => {
|
||||||
const body: Buffer[] = [];
|
const body: Buffer[] = [];
|
||||||
@@ -53,6 +60,7 @@ const httpsPost = async (
|
|||||||
const createChatCompletion = async (
|
const createChatCompletion = async (
|
||||||
apiKey: string,
|
apiKey: string,
|
||||||
json: CreateChatCompletionRequest,
|
json: CreateChatCompletionRequest,
|
||||||
|
proxy?: string,
|
||||||
) => {
|
) => {
|
||||||
const { response, data } = await httpsPost(
|
const { response, data } = await httpsPost(
|
||||||
'api.openai.com',
|
'api.openai.com',
|
||||||
@@ -61,6 +69,7 @@ const createChatCompletion = async (
|
|||||||
Authorization: `Bearer ${apiKey}`,
|
Authorization: `Bearer ${apiKey}`,
|
||||||
},
|
},
|
||||||
json,
|
json,
|
||||||
|
proxy,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -97,6 +106,7 @@ export const generateCommitMessage = async (
|
|||||||
locale: string,
|
locale: string,
|
||||||
diff: string,
|
diff: string,
|
||||||
completions: number,
|
completions: number,
|
||||||
|
proxy?: string,
|
||||||
) => {
|
) => {
|
||||||
const prompt = getPrompt(locale, diff);
|
const prompt = getPrompt(locale, diff);
|
||||||
|
|
||||||
@@ -109,20 +119,24 @@ export const generateCommitMessage = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const completion = await createChatCompletion(apiKey, {
|
const completion = await createChatCompletion(
|
||||||
model,
|
apiKey,
|
||||||
messages: [{
|
{
|
||||||
role: 'user',
|
model,
|
||||||
content: prompt,
|
messages: [{
|
||||||
}],
|
role: 'user',
|
||||||
temperature: 0.7,
|
content: prompt,
|
||||||
top_p: 1,
|
}],
|
||||||
frequency_penalty: 0,
|
temperature: 0.7,
|
||||||
presence_penalty: 0,
|
top_p: 1,
|
||||||
max_tokens: 200,
|
frequency_penalty: 0,
|
||||||
stream: false,
|
presence_penalty: 0,
|
||||||
n: completions,
|
max_tokens: 200,
|
||||||
});
|
stream: false,
|
||||||
|
n: completions,
|
||||||
|
},
|
||||||
|
proxy,
|
||||||
|
);
|
||||||
|
|
||||||
return deduplicateMessages(
|
return deduplicateMessages(
|
||||||
completion.choices
|
completion.choices
|
||||||
|
|||||||
Reference in New Issue
Block a user