Skip to content

Commit f6de2dc

Browse files
authored
Merge pull request #467 from EmilienMottet/master
Add OCO_API_CUSTOM_HEADERS
2 parents 25c6a0d + 6aae1c7 commit f6de2dc

File tree

7 files changed

+94
-7
lines changed

7 files changed

+94
-7
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ Create a `.env` file and add OpenCommit config variables there like this:
109109
OCO_AI_PROVIDER=<openai (default), anthropic, azure, ollama, gemini, flowise, deepseek>
110110
OCO_API_KEY=<your OpenAI API token> // or other LLM provider API token
111111
OCO_API_URL=<may be used to set proxy path to OpenAI api>
112+
OCO_API_CUSTOM_HEADERS=<JSON string of custom HTTP headers to include in API requests>
112113
OCO_TOKENS_MAX_INPUT=<max model token limit (default: 4096)>
113114
OCO_TOKENS_MAX_OUTPUT=<max response tokens (default: 500)>
114115
OCO_DESCRIPTION=<postface a message with ~3 sentences description of the changes>

src/commands/config.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export enum CONFIG_KEYS {
2525
OCO_ONE_LINE_COMMIT = 'OCO_ONE_LINE_COMMIT',
2626
OCO_TEST_MOCK_TYPE = 'OCO_TEST_MOCK_TYPE',
2727
OCO_API_URL = 'OCO_API_URL',
28+
OCO_API_CUSTOM_HEADERS = 'OCO_API_CUSTOM_HEADERS',
2829
OCO_OMIT_SCOPE = 'OCO_OMIT_SCOPE',
2930
OCO_GITPUSH = 'OCO_GITPUSH' // todo: deprecate
3031
}
@@ -204,6 +205,22 @@ export const configValidators = {
204205
return value;
205206
},
206207

