feat: add role management and usage tracking for AI code review
- Implemented role parsing and loading from markdown files, including attributes like name, side, focus, badge, color, and personality. - Created functions to build prompts for analysis, line location, and verdicts based on roles. - Added tests for role management functionalities to ensure correct parsing and loading of roles. - Developed usage tracking for AI assistant interactions, including token usage and rate limits. - Implemented functions to extract and record usage data from various LLM providers. - Added tests for usage tracking functionalities to validate correct accumulation and reporting of usage statistics.
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import { describe, it, beforeEach, afterEach } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { getLLMConfig, shouldSkipOpenCodeTLSVerify } from './config.js';
|
||||
|
||||
const ENV_KEYS = [
|
||||
'OPENAI_API_KEY', 'OPENAI_BASE_URL', 'OPENAI_MODEL',
|
||||
'CLAUDE_API_KEY', 'CLAUDE_BASE_URL', 'CLAUDE_MODEL',
|
||||
'GEMINI_API_KEY', 'GEMINI_BASE_URL', 'GEMINI_MODEL',
|
||||
'OLLAMA_BASE_URL', 'OLLAMA_MODEL',
|
||||
'AMAZONQ_API_KEY', 'AMAZONQ_BASE_URL', 'AMAZONQ_MODEL',
|
||||
'OPENCODE_BASE_URL', 'OPENCODE_MODEL', 'OPENCODE_PROVIDER',
|
||||
'OPENCODE_SERVER_USERNAME', 'OPENCODE_SERVER_PASSWORD',
|
||||
'OPENCODE_SKIP_TLS_VERIFY',
|
||||
];
|
||||
|
||||
let saved = {};
|
||||
beforeEach(() => {
|
||||
saved = {};
|
||||
for (const k of ENV_KEYS) { saved[k] = process.env[k]; delete process.env[k]; }
|
||||
});
|
||||
afterEach(() => {
|
||||
for (const k of ENV_KEYS) {
|
||||
if (saved[k] === undefined) delete process.env[k];
|
||||
else process.env[k] = saved[k];
|
||||
}
|
||||
});
|
||||
|
||||
describe('getLLMConfig', () => {
|
||||
it('returns null provider when no env vars set', () => {
|
||||
const cfg = getLLMConfig();
|
||||
assert.equal(cfg.provider, null);
|
||||
assert.deepEqual(cfg.apiKeys, []);
|
||||
});
|
||||
|
||||
it('detects openai with defaults', () => {
|
||||
process.env.OPENAI_API_KEY = 'sk-test';
|
||||
const cfg = getLLMConfig();
|
||||
assert.equal(cfg.provider, 'openai');
|
||||
assert.deepEqual(cfg.apiKeys, ['sk-test']);
|
||||
assert.equal(cfg.baseURL, 'https://api.openai.com/v1');
|
||||
assert.equal(cfg.model, 'gpt-4o-mini');
|
||||
});
|
||||
|
||||
it('detects openai with custom base url and model', () => {
|
||||
process.env.OPENAI_API_KEY = 'sk-test';
|
||||
process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1';
|
||||
process.env.OPENAI_MODEL = 'gpt-4o';
|
||||
const cfg = getLLMConfig();
|
||||
assert.equal(cfg.provider, 'openai');
|
||||
assert.equal(cfg.baseURL, 'https://openrouter.ai/api/v1');
|
||||
assert.equal(cfg.model, 'gpt-4o');
|
||||
});
|
||||
|
||||
it('detects gemini with comma-separated keys, picks one', () => {
|
||||
process.env.GEMINI_API_KEY = 'key1,key2,key3';
|
||||
const cfg = getLLMConfig();
|
||||
assert.equal(cfg.provider, 'gemini');
|
||||
assert.deepEqual(cfg.apiKeys, ['key1', 'key2', 'key3']);
|
||||
});
|
||||
|
||||
it('detects gemini with single key (no comma)', () => {
|
||||
process.env.GEMINI_API_KEY = 'gemini-key';
|
||||
const cfg = getLLMConfig();
|
||||
assert.equal(cfg.provider, 'gemini');
|
||||
assert.equal(cfg.model, 'gemini-2.5-flash');
|
||||
});
|
||||
|
||||
it('detects gemini with custom model', () => {
|
||||
process.env.GEMINI_API_KEY = 'gemini-key';
|
||||
process.env.GEMINI_MODEL = 'gemini-2.0-flash';
|
||||
const cfg = getLLMConfig();
|
||||
assert.equal(cfg.model, 'gemini-2.0-flash');
|
||||
});
|
||||
|
||||
it('detects claude with defaults', () => {
|
||||
process.env.CLAUDE_API_KEY = 'claude-key';
|
||||
const cfg = getLLMConfig();
|
||||
assert.equal(cfg.provider, 'claude');
|
||||
assert.equal(cfg.model, 'claude-3-haiku-20240307');
|
||||
});
|
||||
|
||||
it('detects amazonq with its own model env', () => {
|
||||
process.env.AMAZONQ_API_KEY = 'aq-key';
|
||||
process.env.AMAZONQ_MODEL = 'my-amazon-model';
|
||||
const cfg = getLLMConfig();
|
||||
assert.equal(cfg.provider, 'amazonq');
|
||||
assert.equal(cfg.model, 'my-amazon-model');
|
||||
});
|
||||
|
||||
it('detects opencode server with gemini defaults', () => {
|
||||
process.env.OPENCODE_BASE_URL = 'http://opencode.local:4096';
|
||||
const cfg = getLLMConfig();
|
||||
assert.equal(cfg.provider, 'opencode');
|
||||
assert.deepEqual(cfg.apiKeys, ['opencode']);
|
||||
assert.equal(cfg.baseURL, 'http://opencode.local:4096');
|
||||
assert.equal(cfg.model, 'gemini-2.5-flash');
|
||||
});
|
||||
|
||||
it('detects opencode server with custom model', () => {
|
||||
process.env.OPENCODE_BASE_URL = 'http://opencode.local:4096';
|
||||
process.env.OPENCODE_MODEL = 'google/gemini-2.5-pro';
|
||||
const cfg = getLLMConfig();
|
||||
assert.equal(cfg.provider, 'opencode');
|
||||
assert.equal(cfg.baseURL, 'http://opencode.local:4096');
|
||||
assert.equal(cfg.model, 'google/gemini-2.5-pro');
|
||||
});
|
||||
|
||||
it('skips OpenCode TLS verification by default', () => {
|
||||
assert.equal(shouldSkipOpenCodeTLSVerify(), true);
|
||||
});
|
||||
|
||||
it('allows explicitly enabling OpenCode TLS verification', () => {
|
||||
process.env.OPENCODE_SKIP_TLS_VERIFY = 'false';
|
||||
assert.equal(shouldSkipOpenCodeTLSVerify(), false);
|
||||
});
|
||||
|
||||
it('skips OpenCode TLS verification for empty string and non-false values', () => {
|
||||
for (const value of ['', '0', 'true', 'yes', '1', 'on', 'custom']) {
|
||||
process.env.OPENCODE_SKIP_TLS_VERIFY = value;
|
||||
assert.equal(shouldSkipOpenCodeTLSVerify(), true);
|
||||
}
|
||||
});
|
||||
|
||||
it('openai takes priority over gemini when both set', () => {
|
||||
process.env.OPENAI_API_KEY = 'sk-test';
|
||||
process.env.GEMINI_API_KEY = 'gemini-key';
|
||||
const cfg = getLLMConfig();
|
||||
assert.equal(cfg.provider, 'openai');
|
||||
});
|
||||
|
||||
it('empty string api key is treated as not set', () => {
|
||||
process.env.OPENAI_API_KEY = '';
|
||||
process.env.GEMINI_API_KEY = 'gemini-key';
|
||||
const cfg = getLLMConfig();
|
||||
assert.equal(cfg.provider, 'gemini');
|
||||
});
|
||||
|
||||
it('detects ollama without api key', () => {
|
||||
process.env.OLLAMA_BASE_URL = 'http://localhost:11434';
|
||||
process.env.OLLAMA_MODEL = 'llama3';
|
||||
const cfg = getLLMConfig();
|
||||
assert.equal(cfg.provider, 'ollama');
|
||||
assert.equal(cfg.model, 'llama3');
|
||||
});
|
||||
|
||||
it('comma-only api key is treated as not set', () => {
|
||||
process.env.OPENAI_API_KEY = ',,,';
|
||||
const cfg = getLLMConfig();
|
||||
assert.equal(cfg.provider, null);
|
||||
assert.deepEqual(cfg.apiKeys, []);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user