chorus-cli 0.5.1 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +139 -22
- package/package.json +1 -1
- package/tools/__pycache__/coder.cpython-314.pyc +0 -0
- package/tools/__pycache__/qa.cpython-314.pyc +0 -0
- package/tools/coder.py +13 -2
- package/tools/qa.py +14 -2
package/index.js
CHANGED
|
@@ -62,7 +62,7 @@ async function getMachineId() {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// Run coder.py with real-time stderr streaming so progress is visible
|
|
65
|
-
function runCoder(prompt) {
|
|
65
|
+
function runCoder(prompt, { model } = {}) {
|
|
66
66
|
return new Promise((resolve, reject) => {
|
|
67
67
|
const env = { ...process.env };
|
|
68
68
|
if (CONFIG.ai.chorusApiKey) {
|
|
@@ -72,7 +72,12 @@ function runCoder(prompt) {
|
|
|
72
72
|
if (CONFIG.ai.machineId) {
|
|
73
73
|
env.CHORUS_MACHINE_ID = CONFIG.ai.machineId;
|
|
74
74
|
}
|
|
75
|
-
|
|
75
|
+
if (isFreeModel(model)) {
|
|
76
|
+
env.CHORUS_FREE = '1';
|
|
77
|
+
}
|
|
78
|
+
const coderArgs = [CONFIG.ai.coderPath, '--prompt', prompt];
|
|
79
|
+
if (model) { coderArgs.push('--model', model); }
|
|
80
|
+
const proc = spawn(CONFIG.ai.venvPython, coderArgs, {
|
|
76
81
|
cwd: process.cwd(),
|
|
77
82
|
env,
|
|
78
83
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -103,7 +108,7 @@ function runCoder(prompt) {
|
|
|
103
108
|
}
|
|
104
109
|
|
|
105
110
|
// Run qa.py with issue context on stdin, capture JSON from stdout
|
|
106
|
-
function runQAChat(issue, enrichedDetails, qaName, useSuper = false) {
|
|
111
|
+
function runQAChat(issue, enrichedDetails, qaName, useSuper = false, { model } = {}) {
|
|
107
112
|
return new Promise((resolve, reject) => {
|
|
108
113
|
const input = JSON.stringify({
|
|
109
114
|
issue_number: issue.number,
|
|
@@ -117,6 +122,7 @@ function runQAChat(issue, enrichedDetails, qaName, useSuper = false) {
|
|
|
117
122
|
args.push('--auth', CONFIG.teams.authPath);
|
|
118
123
|
}
|
|
119
124
|
if (useSuper) {args.push('--super');}
|
|
125
|
+
if (model) {args.push('--model', model);}
|
|
120
126
|
|
|
121
127
|
const env = { ...process.env };
|
|
122
128
|
if (CONFIG.ai.chorusApiKey) {
|
|
@@ -126,6 +132,9 @@ function runQAChat(issue, enrichedDetails, qaName, useSuper = false) {
|
|
|
126
132
|
if (CONFIG.ai.machineId) {
|
|
127
133
|
env.CHORUS_MACHINE_ID = CONFIG.ai.machineId;
|
|
128
134
|
}
|
|
135
|
+
if (isFreeModel(model)) {
|
|
136
|
+
env.CHORUS_FREE = '1';
|
|
137
|
+
}
|
|
129
138
|
if (CONFIG.messenger === 'slack' && CONFIG.slack.botToken) {
|
|
130
139
|
env.SLACK_BOT_TOKEN = CONFIG.slack.botToken;
|
|
131
140
|
}
|
|
@@ -202,7 +211,7 @@ const CONFIG = {
|
|
|
202
211
|
// Use createProvider(CONFIG, issueArg) to get the right provider.
|
|
203
212
|
|
|
204
213
|
// ===== AI ENRICHMENT =====
|
|
205
|
-
async function enrichWithAI(issue) {
|
|
214
|
+
async function enrichWithAI(issue, { model } = {}) {
|
|
206
215
|
const prompt = `Analyze this GitHub issue and write questions for QA clarification.
|
|
207
216
|
|
|
208
217
|
ISSUE DETAILS:
|
|
@@ -253,7 +262,7 @@ IMPORTANT: Output ONLY the message above. Do not include any preamble, thinking
|
|
|
253
262
|
const openai = new OpenAI(openaiOpts);
|
|
254
263
|
|
|
255
264
|
const response = await openai.chat.completions.create({
|
|
256
|
-
model: 'chorus-default',
|
|
265
|
+
model: model || 'chorus-default',
|
|
257
266
|
max_tokens: 2000,
|
|
258
267
|
messages: [
|
|
259
268
|
{
|
|
@@ -262,7 +271,10 @@ IMPORTANT: Output ONLY the message above. Do not include any preamble, thinking
|
|
|
262
271
|
}
|
|
263
272
|
]
|
|
264
273
|
}, {
|
|
265
|
-
headers: {
|
|
274
|
+
headers: {
|
|
275
|
+
'X-Chorus-Mode': 'enrich',
|
|
276
|
+
...(isFreeModel(model) && { 'X-Chorus-Free': '1' }),
|
|
277
|
+
},
|
|
266
278
|
});
|
|
267
279
|
|
|
268
280
|
if (response.usage) {
|
|
@@ -279,7 +291,7 @@ IMPORTANT: Output ONLY the message above. Do not include any preamble, thinking
|
|
|
279
291
|
}
|
|
280
292
|
|
|
281
293
|
// ===== CODE GENERATION =====
|
|
282
|
-
async function generateCode(issue, enrichedDetails, qaResponse) {
|
|
294
|
+
async function generateCode(issue, enrichedDetails, qaResponse, { model } = {}) {
|
|
283
295
|
const tool = CONFIG.ai.codingTool;
|
|
284
296
|
|
|
285
297
|
if (tool === 'coder') {
|
|
@@ -304,7 +316,7 @@ Instructions:
|
|
|
304
316
|
|
|
305
317
|
console.log('🔨 Generating code with Coder agent...');
|
|
306
318
|
|
|
307
|
-
return await runCoder(prompt);
|
|
319
|
+
return await runCoder(prompt, { model });
|
|
308
320
|
}
|
|
309
321
|
|
|
310
322
|
// Fallback: kimi
|
|
@@ -423,7 +435,7 @@ async function getCodeRabbitReview(solution, issue, provider) {
|
|
|
423
435
|
}
|
|
424
436
|
}
|
|
425
437
|
|
|
426
|
-
async function refineCode(solution, review) {
|
|
438
|
+
async function refineCode(solution, review, { model } = {}) {
|
|
427
439
|
const tool = CONFIG.ai.codingTool;
|
|
428
440
|
|
|
429
441
|
if (tool === 'coder') {
|
|
@@ -448,7 +460,7 @@ Instructions:
|
|
|
448
460
|
|
|
449
461
|
console.log('🔄 Refining code with Coder agent...');
|
|
450
462
|
|
|
451
|
-
return await runCoder(prompt);
|
|
463
|
+
return await runCoder(prompt, { model });
|
|
452
464
|
}
|
|
453
465
|
|
|
454
466
|
// Fallback: kimi
|
|
@@ -535,6 +547,10 @@ ${lintOutput.slice(0, 5000)}`;
|
|
|
535
547
|
|
|
536
548
|
// ===== TOKEN LIMIT =====
|
|
537
549
|
|
|
550
|
+
function isFreeModel(model) {
|
|
551
|
+
return model && FREE_MODELS.has(model);
|
|
552
|
+
}
|
|
553
|
+
|
|
538
554
|
function isTokenLimitError(err) {
|
|
539
555
|
const msg = typeof err === 'string' ? err : (err?.message || err?.error || '');
|
|
540
556
|
return msg.includes('token limit exceeded') || msg.includes('rate_limit_error');
|
|
@@ -576,7 +592,7 @@ async function printTokenLimitMessage() {
|
|
|
576
592
|
}
|
|
577
593
|
|
|
578
594
|
// ===== MAIN WORKFLOW =====
|
|
579
|
-
async function processTicket(issueArg, { useSuper = false, skipQA = false, qaName: qaNameOverride } = {}) {
|
|
595
|
+
async function processTicket(issueArg, { useSuper = false, skipQA = false, qaName: qaNameOverride, model } = {}) {
|
|
580
596
|
try {
|
|
581
597
|
console.log('🚀 Starting ticket processing...\n');
|
|
582
598
|
|
|
@@ -600,7 +616,25 @@ async function processTicket(issueArg, { useSuper = false, skipQA = false, qaNam
|
|
|
600
616
|
efs(CONFIG.ai.venvPython, ['-m', 'pip', 'install', '-r', reqFile], { stdio: 'inherit' });
|
|
601
617
|
}
|
|
602
618
|
|
|
603
|
-
// 0a.
|
|
619
|
+
// 0a. Check token balance — free models can proceed with zero balance
|
|
620
|
+
const usingFree = isFreeModel(model);
|
|
621
|
+
const email = await fetchAccountEmail();
|
|
622
|
+
const balance = await fetchCreditBalance(email);
|
|
623
|
+
|
|
624
|
+
if (balance !== null && balance <= 0 && !usingFree) {
|
|
625
|
+
await printTokenLimitMessage();
|
|
626
|
+
console.log(' Tip: Use --free to run with a free model\n');
|
|
627
|
+
process.exit(1);
|
|
628
|
+
}
|
|
629
|
+
if (usingFree) {
|
|
630
|
+
if (balance !== null && balance <= 0) {
|
|
631
|
+
console.log(`🆓 Using free model "${model}"\n`);
|
|
632
|
+
} else {
|
|
633
|
+
console.log(`🆓 Using free model "${model}"\n`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// 0b. Verify no modified tracked files (untracked files like .chorus/ are fine)
|
|
604
638
|
const { stdout: gitStatus } = await execPromise('git status --porcelain --untracked-files=no');
|
|
605
639
|
if (gitStatus.trim()) {
|
|
606
640
|
console.error('⚠️ Working directory has uncommitted changes. Commit or stash first:');
|
|
@@ -640,7 +674,7 @@ async function processTicket(issueArg, { useSuper = false, skipQA = false, qaNam
|
|
|
640
674
|
console.log(`Found issue #${issue.number}: ${issue.title}\n`);
|
|
641
675
|
|
|
642
676
|
// 2. Enrich with AI
|
|
643
|
-
const enrichedDetails = await enrichWithAI(issue);
|
|
677
|
+
const enrichedDetails = await enrichWithAI(issue, { model });
|
|
644
678
|
console.log('Enrichment complete\n', enrichedDetails);
|
|
645
679
|
|
|
646
680
|
// 3. Multi-turn QA conversation via qa.py
|
|
@@ -650,12 +684,13 @@ async function processTicket(issueArg, { useSuper = false, skipQA = false, qaNam
|
|
|
650
684
|
} else {
|
|
651
685
|
const qaName = qaNameOverride || await provider.getUserDisplayName(issue.user.login);
|
|
652
686
|
console.log(`💬 Starting QA conversation with ${qaName?.login}...`);
|
|
653
|
-
const qaResult = await runQAChat(issue, enrichedDetails, qaName, useSuper);
|
|
687
|
+
const qaResult = await runQAChat(issue, enrichedDetails, qaName, useSuper, { model });
|
|
654
688
|
qaResponse = qaResult.requirements;
|
|
655
689
|
|
|
656
690
|
if (!qaResult.completed) {
|
|
657
|
-
if (isTokenLimitError(qaResult.error)) {
|
|
691
|
+
if (isTokenLimitError(qaResult.error) && !usingFree) {
|
|
658
692
|
await printTokenLimitMessage();
|
|
693
|
+
console.log(' Tip: Use --free to run with a free model\n');
|
|
659
694
|
process.exit(1);
|
|
660
695
|
}
|
|
661
696
|
console.warn('⚠️ QA chat did not complete successfully:', qaResult.error || 'unknown');
|
|
@@ -678,7 +713,7 @@ async function processTicket(issueArg, { useSuper = false, skipQA = false, qaNam
|
|
|
678
713
|
|
|
679
714
|
for (let attempt = 1; attempt <= maxCodeAttempts; attempt++) {
|
|
680
715
|
if (attempt === 1) {
|
|
681
|
-
solution = await generateCode(issue, enrichedDetails, qaResponse);
|
|
716
|
+
solution = await generateCode(issue, enrichedDetails, qaResponse, { model });
|
|
682
717
|
} else {
|
|
683
718
|
// Reprompt with explicit instruction that files must be written
|
|
684
719
|
const retryPrompt = `You previously attempted to implement this issue but DID NOT write any files. Your task is NOT complete until you have actually created or modified files using write_file or edit_file.
|
|
@@ -699,13 +734,14 @@ ${qaResponse}
|
|
|
699
734
|
CRITICAL: You MUST write code to actual files. Do not just describe changes — use write_file or edit_file to make them. If you are unsure where to make changes, explore the codebase first, then write the code.`;
|
|
700
735
|
|
|
701
736
|
console.log(`🔁 Reprompting coder (attempt ${attempt}/${maxCodeAttempts})...`);
|
|
702
|
-
solution = await runCoder(retryPrompt);
|
|
737
|
+
solution = await runCoder(retryPrompt, { model });
|
|
703
738
|
}
|
|
704
739
|
|
|
705
740
|
if (solution.completed === false) {
|
|
706
741
|
const errs = solution.errors || [solution.summary || ''];
|
|
707
|
-
if (errs.some(e => isTokenLimitError(e))) {
|
|
742
|
+
if (errs.some(e => isTokenLimitError(e)) && !usingFree) {
|
|
708
743
|
printTokenLimitMessage();
|
|
744
|
+
console.log(' Tip: Use --free to run with a free model\n');
|
|
709
745
|
process.exit(1);
|
|
710
746
|
}
|
|
711
747
|
console.error('❌ Code generation failed:', errs);
|
|
@@ -765,7 +801,7 @@ CRITICAL: You MUST write code to actual files. Do not just describe changes —
|
|
|
765
801
|
while (review.needsChanges && iterations < maxIterations) {
|
|
766
802
|
console.log(`Iteration ${iterations + 1}/${maxIterations}...`);
|
|
767
803
|
|
|
768
|
-
const refined = await refineCode(solution, review);
|
|
804
|
+
const refined = await refineCode(solution, review, { model });
|
|
769
805
|
|
|
770
806
|
if (refined.completed === false) {
|
|
771
807
|
console.warn('⚠️ Refinement had errors:', refined.errors);
|
|
@@ -843,8 +879,9 @@ Instructions:
|
|
|
843
879
|
|
|
844
880
|
if (revised.completed === false) {
|
|
845
881
|
const errs = revised.errors || [revised.summary || ''];
|
|
846
|
-
if (errs.some(e => isTokenLimitError(e))) {
|
|
882
|
+
if (errs.some(e => isTokenLimitError(e)) && !usingFree) {
|
|
847
883
|
printTokenLimitMessage();
|
|
884
|
+
console.log(' Tip: Use --free to run with a free model\n');
|
|
848
885
|
rl.close();
|
|
849
886
|
process.exit(1);
|
|
850
887
|
}
|
|
@@ -874,8 +911,9 @@ Instructions:
|
|
|
874
911
|
console.log('\n✨ Ticket processing complete!');
|
|
875
912
|
|
|
876
913
|
} catch (error) {
|
|
877
|
-
if (isTokenLimitError(error)) {
|
|
914
|
+
if (isTokenLimitError(error) && !isFreeModel(model)) {
|
|
878
915
|
await printTokenLimitMessage();
|
|
916
|
+
console.log(' Tip: Use --free to run with a free model\n');
|
|
879
917
|
process.exit(1);
|
|
880
918
|
}
|
|
881
919
|
console.error('❌ Error processing ticket:', error);
|
|
@@ -1271,21 +1309,89 @@ function printZEP() {
|
|
|
1271
1309
|
const command = process.argv[2];
|
|
1272
1310
|
const _envExists = require('fs').existsSync(path.join(os.homedir(), '.config', 'chorus', '.env'));
|
|
1273
1311
|
|
|
1312
|
+
const FREE_MODELS = new Set([
|
|
1313
|
+
'gpt-oss', 'gpt-oss-20b', 'qwen-coder', 'qwen', 'llama', 'hermes',
|
|
1314
|
+
'nemotron', 'gemma', 'mistral', 'glm', 'step', 'solar'
|
|
1315
|
+
]);
|
|
1316
|
+
|
|
1317
|
+
const PAID_MODELS = new Set([
|
|
1318
|
+
'claude', 'chatgpt', 'deepseek', 'kimi', 'gemini', 'grok', 'command-r'
|
|
1319
|
+
]);
|
|
1320
|
+
|
|
1321
|
+
const ALLOWED_MODELS = new Set([...FREE_MODELS, ...PAID_MODELS]);
|
|
1322
|
+
|
|
1323
|
+
const DEFAULT_FREE_MODEL = 'gpt-oss';
|
|
1324
|
+
|
|
1325
|
+
function printModelTable(out = console.error) {
|
|
1326
|
+
out('\n Free models:');
|
|
1327
|
+
out(' ┌─────────────────────┬──────────────────────────────────┐');
|
|
1328
|
+
out(' │ Flag value │ Model │');
|
|
1329
|
+
out(' ├─────────────────────┼──────────────────────────────────┤');
|
|
1330
|
+
out(' │ gpt-oss │ GPT-OSS 120B (default) │');
|
|
1331
|
+
out(' │ gpt-oss-20b │ GPT-OSS 20B │');
|
|
1332
|
+
out(' │ qwen-coder │ Qwen3 Coder 480B │');
|
|
1333
|
+
out(' │ qwen │ Qwen3 Next 80B │');
|
|
1334
|
+
out(' │ llama │ Llama 3.3 70B │');
|
|
1335
|
+
out(' │ hermes │ Hermes 3 405B │');
|
|
1336
|
+
out(' │ nemotron │ Nemotron 3 Nano 30B │');
|
|
1337
|
+
out(' │ gemma │ Gemma 3 27B │');
|
|
1338
|
+
out(' │ mistral │ Mistral Small 3.1 24B │');
|
|
1339
|
+
out(' │ glm │ GLM 4.5 Air │');
|
|
1340
|
+
out(' │ step │ Step 3.5 Flash │');
|
|
1341
|
+
out(' │ solar │ Solar Pro 3 │');
|
|
1342
|
+
out(' └─────────────────────┴──────────────────────────────────┘');
|
|
1343
|
+
out('\n Paid models:');
|
|
1344
|
+
out(' ┌─────────────────────┬──────────────────────────────────┐');
|
|
1345
|
+
out(' │ Flag value │ Model │');
|
|
1346
|
+
out(' ├─────────────────────┼──────────────────────────────────┤');
|
|
1347
|
+
out(' │ claude │ Claude │');
|
|
1348
|
+
out(' │ chatgpt │ ChatGPT │');
|
|
1349
|
+
out(' │ deepseek │ DeepSeek │');
|
|
1350
|
+
out(' │ kimi │ Kimi │');
|
|
1351
|
+
out(' │ gemini │ Gemini │');
|
|
1352
|
+
out(' │ grok │ Grok │');
|
|
1353
|
+
out(' │ command-r │ Command R │');
|
|
1354
|
+
out(' └─────────────────────┴──────────────────────────────────┘');
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1274
1357
|
function parseRunArgs() {
|
|
1275
1358
|
const args = process.argv.slice(3);
|
|
1276
|
-
const opts = { useSuper: false, skipQA: false, qaName: null, issueArg: null };
|
|
1359
|
+
const opts = { useSuper: false, skipQA: false, qaName: null, issueArg: null, model: null, free: false };
|
|
1277
1360
|
|
|
1278
1361
|
for (let i = 0; i < args.length; i++) {
|
|
1279
1362
|
if (args[i] === '--super') {
|
|
1280
1363
|
opts.useSuper = true;
|
|
1281
1364
|
} else if (args[i] === '--skip-qa') {
|
|
1282
1365
|
opts.skipQA = true;
|
|
1366
|
+
} else if (args[i] === '--free') {
|
|
1367
|
+
opts.free = true;
|
|
1283
1368
|
} else if (args[i] === '--qa' && i + 1 < args.length) {
|
|
1284
1369
|
opts.qaName = args[++i];
|
|
1370
|
+
} else if (args[i] === '--model' && i + 1 < args.length) {
|
|
1371
|
+
const val = args[++i];
|
|
1372
|
+
if (!ALLOWED_MODELS.has(val)) {
|
|
1373
|
+
console.error(`Error: invalid model "${val}". Allowed models: ${[...ALLOWED_MODELS].join(', ')}`);
|
|
1374
|
+
printModelTable();
|
|
1375
|
+
process.exit(1);
|
|
1376
|
+
}
|
|
1377
|
+
opts.model = val;
|
|
1285
1378
|
} else if (!args[i].startsWith('--')) {
|
|
1286
1379
|
opts.issueArg = args[i];
|
|
1287
1380
|
}
|
|
1288
1381
|
}
|
|
1382
|
+
|
|
1383
|
+
// --free: default to best free model, or validate that --model is free
|
|
1384
|
+
if (opts.free) {
|
|
1385
|
+
if (opts.model && !FREE_MODELS.has(opts.model)) {
|
|
1386
|
+
console.error(`Error: "${opts.model}" is a paid model and cannot be used with --free.`);
|
|
1387
|
+
printModelTable();
|
|
1388
|
+
process.exit(1);
|
|
1389
|
+
}
|
|
1390
|
+
if (!opts.model) {
|
|
1391
|
+
opts.model = DEFAULT_FREE_MODEL;
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1289
1395
|
return opts;
|
|
1290
1396
|
}
|
|
1291
1397
|
|
|
@@ -1303,6 +1409,13 @@ if (command === 'setup') {
|
|
|
1303
1409
|
const { issueArg, ...opts } = parseRunArgs();
|
|
1304
1410
|
processTicket(issueArg, opts).catch(console.error);
|
|
1305
1411
|
}
|
|
1412
|
+
} else if (command === 'models') {
|
|
1413
|
+
console.log('\nChorus — Available Models');
|
|
1414
|
+
printModelTable(console.log);
|
|
1415
|
+
console.log('\nUsage:');
|
|
1416
|
+
console.log(' chorus run 4464 --model deepseek Use a specific model');
|
|
1417
|
+
console.log(' chorus run 4464 --free Use the best free model (GPT-OSS 120B)');
|
|
1418
|
+
console.log(' chorus run 4464 --free --model qwen-coder Use a specific free model\n');
|
|
1306
1419
|
} else if (command === 'zep') {
|
|
1307
1420
|
printZEP()
|
|
1308
1421
|
} else {
|
|
@@ -1321,10 +1434,14 @@ Chorus — AI-powered ticket automation (GitHub & Azure DevOps)
|
|
|
1321
1434
|
|
|
1322
1435
|
Usage:
|
|
1323
1436
|
chorus setup - Set up provider, Chorus auth + messenger
|
|
1437
|
+
chorus models - List all available models (free & paid)
|
|
1324
1438
|
chorus run - Process latest assigned issue
|
|
1325
1439
|
chorus run 4464 - Process specific issue by number
|
|
1326
1440
|
chorus run <url> - Process issue from full URL (auto-detects provider)
|
|
1327
1441
|
chorus run 4464 --super - Use Opus 4.6 for QA evaluation
|
|
1442
|
+
chorus run 4464 --model deepseek - Use a specific model (see --free for free options)
|
|
1443
|
+
chorus run 4464 --free - Use the best free model (GPT-OSS 120B)
|
|
1444
|
+
chorus run 4464 --free --model qwen-coder - Use a specific free model
|
|
1328
1445
|
chorus run 4464 --qa 'John Doe' - Specify QA contact name for chat
|
|
1329
1446
|
chorus run 4464 --skip-qa - Skip QA conversation, go straight to coding
|
|
1330
1447
|
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
package/tools/coder.py
CHANGED
|
@@ -878,8 +878,13 @@ def run_prompt(client, prompt, chorus_context):
|
|
|
878
878
|
def main():
|
|
879
879
|
parser = argparse.ArgumentParser(description="Coder — AI coding agent powered by Claude via Chorus")
|
|
880
880
|
parser.add_argument("-p", "--prompt", help="Run a single prompt headlessly and output JSON")
|
|
881
|
+
parser.add_argument("--model", help="Override the model sent to the proxy")
|
|
881
882
|
args = parser.parse_args()
|
|
882
883
|
|
|
884
|
+
global MODEL
|
|
885
|
+
if args.model:
|
|
886
|
+
MODEL = args.model
|
|
887
|
+
|
|
883
888
|
api_key = os.environ.get("CHORUS_API_KEY")
|
|
884
889
|
if not api_key:
|
|
885
890
|
print(f"{C.RED}Error: CHORUS_API_KEY not set. Run 'chorus setup' to configure.{C.RESET}", file=sys.stderr)
|
|
@@ -887,9 +892,15 @@ def main():
|
|
|
887
892
|
|
|
888
893
|
base_url = os.environ.get("CHORUS_API_URL", "https://chorus-bad0f.web.app/v1")
|
|
889
894
|
machine_id = os.environ.get("CHORUS_MACHINE_ID")
|
|
890
|
-
|
|
895
|
+
chorus_free = os.environ.get("CHORUS_FREE", "")
|
|
896
|
+
default_headers = {}
|
|
891
897
|
if machine_id:
|
|
892
|
-
|
|
898
|
+
default_headers["X-Machine-Id"] = machine_id
|
|
899
|
+
if chorus_free:
|
|
900
|
+
default_headers["X-Chorus-Free"] = "1"
|
|
901
|
+
client_kwargs = {"api_key": api_key, "base_url": base_url}
|
|
902
|
+
if default_headers:
|
|
903
|
+
client_kwargs["default_headers"] = default_headers
|
|
893
904
|
client = OpenAI(**client_kwargs)
|
|
894
905
|
cwd = os.getcwd()
|
|
895
906
|
|
package/tools/qa.py
CHANGED
|
@@ -408,9 +408,15 @@ def run_qa_chat(issue_context, messenger, qa_name):
|
|
|
408
408
|
api_key = os.environ.get("CHORUS_API_KEY")
|
|
409
409
|
base_url = os.environ.get("CHORUS_API_URL", "https://chorus-bad0f.web.app/v1")
|
|
410
410
|
machine_id = os.environ.get("CHORUS_MACHINE_ID")
|
|
411
|
-
|
|
411
|
+
chorus_free = os.environ.get("CHORUS_FREE", "")
|
|
412
|
+
default_headers = {}
|
|
412
413
|
if machine_id:
|
|
413
|
-
|
|
414
|
+
default_headers["X-Machine-Id"] = machine_id
|
|
415
|
+
if chorus_free:
|
|
416
|
+
default_headers["X-Chorus-Free"] = "1"
|
|
417
|
+
client_kwargs = {"api_key": api_key, "base_url": base_url}
|
|
418
|
+
if default_headers:
|
|
419
|
+
client_kwargs["default_headers"] = default_headers
|
|
414
420
|
client = OpenAI(**client_kwargs)
|
|
415
421
|
conversation = []
|
|
416
422
|
raw_responses = []
|
|
@@ -483,8 +489,14 @@ def main():
|
|
|
483
489
|
parser.add_argument("--auth", help="Path to Teams auth state JSON (required for --messenger teams)")
|
|
484
490
|
parser.add_argument("--qa", required=True, help="QA person's name")
|
|
485
491
|
parser.add_argument("--super", action="store_true", help="Use Opus 4.6 instead of Sonnet")
|
|
492
|
+
parser.add_argument("--model", help="Override the model sent to the proxy")
|
|
486
493
|
args = parser.parse_args()
|
|
487
494
|
|
|
495
|
+
# Override model if specified
|
|
496
|
+
global MODEL
|
|
497
|
+
if args.model:
|
|
498
|
+
MODEL = args.model
|
|
499
|
+
|
|
488
500
|
# chorus_mode tells the proxy which model to use
|
|
489
501
|
global QA_CHORUS_MODE
|
|
490
502
|
if args.super:
|