208+
[CONFIG_KEYS.OCO_API_CUSTOM_HEADERS](value) {
209+
try {
210+
// Custom headers must be a valid JSON string
211+
if (typeof value === 'string') {
212+
JSON.parse(value);
213+
}
214+
return value;
215+
} catch (error) {
216+
validateConfig(
217+
CONFIG_KEYS.OCO_API_CUSTOM_HEADERS,
218+
false,
219+
'Must be a valid JSON string of headers'
220+
);
221+
}
222+
},
223+
207224
[CONFIG_KEYS.OCO_TOKENS_MAX_INPUT](value: any) {
208225
value = parseInt(value);
209226
validateConfig(
@@ -380,6 +397,7 @@ export type ConfigType = {
380397
[CONFIG_KEYS.OCO_TOKENS_MAX_INPUT]: number;
381398
[CONFIG_KEYS.OCO_TOKENS_MAX_OUTPUT]: number;
382399
[CONFIG_KEYS.OCO_API_URL]?: string;
400+
[CONFIG_KEYS.OCO_API_CUSTOM_HEADERS]?: string;
383401
[CONFIG_KEYS.OCO_DESCRIPTION]: boolean;
384402
[CONFIG_KEYS.OCO_EMOJI]: boolean;
385403
[CONFIG_KEYS.OCO_WHY]: boolean;
@@ -462,6 +480,7 @@ const getEnvConfig = (envPath: string) => {
462480
OCO_MODEL: process.env.OCO_MODEL,
463481
OCO_API_URL: process.env.OCO_API_URL,
464482
OCO_API_KEY: process.env.OCO_API_KEY,
483+
OCO_API_CUSTOM_HEADERS: process.env.OCO_API_CUSTOM_HEADERS,
465484
OCO_AI_PROVIDER: process.env.OCO_AI_PROVIDER as OCO_AI_PROVIDER_ENUM,
466485

467486
OCO_TOKENS_MAX_INPUT: parseConfigVarValue(process.env.OCO_TOKENS_MAX_INPUT),

src/engine/Engine.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface AiEngineConfig {
1111
maxTokensOutput: number;
1212
maxTokensInput: number;
1313
baseURL?: string;
14+
customHeaders?: Record<string, string>;
1415
}
1516

1617
type Client =

src/engine/ollama.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,18 @@ export class OllamaEngine implements AiEngine {
1111

1212
constructor(config) {
1313
this.config = config;
14+
15+
// Combine base headers with custom headers
16+
const headers = {
17+
'Content-Type': 'application/json',
18+
...config.customHeaders
19+
};
20+
1421
this.client = axios.create({
1522
url: config.baseURL
1623
? `${config.baseURL}/${config.apiKey}`
1724
: 'http://localhost:11434/api/chat',
18-
headers: { 'Content-Type': 'application/json' }
25+
headers
1926
});
2027
}
2128

src/engine/openAi.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import axios from 'axios';
22
import { OpenAI } from 'openai';
33
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
4+
import { parseCustomHeaders } from '../utils/engine';
45
import { removeContentTags } from '../utils/removeContentTags';
56
import { tokenCount } from '../utils/tokenCount';
67
import { AiEngine, AiEngineConfig } from './Engine';
@@ -14,11 +15,22 @@ export class OpenAiEngine implements AiEngine {
1415
constructor(config: OpenAiConfig) {
1516
this.config = config;
1617

17-
if (!config.baseURL) {
18-
this.client = new OpenAI({ apiKey: config.apiKey });
19-
} else {
20-
this.client = new OpenAI({ apiKey: config.apiKey, baseURL: config.baseURL });
18+
const clientOptions: OpenAI.ClientOptions = {
19+
apiKey: config.apiKey
20+
};
21+
22+
if (config.baseURL) {
23+
clientOptions.baseURL = config.baseURL;
24+
}
25+
26+
if (config.customHeaders) {
27+
const headers = parseCustomHeaders(config.customHeaders);
28+
if (Object.keys(headers).length > 0) {
29+
clientOptions.defaultHeaders = headers;
30+
}
2131
}
32+
33+
this.client = new OpenAI(clientOptions);
2234
}
2335

2436
public generateCommitMessage = async (
@@ -42,7 +54,7 @@ export class OpenAiEngine implements AiEngine {
4254
this.config.maxTokensInput - this.config.maxTokensOutput
4355
)
4456
throw new Error(GenerateCommitMessageErrorEnum.tooMuchTokens);
45-
57+
4658
const completion = await this.client.chat.completions.create(params);
4759

4860
const message = completion.choices[0].message;

src/utils/engine.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,39 @@ import { GroqEngine } from '../engine/groq';
1212
import { MLXEngine } from '../engine/mlx';
1313
import { DeepseekEngine } from '../engine/deepseek';
1414

15+
export function parseCustomHeaders(headers: any): Record<string, string> {
16+
let parsedHeaders = {};
17+
18+
if (!headers) {
19+
return parsedHeaders;
20+
}
21+
22+
try {
23+
if (typeof headers === 'object' && !Array.isArray(headers)) {
24+
parsedHeaders = headers;
25+
} else {
26+
parsedHeaders = JSON.parse(headers);
27+
}
28+
} catch (error) {
29+
console.warn('Invalid OCO_API_CUSTOM_HEADERS format, ignoring custom headers');
30+
}
31+
32+
return parsedHeaders;
33+
}
34+
1535
export function getEngine(): AiEngine {
1636
const config = getConfig();
1737
const provider = config.OCO_AI_PROVIDER;
1838

39+
const customHeaders = parseCustomHeaders(config.OCO_API_CUSTOM_HEADERS);
40+
1941
const DEFAULT_CONFIG = {
2042
model: config.OCO_MODEL!,
2143
maxTokensOutput: config.OCO_TOKENS_MAX_OUTPUT!,
2244
maxTokensInput: config.OCO_TOKENS_MAX_INPUT!,
2345
baseURL: config.OCO_API_URL!,
24-
apiKey: config.OCO_API_KEY!
46+
apiKey: config.OCO_API_KEY!,
47+
customHeaders
2548
};
2649

2750
switch (provider) {

test/unit/config.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,30 @@ describe('config', () => {
122122
expect(config.OCO_ONE_LINE_COMMIT).toEqual(false);
123123
expect(config.OCO_OMIT_SCOPE).toEqual(true);
124124
});
125+
126+
it('should handle custom HTTP headers correctly', async () => {
127+
globalConfigFile = await generateConfig('.opencommit', {
128+
OCO_API_CUSTOM_HEADERS: '{"X-Global-Header": "global-value"}'
129+
});
130+
131+
envConfigFile = await generateConfig('.env', {
132+
OCO_API_CUSTOM_HEADERS: '{"Authorization": "Bearer token123", "X-Custom-Header": "test-value"}'
133+
});
134+
135+
const config = getConfig({
136+
globalPath: globalConfigFile.filePath,
137+
envPath: envConfigFile.filePath
138+
});
139+
140+
expect(config).not.toEqual(null);
141+
expect(config.OCO_API_CUSTOM_HEADERS).toEqual({"Authorization": "Bearer token123", "X-Custom-Header": "test-value"});
142+
143+
// No need to parse JSON again since it's already an object
144+
const parsedHeaders = config.OCO_API_CUSTOM_HEADERS;
145+
expect(parsedHeaders).toHaveProperty('Authorization', 'Bearer token123');
146+
expect(parsedHeaders).toHaveProperty('X-Custom-Header', 'test-value');
147+
expect(parsedHeaders).not.toHaveProperty('X-Global-Header');
148+
});
125149

126150
it('should handle empty local config correctly', async () => {
127151
globalConfigFile = await generateConfig('.opencommit', {

0 commit comments

Comments
 (0)