opencara 0.14.0 → 0.15.0
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/dist/index.js +464 -50
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,8 +6,8 @@ import { Command as Command2 } from "commander";
|
|
|
6
6
|
// src/commands/agent.ts
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
import crypto from "crypto";
|
|
9
|
-
import * as
|
|
10
|
-
import * as
|
|
9
|
+
import * as fs5 from "fs";
|
|
10
|
+
import * as path5 from "path";
|
|
11
11
|
|
|
12
12
|
// ../shared/dist/types.js
|
|
13
13
|
function isRepoAllowed(repoConfig, targetOwner, targetRepo, agentOwner) {
|
|
@@ -117,6 +117,10 @@ import { parse, stringify } from "yaml";
|
|
|
117
117
|
var DEFAULT_PLATFORM_URL = "https://api.opencara.dev";
|
|
118
118
|
var CONFIG_DIR = path.join(os.homedir(), ".opencara");
|
|
119
119
|
var CONFIG_FILE = process.env.OPENCARA_CONFIG && process.env.OPENCARA_CONFIG.trim() ? path.resolve(process.env.OPENCARA_CONFIG) : path.join(CONFIG_DIR, "config.yml");
|
|
120
|
+
function ensureConfigDir() {
|
|
121
|
+
const dir = path.dirname(CONFIG_FILE);
|
|
122
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
123
|
+
}
|
|
120
124
|
var DEFAULT_MAX_DIFF_SIZE_KB = 100;
|
|
121
125
|
var DEFAULT_MAX_CONSECUTIVE_ERRORS = 10;
|
|
122
126
|
var VALID_REPO_MODES = ["all", "own", "whitelist", "blacklist"];
|
|
@@ -256,8 +260,23 @@ function validateConfigData(data, envPlatformUrl) {
|
|
|
256
260
|
);
|
|
257
261
|
overrides.maxConsecutiveErrors = DEFAULT_MAX_CONSECUTIVE_ERRORS;
|
|
258
262
|
}
|
|
263
|
+
for (const field of [
|
|
264
|
+
"max_reviews_per_day",
|
|
265
|
+
"max_tokens_per_day",
|
|
266
|
+
"max_tokens_per_review"
|
|
267
|
+
]) {
|
|
268
|
+
if (field in data && typeof data[field] === "number" && data[field] <= 0) {
|
|
269
|
+
console.warn(
|
|
270
|
+
`\u26A0 Config warning: ${field} must be > 0, got ${data[field]}, ignoring (unlimited)`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
259
274
|
return overrides;
|
|
260
275
|
}
|
|
276
|
+
function parsePositiveInt(value) {
|
|
277
|
+
if (typeof value === "number" && Number.isInteger(value) && value > 0) return value;
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
261
280
|
function loadConfig() {
|
|
262
281
|
const envPlatformUrl = process.env.OPENCARA_PLATFORM_URL?.trim() || null;
|
|
263
282
|
const defaults = {
|
|
@@ -269,7 +288,12 @@ function loadConfig() {
|
|
|
269
288
|
githubUsername: null,
|
|
270
289
|
codebaseDir: null,
|
|
271
290
|
agentCommand: null,
|
|
272
|
-
agents: null
|
|
291
|
+
agents: null,
|
|
292
|
+
usageLimits: {
|
|
293
|
+
maxReviewsPerDay: null,
|
|
294
|
+
maxTokensPerDay: null,
|
|
295
|
+
maxTokensPerReview: null
|
|
296
|
+
}
|
|
273
297
|
};
|
|
274
298
|
if (!fs.existsSync(CONFIG_FILE)) {
|
|
275
299
|
return defaults;
|
|
@@ -289,7 +313,12 @@ function loadConfig() {
|
|
|
289
313
|
githubUsername: typeof data.github_username === "string" ? data.github_username : null,
|
|
290
314
|
codebaseDir: typeof data.codebase_dir === "string" ? data.codebase_dir : null,
|
|
291
315
|
agentCommand: typeof data.agent_command === "string" ? data.agent_command : null,
|
|
292
|
-
agents: parseAgents(data)
|
|
316
|
+
agents: parseAgents(data),
|
|
317
|
+
usageLimits: {
|
|
318
|
+
maxReviewsPerDay: parsePositiveInt(data.max_reviews_per_day),
|
|
319
|
+
maxTokensPerDay: parsePositiveInt(data.max_tokens_per_day),
|
|
320
|
+
maxTokensPerReview: parsePositiveInt(data.max_tokens_per_review)
|
|
321
|
+
}
|
|
293
322
|
};
|
|
294
323
|
}
|
|
295
324
|
function resolveGithubToken(agentToken, globalToken) {
|
|
@@ -465,24 +494,24 @@ var ApiClient = class {
|
|
|
465
494
|
}
|
|
466
495
|
return h;
|
|
467
496
|
}
|
|
468
|
-
async get(
|
|
469
|
-
this.log(`GET ${
|
|
470
|
-
const res = await fetch(`${this.baseUrl}${
|
|
497
|
+
async get(path6) {
|
|
498
|
+
this.log(`GET ${path6}`);
|
|
499
|
+
const res = await fetch(`${this.baseUrl}${path6}`, {
|
|
471
500
|
method: "GET",
|
|
472
501
|
headers: this.headers()
|
|
473
502
|
});
|
|
474
|
-
return this.handleResponse(res,
|
|
503
|
+
return this.handleResponse(res, path6);
|
|
475
504
|
}
|
|
476
|
-
async post(
|
|
477
|
-
this.log(`POST ${
|
|
478
|
-
const res = await fetch(`${this.baseUrl}${
|
|
505
|
+
async post(path6, body) {
|
|
506
|
+
this.log(`POST ${path6}`);
|
|
507
|
+
const res = await fetch(`${this.baseUrl}${path6}`, {
|
|
479
508
|
method: "POST",
|
|
480
509
|
headers: this.headers(),
|
|
481
510
|
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
482
511
|
});
|
|
483
|
-
return this.handleResponse(res,
|
|
512
|
+
return this.handleResponse(res, path6);
|
|
484
513
|
}
|
|
485
|
-
async handleResponse(res,
|
|
514
|
+
async handleResponse(res, path6) {
|
|
486
515
|
if (!res.ok) {
|
|
487
516
|
let message = `HTTP ${res.status}`;
|
|
488
517
|
let errorCode;
|
|
@@ -494,10 +523,10 @@ var ApiClient = class {
|
|
|
494
523
|
}
|
|
495
524
|
} catch {
|
|
496
525
|
}
|
|
497
|
-
this.log(`${res.status} ${message} (${
|
|
526
|
+
this.log(`${res.status} ${message} (${path6})`);
|
|
498
527
|
throw new HttpError(res.status, message, errorCode);
|
|
499
528
|
}
|
|
500
|
-
this.log(`${res.status} OK (${
|
|
529
|
+
this.log(`${res.status} OK (${path6})`);
|
|
501
530
|
return await res.json();
|
|
502
531
|
}
|
|
503
532
|
};
|
|
@@ -629,18 +658,35 @@ function parseClaudeTokens(text) {
|
|
|
629
658
|
const inputMatch = text.match(/"input_tokens"\s*:\s*(\d+)/);
|
|
630
659
|
const outputMatch = text.match(/"output_tokens"\s*:\s*(\d+)/);
|
|
631
660
|
if (inputMatch && outputMatch) {
|
|
632
|
-
return
|
|
661
|
+
return {
|
|
662
|
+
input: parseInt(inputMatch[1], 10),
|
|
663
|
+
output: parseInt(outputMatch[1], 10)
|
|
664
|
+
};
|
|
633
665
|
}
|
|
634
666
|
return null;
|
|
635
667
|
}
|
|
636
668
|
function parseTokenUsage(stdout, stderr) {
|
|
637
669
|
const codexMatch = stdout.match(/tokens\s+used[\s:]*([0-9,]+)/i);
|
|
638
|
-
if (codexMatch)
|
|
639
|
-
|
|
640
|
-
|
|
670
|
+
if (codexMatch) {
|
|
671
|
+
const total = parseInt(codexMatch[1].replace(/,/g, ""), 10);
|
|
672
|
+
return { tokens: total, parsed: true, input: 0, output: total };
|
|
673
|
+
}
|
|
674
|
+
const claudeResult = parseClaudeTokens(stdout) ?? parseClaudeTokens(stderr);
|
|
675
|
+
if (claudeResult !== null) {
|
|
676
|
+
return {
|
|
677
|
+
tokens: claudeResult.input + claudeResult.output,
|
|
678
|
+
parsed: true,
|
|
679
|
+
input: claudeResult.input,
|
|
680
|
+
output: claudeResult.output
|
|
681
|
+
};
|
|
682
|
+
}
|
|
641
683
|
const qwenMatch = stdout.match(/"tokens"\s*:\s*\{[^}]*"total"\s*:\s*(\d+)/);
|
|
642
|
-
if (qwenMatch)
|
|
643
|
-
|
|
684
|
+
if (qwenMatch) {
|
|
685
|
+
const total = parseInt(qwenMatch[1], 10);
|
|
686
|
+
return { tokens: total, parsed: true, input: 0, output: total };
|
|
687
|
+
}
|
|
688
|
+
const estimated = estimateTokens(stdout);
|
|
689
|
+
return { tokens: estimated, parsed: false, input: 0, output: estimated };
|
|
644
690
|
}
|
|
645
691
|
function executeTool(commandTemplate, prompt, timeoutMs, signal, vars, cwd) {
|
|
646
692
|
const promptViaArg = commandTemplate.includes("${PROMPT}");
|
|
@@ -729,7 +775,18 @@ function executeTool(commandTemplate, prompt, timeoutMs, signal, vars, cwd) {
|
|
|
729
775
|
console.warn(`Tool stderr: ${stderr.slice(0, MAX_STDERR_LENGTH)}`);
|
|
730
776
|
}
|
|
731
777
|
const usage2 = parseTokenUsage(stdout, stderr);
|
|
732
|
-
resolve2({
|
|
778
|
+
resolve2({
|
|
779
|
+
stdout,
|
|
780
|
+
stderr,
|
|
781
|
+
tokensUsed: usage2.tokens,
|
|
782
|
+
tokensParsed: usage2.parsed,
|
|
783
|
+
tokenDetail: {
|
|
784
|
+
input: usage2.input,
|
|
785
|
+
output: usage2.output,
|
|
786
|
+
total: usage2.tokens,
|
|
787
|
+
parsed: usage2.parsed
|
|
788
|
+
}
|
|
789
|
+
});
|
|
733
790
|
return;
|
|
734
791
|
}
|
|
735
792
|
const errMsg = stderr ? `Tool "${command}" failed (exit code ${code}): ${stderr.slice(0, MAX_STDERR_LENGTH)}` : `Tool "${command}" failed with exit code ${code}`;
|
|
@@ -737,7 +794,18 @@ function executeTool(commandTemplate, prompt, timeoutMs, signal, vars, cwd) {
|
|
|
737
794
|
return;
|
|
738
795
|
}
|
|
739
796
|
const usage = parseTokenUsage(stdout, stderr);
|
|
740
|
-
resolve2({
|
|
797
|
+
resolve2({
|
|
798
|
+
stdout,
|
|
799
|
+
stderr,
|
|
800
|
+
tokensUsed: usage.tokens,
|
|
801
|
+
tokensParsed: usage.parsed,
|
|
802
|
+
tokenDetail: {
|
|
803
|
+
input: usage.input,
|
|
804
|
+
output: usage.output,
|
|
805
|
+
total: usage.tokens,
|
|
806
|
+
parsed: usage.parsed
|
|
807
|
+
}
|
|
808
|
+
});
|
|
741
809
|
});
|
|
742
810
|
});
|
|
743
811
|
}
|
|
@@ -861,11 +929,19 @@ ${userMessage}`;
|
|
|
861
929
|
);
|
|
862
930
|
const { verdict, review } = extractVerdict(result.stdout);
|
|
863
931
|
const inputTokens = result.tokensParsed ? 0 : estimateTokens(fullPrompt);
|
|
932
|
+
const detail = result.tokenDetail;
|
|
933
|
+
const tokenDetail = result.tokensParsed ? detail : {
|
|
934
|
+
input: inputTokens,
|
|
935
|
+
output: detail.output,
|
|
936
|
+
total: inputTokens + detail.output,
|
|
937
|
+
parsed: false
|
|
938
|
+
};
|
|
864
939
|
return {
|
|
865
940
|
review,
|
|
866
941
|
verdict,
|
|
867
942
|
tokensUsed: result.tokensUsed + inputTokens,
|
|
868
|
-
tokensEstimated: !result.tokensParsed
|
|
943
|
+
tokensEstimated: !result.tokensParsed,
|
|
944
|
+
tokenDetail
|
|
869
945
|
};
|
|
870
946
|
} finally {
|
|
871
947
|
clearTimeout(abortTimer);
|
|
@@ -985,10 +1061,18 @@ ${userMessage}`;
|
|
|
985
1061
|
deps.codebaseDir ?? void 0
|
|
986
1062
|
);
|
|
987
1063
|
const inputTokens = result.tokensParsed ? 0 : estimateTokens(fullPrompt);
|
|
1064
|
+
const detail = result.tokenDetail;
|
|
1065
|
+
const tokenDetail = result.tokensParsed ? detail : {
|
|
1066
|
+
input: inputTokens,
|
|
1067
|
+
output: detail.output,
|
|
1068
|
+
total: inputTokens + detail.output,
|
|
1069
|
+
parsed: false
|
|
1070
|
+
};
|
|
988
1071
|
return {
|
|
989
1072
|
summary: result.stdout,
|
|
990
1073
|
tokensUsed: result.tokensUsed + inputTokens,
|
|
991
|
-
tokensEstimated: !result.tokensParsed
|
|
1074
|
+
tokensEstimated: !result.tokensParsed,
|
|
1075
|
+
tokenDetail
|
|
992
1076
|
};
|
|
993
1077
|
} finally {
|
|
994
1078
|
clearTimeout(abortTimer);
|
|
@@ -1153,16 +1237,190 @@ var RouterTimeoutError = class extends Error {
|
|
|
1153
1237
|
|
|
1154
1238
|
// src/consumption.ts
|
|
1155
1239
|
function createSessionTracker() {
|
|
1156
|
-
return { tokens: 0, reviews: 0 };
|
|
1240
|
+
return { tokens: 0, reviews: 0, tokenBreakdown: { input: 0, output: 0, estimated: 0 } };
|
|
1157
1241
|
}
|
|
1158
|
-
function recordSessionUsage(session,
|
|
1159
|
-
|
|
1160
|
-
|
|
1242
|
+
function recordSessionUsage(session, tokensOrOptions) {
|
|
1243
|
+
if (typeof tokensOrOptions === "number") {
|
|
1244
|
+
session.tokens += tokensOrOptions;
|
|
1245
|
+
session.reviews += 1;
|
|
1246
|
+
session.tokenBreakdown.estimated += tokensOrOptions;
|
|
1247
|
+
} else {
|
|
1248
|
+
session.tokens += tokensOrOptions.totalTokens;
|
|
1249
|
+
session.reviews += 1;
|
|
1250
|
+
if (tokensOrOptions.estimated) {
|
|
1251
|
+
session.tokenBreakdown.estimated += tokensOrOptions.totalTokens;
|
|
1252
|
+
} else {
|
|
1253
|
+
session.tokenBreakdown.input += tokensOrOptions.inputTokens;
|
|
1254
|
+
session.tokenBreakdown.output += tokensOrOptions.outputTokens;
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1161
1257
|
}
|
|
1162
1258
|
function formatPostReviewStats(session) {
|
|
1163
|
-
|
|
1259
|
+
const { input, output, estimated } = session.tokenBreakdown;
|
|
1260
|
+
const hasBreakdown = input > 0 || output > 0;
|
|
1261
|
+
let detail = "";
|
|
1262
|
+
if (hasBreakdown) {
|
|
1263
|
+
const parts = [];
|
|
1264
|
+
if (input > 0) parts.push(`${input.toLocaleString()} in`);
|
|
1265
|
+
if (output > 0) parts.push(`${output.toLocaleString()} out`);
|
|
1266
|
+
if (estimated > 0) parts.push(`${estimated.toLocaleString()} est`);
|
|
1267
|
+
detail = ` (${parts.join(" + ")})`;
|
|
1268
|
+
}
|
|
1269
|
+
return ` Session: ${session.tokens.toLocaleString()} tokens${detail} / ${session.reviews} reviews`;
|
|
1164
1270
|
}
|
|
1165
1271
|
|
|
1272
|
+
// src/usage-tracker.ts
|
|
1273
|
+
import * as fs4 from "fs";
|
|
1274
|
+
import * as path4 from "path";
|
|
1275
|
+
var USAGE_FILE = path4.join(CONFIG_DIR, "usage.json");
|
|
1276
|
+
var MAX_HISTORY_DAYS = 30;
|
|
1277
|
+
var WARNING_THRESHOLD = 0.8;
|
|
1278
|
+
function todayKey() {
|
|
1279
|
+
const now = /* @__PURE__ */ new Date();
|
|
1280
|
+
const y = now.getFullYear();
|
|
1281
|
+
const m = String(now.getMonth() + 1).padStart(2, "0");
|
|
1282
|
+
const d = String(now.getDate()).padStart(2, "0");
|
|
1283
|
+
return `${y}-${m}-${d}`;
|
|
1284
|
+
}
|
|
1285
|
+
function totalTokens(t) {
|
|
1286
|
+
return t.input + t.output + t.estimated;
|
|
1287
|
+
}
|
|
1288
|
+
var UsageTracker = class {
|
|
1289
|
+
data;
|
|
1290
|
+
filePath;
|
|
1291
|
+
constructor(filePath = USAGE_FILE) {
|
|
1292
|
+
this.filePath = filePath;
|
|
1293
|
+
this.data = this.load();
|
|
1294
|
+
this.pruneHistory();
|
|
1295
|
+
}
|
|
1296
|
+
load() {
|
|
1297
|
+
try {
|
|
1298
|
+
if (fs4.existsSync(this.filePath)) {
|
|
1299
|
+
const raw = fs4.readFileSync(this.filePath, "utf-8");
|
|
1300
|
+
const parsed = JSON.parse(raw);
|
|
1301
|
+
if (parsed && Array.isArray(parsed.days)) {
|
|
1302
|
+
return parsed;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
} catch {
|
|
1306
|
+
}
|
|
1307
|
+
return { days: [] };
|
|
1308
|
+
}
|
|
1309
|
+
save() {
|
|
1310
|
+
ensureConfigDir();
|
|
1311
|
+
fs4.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), {
|
|
1312
|
+
encoding: "utf-8",
|
|
1313
|
+
mode: 384
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
/** Get or create today's usage record. Prunes old history. */
|
|
1317
|
+
getToday() {
|
|
1318
|
+
const key = todayKey();
|
|
1319
|
+
let today = this.data.days.find((d) => d.date === key);
|
|
1320
|
+
if (!today) {
|
|
1321
|
+
today = { date: key, reviews: 0, tokens: { input: 0, output: 0, estimated: 0 } };
|
|
1322
|
+
this.data.days.push(today);
|
|
1323
|
+
this.pruneHistory();
|
|
1324
|
+
}
|
|
1325
|
+
return today;
|
|
1326
|
+
}
|
|
1327
|
+
/** Record a completed review with its token usage. */
|
|
1328
|
+
recordReview(tokens) {
|
|
1329
|
+
const today = this.getToday();
|
|
1330
|
+
today.reviews += 1;
|
|
1331
|
+
if (tokens.estimated) {
|
|
1332
|
+
today.tokens.estimated += tokens.input + tokens.output;
|
|
1333
|
+
} else {
|
|
1334
|
+
today.tokens.input += tokens.input;
|
|
1335
|
+
today.tokens.output += tokens.output;
|
|
1336
|
+
}
|
|
1337
|
+
this.save();
|
|
1338
|
+
}
|
|
1339
|
+
/** Check whether a new review is allowed under the configured limits. */
|
|
1340
|
+
checkLimits(limits) {
|
|
1341
|
+
const today = this.getToday();
|
|
1342
|
+
const todayTokenTotal = totalTokens(today.tokens);
|
|
1343
|
+
if (limits.maxReviewsPerDay !== null && today.reviews >= limits.maxReviewsPerDay) {
|
|
1344
|
+
return {
|
|
1345
|
+
allowed: false,
|
|
1346
|
+
reason: `Daily review limit reached (${today.reviews}/${limits.maxReviewsPerDay})`
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
if (limits.maxTokensPerDay !== null && todayTokenTotal >= limits.maxTokensPerDay) {
|
|
1350
|
+
return {
|
|
1351
|
+
allowed: false,
|
|
1352
|
+
reason: `Daily token budget exhausted (${todayTokenTotal.toLocaleString()}/${limits.maxTokensPerDay.toLocaleString()})`
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
const warnings = [];
|
|
1356
|
+
if (limits.maxReviewsPerDay !== null) {
|
|
1357
|
+
const ratio = today.reviews / limits.maxReviewsPerDay;
|
|
1358
|
+
if (ratio >= WARNING_THRESHOLD) {
|
|
1359
|
+
warnings.push(
|
|
1360
|
+
`Reviews: ${today.reviews}/${limits.maxReviewsPerDay} (${Math.round(ratio * 100)}%)`
|
|
1361
|
+
);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
if (limits.maxTokensPerDay !== null) {
|
|
1365
|
+
const ratio = todayTokenTotal / limits.maxTokensPerDay;
|
|
1366
|
+
if (ratio >= WARNING_THRESHOLD) {
|
|
1367
|
+
warnings.push(
|
|
1368
|
+
`Tokens: ${todayTokenTotal.toLocaleString()}/${limits.maxTokensPerDay.toLocaleString()} (${Math.round(ratio * 100)}%)`
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
return { allowed: true, warning: warnings.length > 0 ? warnings.join("; ") : void 0 };
|
|
1373
|
+
}
|
|
1374
|
+
/** Check whether a specific review's estimated token count exceeds the per-review limit. */
|
|
1375
|
+
checkPerReviewLimit(estimatedTokens, limits) {
|
|
1376
|
+
if (limits.maxTokensPerReview !== null && estimatedTokens > limits.maxTokensPerReview) {
|
|
1377
|
+
return {
|
|
1378
|
+
allowed: false,
|
|
1379
|
+
reason: `Estimated tokens (${estimatedTokens.toLocaleString()}) exceed per-review limit (${limits.maxTokensPerReview.toLocaleString()})`
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
return { allowed: true };
|
|
1383
|
+
}
|
|
1384
|
+
/** Remove entries older than MAX_HISTORY_DAYS. */
|
|
1385
|
+
pruneHistory() {
|
|
1386
|
+
if (this.data.days.length <= MAX_HISTORY_DAYS) return;
|
|
1387
|
+
this.data.days.sort((a, b) => b.date.localeCompare(a.date));
|
|
1388
|
+
this.data.days = this.data.days.slice(0, MAX_HISTORY_DAYS);
|
|
1389
|
+
}
|
|
1390
|
+
/** Format a usage summary for display on shutdown. */
|
|
1391
|
+
formatSummary(limits) {
|
|
1392
|
+
const today = this.getToday();
|
|
1393
|
+
const todayTokenTotal = totalTokens(today.tokens);
|
|
1394
|
+
const lines = ["Usage Summary:"];
|
|
1395
|
+
lines.push(` Date: ${today.date}`);
|
|
1396
|
+
lines.push(
|
|
1397
|
+
` Reviews: ${today.reviews}${limits.maxReviewsPerDay !== null ? `/${limits.maxReviewsPerDay}` : ""}`
|
|
1398
|
+
);
|
|
1399
|
+
const tokenParts = [];
|
|
1400
|
+
if (today.tokens.input > 0) tokenParts.push(`${today.tokens.input.toLocaleString()} in`);
|
|
1401
|
+
if (today.tokens.output > 0) tokenParts.push(`${today.tokens.output.toLocaleString()} out`);
|
|
1402
|
+
if (today.tokens.estimated > 0)
|
|
1403
|
+
tokenParts.push(`${today.tokens.estimated.toLocaleString()} est`);
|
|
1404
|
+
const breakdown = tokenParts.length > 0 ? ` (${tokenParts.join(" + ")})` : "";
|
|
1405
|
+
lines.push(
|
|
1406
|
+
` Tokens: ${todayTokenTotal.toLocaleString()}${limits.maxTokensPerDay !== null ? `/${limits.maxTokensPerDay.toLocaleString()}` : ""}${breakdown}`
|
|
1407
|
+
);
|
|
1408
|
+
if (limits.maxTokensPerDay !== null) {
|
|
1409
|
+
const remaining = Math.max(0, limits.maxTokensPerDay - todayTokenTotal);
|
|
1410
|
+
lines.push(` Remaining token budget: ${remaining.toLocaleString()}`);
|
|
1411
|
+
}
|
|
1412
|
+
if (limits.maxReviewsPerDay !== null) {
|
|
1413
|
+
const remaining = Math.max(0, limits.maxReviewsPerDay - today.reviews);
|
|
1414
|
+
lines.push(` Remaining reviews: ${remaining}`);
|
|
1415
|
+
}
|
|
1416
|
+
return lines.join("\n");
|
|
1417
|
+
}
|
|
1418
|
+
/** Get all stored usage data (for testing/inspection). */
|
|
1419
|
+
getData() {
|
|
1420
|
+
return this.data;
|
|
1421
|
+
}
|
|
1422
|
+
};
|
|
1423
|
+
|
|
1166
1424
|
// src/pr-context.ts
|
|
1167
1425
|
async function githubGet(url, deps) {
|
|
1168
1426
|
const headers = {
|
|
@@ -1365,7 +1623,8 @@ function computeRoles(agent) {
|
|
|
1365
1623
|
if (agent.synthesizer_only) return ["summary"];
|
|
1366
1624
|
return ["review", "summary"];
|
|
1367
1625
|
}
|
|
1368
|
-
async function fetchDiff(diffUrl, githubToken, signal) {
|
|
1626
|
+
async function fetchDiff(diffUrl, githubToken, signal, maxDiffSizeKb) {
|
|
1627
|
+
const maxBytes = maxDiffSizeKb ? maxDiffSizeKb * 1024 : Infinity;
|
|
1369
1628
|
return withRetry(
|
|
1370
1629
|
async () => {
|
|
1371
1630
|
const headers = {};
|
|
@@ -1390,13 +1649,56 @@ async function fetchDiff(diffUrl, githubToken, signal) {
|
|
|
1390
1649
|
}
|
|
1391
1650
|
throw new Error(msg);
|
|
1392
1651
|
}
|
|
1652
|
+
if (maxBytes < Infinity) {
|
|
1653
|
+
const contentLength = parseInt(response.headers.get("content-length") ?? "", 10);
|
|
1654
|
+
if (!isNaN(contentLength) && contentLength > maxBytes) {
|
|
1655
|
+
if (response.body) {
|
|
1656
|
+
void response.body.cancel();
|
|
1657
|
+
}
|
|
1658
|
+
throw new DiffTooLargeError(
|
|
1659
|
+
`Diff too large (${Math.round(contentLength / 1024)}KB > ${maxDiffSizeKb}KB, from Content-Length)`
|
|
1660
|
+
);
|
|
1661
|
+
}
|
|
1662
|
+
if (response.body) {
|
|
1663
|
+
const reader = response.body.getReader();
|
|
1664
|
+
const chunks = [];
|
|
1665
|
+
let totalBytes = 0;
|
|
1666
|
+
for (; ; ) {
|
|
1667
|
+
const { done, value } = await reader.read();
|
|
1668
|
+
if (done) break;
|
|
1669
|
+
totalBytes += value.length;
|
|
1670
|
+
if (totalBytes > maxBytes) {
|
|
1671
|
+
void reader.cancel();
|
|
1672
|
+
throw new DiffTooLargeError(`Diff too large (>${maxDiffSizeKb}KB)`);
|
|
1673
|
+
}
|
|
1674
|
+
chunks.push(value);
|
|
1675
|
+
}
|
|
1676
|
+
return new TextDecoder().decode(concatUint8Arrays(chunks, totalBytes));
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1393
1679
|
return response.text();
|
|
1394
1680
|
},
|
|
1395
1681
|
{ maxAttempts: 2 },
|
|
1396
1682
|
signal
|
|
1397
1683
|
);
|
|
1398
1684
|
}
|
|
1685
|
+
function concatUint8Arrays(chunks, totalLength) {
|
|
1686
|
+
const result = new Uint8Array(totalLength);
|
|
1687
|
+
let offset = 0;
|
|
1688
|
+
for (const chunk of chunks) {
|
|
1689
|
+
result.set(chunk, offset);
|
|
1690
|
+
offset += chunk.length;
|
|
1691
|
+
}
|
|
1692
|
+
return result;
|
|
1693
|
+
}
|
|
1399
1694
|
var MAX_DIFF_FETCH_ATTEMPTS = 3;
|
|
1695
|
+
function appendContributorAttribution(text, githubUsername) {
|
|
1696
|
+
if (!githubUsername) return text;
|
|
1697
|
+
return `${text}
|
|
1698
|
+
|
|
1699
|
+
---
|
|
1700
|
+
Contributed by [@${githubUsername}](https://github.com/${githubUsername})`;
|
|
1701
|
+
}
|
|
1400
1702
|
async function pollLoop(client, agentId, reviewDeps, consumptionDeps, agentInfo, logger, agentSession, options) {
|
|
1401
1703
|
const {
|
|
1402
1704
|
pollIntervalMs,
|
|
@@ -1415,6 +1717,16 @@ async function pollLoop(client, agentId, reviewDeps, consumptionDeps, agentInfo,
|
|
|
1415
1717
|
let consecutiveErrors = 0;
|
|
1416
1718
|
const diffFailCounts = /* @__PURE__ */ new Map();
|
|
1417
1719
|
while (!signal?.aborted) {
|
|
1720
|
+
if (consumptionDeps.usageTracker && consumptionDeps.usageLimits) {
|
|
1721
|
+
const limitStatus = consumptionDeps.usageTracker.checkLimits(consumptionDeps.usageLimits);
|
|
1722
|
+
if (!limitStatus.allowed) {
|
|
1723
|
+
log(`${icons.stop} ${limitStatus.reason}. Stopping.`);
|
|
1724
|
+
break;
|
|
1725
|
+
}
|
|
1726
|
+
if (limitStatus.warning) {
|
|
1727
|
+
logWarn(`${icons.warn} Approaching limits: ${limitStatus.warning}`);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1418
1730
|
try {
|
|
1419
1731
|
const pollBody = { agent_id: agentId };
|
|
1420
1732
|
if (githubUsername) pollBody.github_username = githubUsername;
|
|
@@ -1528,7 +1840,12 @@ async function handleTask(client, agentId, task, reviewDeps, consumptionDeps, ag
|
|
|
1528
1840
|
}
|
|
1529
1841
|
let diffContent;
|
|
1530
1842
|
try {
|
|
1531
|
-
diffContent = await fetchDiff(
|
|
1843
|
+
diffContent = await fetchDiff(
|
|
1844
|
+
diff_url,
|
|
1845
|
+
reviewDeps.githubToken,
|
|
1846
|
+
signal,
|
|
1847
|
+
reviewDeps.maxDiffSizeKb
|
|
1848
|
+
);
|
|
1532
1849
|
log(` Diff fetched (${Math.round(diffContent.length / 1024)}KB)`);
|
|
1533
1850
|
} catch (err) {
|
|
1534
1851
|
logError(` Failed to fetch diff for task ${task_id}: ${err.message}`);
|
|
@@ -1567,8 +1884,8 @@ async function handleTask(client, agentId, task, reviewDeps, consumptionDeps, ag
|
|
|
1567
1884
|
validatePathSegment(owner, "owner");
|
|
1568
1885
|
validatePathSegment(repo, "repo");
|
|
1569
1886
|
validatePathSegment(task_id, "task_id");
|
|
1570
|
-
const repoScopedDir =
|
|
1571
|
-
|
|
1887
|
+
const repoScopedDir = path5.join(CONFIG_DIR, "repos", owner, repo, task_id);
|
|
1888
|
+
fs5.mkdirSync(repoScopedDir, { recursive: true });
|
|
1572
1889
|
taskCheckoutPath = repoScopedDir;
|
|
1573
1890
|
taskReviewDeps = { ...reviewDeps, codebaseDir: repoScopedDir };
|
|
1574
1891
|
log(` Working directory: ${repoScopedDir}`);
|
|
@@ -1611,7 +1928,8 @@ async function handleTask(client, agentId, task, reviewDeps, consumptionDeps, ag
|
|
|
1611
1928
|
logger,
|
|
1612
1929
|
routerRelay,
|
|
1613
1930
|
signal,
|
|
1614
|
-
contextBlock
|
|
1931
|
+
contextBlock,
|
|
1932
|
+
githubUsername
|
|
1615
1933
|
);
|
|
1616
1934
|
} else {
|
|
1617
1935
|
await executeReviewTask(
|
|
@@ -1629,7 +1947,8 @@ async function handleTask(client, agentId, task, reviewDeps, consumptionDeps, ag
|
|
|
1629
1947
|
logger,
|
|
1630
1948
|
routerRelay,
|
|
1631
1949
|
signal,
|
|
1632
|
-
contextBlock
|
|
1950
|
+
contextBlock,
|
|
1951
|
+
githubUsername
|
|
1633
1952
|
);
|
|
1634
1953
|
}
|
|
1635
1954
|
agentSession.tasksCompleted++;
|
|
@@ -1679,10 +1998,21 @@ async function safeError(client, taskId, agentId, error, logger) {
|
|
|
1679
1998
|
);
|
|
1680
1999
|
}
|
|
1681
2000
|
}
|
|
1682
|
-
async function executeReviewTask(client, agentId, taskId, owner, repo, prNumber, diffContent, prompt, timeoutSeconds, reviewDeps, consumptionDeps, logger, routerRelay, signal, contextBlock) {
|
|
2001
|
+
async function executeReviewTask(client, agentId, taskId, owner, repo, prNumber, diffContent, prompt, timeoutSeconds, reviewDeps, consumptionDeps, logger, routerRelay, signal, contextBlock, githubUsername) {
|
|
2002
|
+
if (consumptionDeps.usageLimits?.maxTokensPerReview != null && consumptionDeps.usageTracker) {
|
|
2003
|
+
const estimatedInput = estimateTokens(diffContent + prompt + (contextBlock ?? ""));
|
|
2004
|
+
const perReviewCheck = consumptionDeps.usageTracker.checkPerReviewLimit(
|
|
2005
|
+
estimatedInput,
|
|
2006
|
+
consumptionDeps.usageLimits
|
|
2007
|
+
);
|
|
2008
|
+
if (!perReviewCheck.allowed) {
|
|
2009
|
+
throw new Error(perReviewCheck.reason);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
1683
2012
|
let reviewText;
|
|
1684
2013
|
let verdict;
|
|
1685
2014
|
let tokensUsed;
|
|
2015
|
+
let usageOpts;
|
|
1686
2016
|
if (routerRelay) {
|
|
1687
2017
|
logger.log(` ${icons.running} Executing review: [router mode]`);
|
|
1688
2018
|
const fullPrompt = routerRelay.buildReviewPrompt({
|
|
@@ -1703,6 +2033,12 @@ async function executeReviewTask(client, agentId, taskId, owner, repo, prNumber,
|
|
|
1703
2033
|
reviewText = parsed.review;
|
|
1704
2034
|
verdict = parsed.verdict;
|
|
1705
2035
|
tokensUsed = estimateTokens(fullPrompt) + estimateTokens(response);
|
|
2036
|
+
usageOpts = {
|
|
2037
|
+
inputTokens: estimateTokens(fullPrompt),
|
|
2038
|
+
outputTokens: estimateTokens(response),
|
|
2039
|
+
totalTokens: tokensUsed,
|
|
2040
|
+
estimated: true
|
|
2041
|
+
};
|
|
1706
2042
|
} else {
|
|
1707
2043
|
logger.log(` ${icons.running} Executing review: ${reviewDeps.commandTemplate}`);
|
|
1708
2044
|
const result = await executeReview(
|
|
@@ -1722,8 +2058,14 @@ async function executeReviewTask(client, agentId, taskId, owner, repo, prNumber,
|
|
|
1722
2058
|
reviewText = result.review;
|
|
1723
2059
|
verdict = result.verdict;
|
|
1724
2060
|
tokensUsed = result.tokensUsed;
|
|
2061
|
+
usageOpts = {
|
|
2062
|
+
inputTokens: result.tokenDetail.input,
|
|
2063
|
+
outputTokens: result.tokenDetail.output,
|
|
2064
|
+
totalTokens: result.tokensUsed,
|
|
2065
|
+
estimated: result.tokensEstimated
|
|
2066
|
+
};
|
|
1725
2067
|
}
|
|
1726
|
-
const sanitizedReview = sanitizeTokens(reviewText);
|
|
2068
|
+
const sanitizedReview = appendContributorAttribution(sanitizeTokens(reviewText), githubUsername);
|
|
1727
2069
|
await withRetry(
|
|
1728
2070
|
() => client.post(`/api/tasks/${taskId}/result`, {
|
|
1729
2071
|
agent_id: agentId,
|
|
@@ -1735,15 +2077,23 @@ async function executeReviewTask(client, agentId, taskId, owner, repo, prNumber,
|
|
|
1735
2077
|
{ maxAttempts: 3 },
|
|
1736
2078
|
signal
|
|
1737
2079
|
);
|
|
1738
|
-
recordSessionUsage(consumptionDeps.session,
|
|
2080
|
+
recordSessionUsage(consumptionDeps.session, usageOpts);
|
|
2081
|
+
if (consumptionDeps.usageTracker) {
|
|
2082
|
+
consumptionDeps.usageTracker.recordReview({
|
|
2083
|
+
input: usageOpts.inputTokens,
|
|
2084
|
+
output: usageOpts.outputTokens,
|
|
2085
|
+
estimated: usageOpts.estimated
|
|
2086
|
+
});
|
|
2087
|
+
}
|
|
1739
2088
|
logger.log(` ${icons.success} Review submitted (${tokensUsed.toLocaleString()} tokens)`);
|
|
1740
2089
|
logger.log(formatPostReviewStats(consumptionDeps.session));
|
|
1741
2090
|
}
|
|
1742
|
-
async function executeSummaryTask(client, agentId, taskId, owner, repo, prNumber, diffContent, prompt, timeoutSeconds, reviews, reviewDeps, consumptionDeps, logger, routerRelay, signal, contextBlock) {
|
|
2091
|
+
async function executeSummaryTask(client, agentId, taskId, owner, repo, prNumber, diffContent, prompt, timeoutSeconds, reviews, reviewDeps, consumptionDeps, logger, routerRelay, signal, contextBlock, githubUsername) {
|
|
1743
2092
|
if (reviews.length === 0) {
|
|
1744
2093
|
let reviewText;
|
|
1745
2094
|
let verdict;
|
|
1746
2095
|
let tokensUsed2;
|
|
2096
|
+
let usageOpts2;
|
|
1747
2097
|
if (routerRelay) {
|
|
1748
2098
|
logger.log(` ${icons.running} Executing summary: [router mode]`);
|
|
1749
2099
|
const fullPrompt = routerRelay.buildReviewPrompt({
|
|
@@ -1764,6 +2114,12 @@ async function executeSummaryTask(client, agentId, taskId, owner, repo, prNumber
|
|
|
1764
2114
|
reviewText = parsed.review;
|
|
1765
2115
|
verdict = parsed.verdict;
|
|
1766
2116
|
tokensUsed2 = estimateTokens(fullPrompt) + estimateTokens(response);
|
|
2117
|
+
usageOpts2 = {
|
|
2118
|
+
inputTokens: estimateTokens(fullPrompt),
|
|
2119
|
+
outputTokens: estimateTokens(response),
|
|
2120
|
+
totalTokens: tokensUsed2,
|
|
2121
|
+
estimated: true
|
|
2122
|
+
};
|
|
1767
2123
|
} else {
|
|
1768
2124
|
logger.log(` ${icons.running} Executing summary: ${reviewDeps.commandTemplate}`);
|
|
1769
2125
|
const result = await executeReview(
|
|
@@ -1783,8 +2139,17 @@ async function executeSummaryTask(client, agentId, taskId, owner, repo, prNumber
|
|
|
1783
2139
|
reviewText = result.review;
|
|
1784
2140
|
verdict = result.verdict;
|
|
1785
2141
|
tokensUsed2 = result.tokensUsed;
|
|
2142
|
+
usageOpts2 = {
|
|
2143
|
+
inputTokens: result.tokenDetail.input,
|
|
2144
|
+
outputTokens: result.tokenDetail.output,
|
|
2145
|
+
totalTokens: result.tokensUsed,
|
|
2146
|
+
estimated: result.tokensEstimated
|
|
2147
|
+
};
|
|
1786
2148
|
}
|
|
1787
|
-
const sanitizedReview =
|
|
2149
|
+
const sanitizedReview = appendContributorAttribution(
|
|
2150
|
+
sanitizeTokens(reviewText),
|
|
2151
|
+
githubUsername
|
|
2152
|
+
);
|
|
1788
2153
|
await withRetry(
|
|
1789
2154
|
() => client.post(`/api/tasks/${taskId}/result`, {
|
|
1790
2155
|
agent_id: agentId,
|
|
@@ -1796,7 +2161,14 @@ async function executeSummaryTask(client, agentId, taskId, owner, repo, prNumber
|
|
|
1796
2161
|
{ maxAttempts: 3 },
|
|
1797
2162
|
signal
|
|
1798
2163
|
);
|
|
1799
|
-
recordSessionUsage(consumptionDeps.session,
|
|
2164
|
+
recordSessionUsage(consumptionDeps.session, usageOpts2);
|
|
2165
|
+
if (consumptionDeps.usageTracker) {
|
|
2166
|
+
consumptionDeps.usageTracker.recordReview({
|
|
2167
|
+
input: usageOpts2.inputTokens,
|
|
2168
|
+
output: usageOpts2.outputTokens,
|
|
2169
|
+
estimated: usageOpts2.estimated
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
1800
2172
|
logger.log(
|
|
1801
2173
|
` ${icons.success} Review submitted as summary (${tokensUsed2.toLocaleString()} tokens)`
|
|
1802
2174
|
);
|
|
@@ -1812,6 +2184,7 @@ async function executeSummaryTask(client, agentId, taskId, owner, repo, prNumber
|
|
|
1812
2184
|
}));
|
|
1813
2185
|
let summaryText;
|
|
1814
2186
|
let tokensUsed;
|
|
2187
|
+
let usageOpts;
|
|
1815
2188
|
if (routerRelay) {
|
|
1816
2189
|
logger.log(` ${icons.running} Executing summary: [router mode]`);
|
|
1817
2190
|
const fullPrompt = routerRelay.buildSummaryPrompt({
|
|
@@ -1830,6 +2203,12 @@ async function executeSummaryTask(client, agentId, taskId, owner, repo, prNumber
|
|
|
1830
2203
|
);
|
|
1831
2204
|
summaryText = response;
|
|
1832
2205
|
tokensUsed = estimateTokens(fullPrompt) + estimateTokens(response);
|
|
2206
|
+
usageOpts = {
|
|
2207
|
+
inputTokens: estimateTokens(fullPrompt),
|
|
2208
|
+
outputTokens: estimateTokens(response),
|
|
2209
|
+
totalTokens: tokensUsed,
|
|
2210
|
+
estimated: true
|
|
2211
|
+
};
|
|
1833
2212
|
} else {
|
|
1834
2213
|
logger.log(` ${icons.running} Executing summary: ${reviewDeps.commandTemplate}`);
|
|
1835
2214
|
const result = await executeSummary(
|
|
@@ -1848,8 +2227,17 @@ async function executeSummaryTask(client, agentId, taskId, owner, repo, prNumber
|
|
|
1848
2227
|
);
|
|
1849
2228
|
summaryText = result.summary;
|
|
1850
2229
|
tokensUsed = result.tokensUsed;
|
|
2230
|
+
usageOpts = {
|
|
2231
|
+
inputTokens: result.tokenDetail.input,
|
|
2232
|
+
outputTokens: result.tokenDetail.output,
|
|
2233
|
+
totalTokens: result.tokensUsed,
|
|
2234
|
+
estimated: result.tokensEstimated
|
|
2235
|
+
};
|
|
1851
2236
|
}
|
|
1852
|
-
const sanitizedSummary =
|
|
2237
|
+
const sanitizedSummary = appendContributorAttribution(
|
|
2238
|
+
sanitizeTokens(summaryText),
|
|
2239
|
+
githubUsername
|
|
2240
|
+
);
|
|
1853
2241
|
await withRetry(
|
|
1854
2242
|
() => client.post(`/api/tasks/${taskId}/result`, {
|
|
1855
2243
|
agent_id: agentId,
|
|
@@ -1860,7 +2248,14 @@ async function executeSummaryTask(client, agentId, taskId, owner, repo, prNumber
|
|
|
1860
2248
|
{ maxAttempts: 3 },
|
|
1861
2249
|
signal
|
|
1862
2250
|
);
|
|
1863
|
-
recordSessionUsage(consumptionDeps.session,
|
|
2251
|
+
recordSessionUsage(consumptionDeps.session, usageOpts);
|
|
2252
|
+
if (consumptionDeps.usageTracker) {
|
|
2253
|
+
consumptionDeps.usageTracker.recordReview({
|
|
2254
|
+
input: usageOpts.inputTokens,
|
|
2255
|
+
output: usageOpts.outputTokens,
|
|
2256
|
+
estimated: usageOpts.estimated
|
|
2257
|
+
});
|
|
2258
|
+
}
|
|
1864
2259
|
logger.log(` ${icons.success} Summary submitted (${tokensUsed.toLocaleString()} tokens)`);
|
|
1865
2260
|
logger.log(formatPostReviewStats(consumptionDeps.session));
|
|
1866
2261
|
}
|
|
@@ -1884,7 +2279,17 @@ function sleep2(ms, signal) {
|
|
|
1884
2279
|
async function startAgent(agentId, platformUrl, agentInfo, reviewDeps, consumptionDeps, options) {
|
|
1885
2280
|
const client = new ApiClient(platformUrl, { apiKey: options?.apiKey });
|
|
1886
2281
|
const session = consumptionDeps?.session ?? createSessionTracker();
|
|
1887
|
-
const
|
|
2282
|
+
const usageTracker = consumptionDeps?.usageTracker ?? new UsageTracker();
|
|
2283
|
+
const usageLimits = options?.usageLimits ?? {
|
|
2284
|
+
maxReviewsPerDay: null,
|
|
2285
|
+
maxTokensPerDay: null,
|
|
2286
|
+
maxTokensPerReview: null
|
|
2287
|
+
};
|
|
2288
|
+
const deps = consumptionDeps ? {
|
|
2289
|
+
...consumptionDeps,
|
|
2290
|
+
usageTracker: consumptionDeps.usageTracker ?? usageTracker,
|
|
2291
|
+
usageLimits: consumptionDeps.usageLimits ?? usageLimits
|
|
2292
|
+
} : { agentId, session, usageTracker, usageLimits };
|
|
1888
2293
|
const logger = createLogger(options?.label);
|
|
1889
2294
|
const { log, logError, logWarn } = logger;
|
|
1890
2295
|
const agentSession = createAgentSession();
|
|
@@ -1921,6 +2326,9 @@ async function startAgent(agentId, platformUrl, agentInfo, reviewDeps, consumpti
|
|
|
1921
2326
|
githubUsername: options?.githubUsername,
|
|
1922
2327
|
signal: abortController.signal
|
|
1923
2328
|
});
|
|
2329
|
+
if (deps.usageTracker) {
|
|
2330
|
+
log(deps.usageTracker.formatSummary(deps.usageLimits ?? usageLimits));
|
|
2331
|
+
}
|
|
1924
2332
|
log(formatExitSummary(agentSession));
|
|
1925
2333
|
}
|
|
1926
2334
|
async function startAgentRouter() {
|
|
@@ -1952,6 +2360,7 @@ async function startAgentRouter() {
|
|
|
1952
2360
|
codebaseDir
|
|
1953
2361
|
};
|
|
1954
2362
|
const session = createSessionTracker();
|
|
2363
|
+
const usageTracker = new UsageTracker();
|
|
1955
2364
|
const model = agentConfig?.model ?? "unknown";
|
|
1956
2365
|
const tool = agentConfig?.tool ?? "unknown";
|
|
1957
2366
|
const label = agentConfig?.name ?? "agent[0]";
|
|
@@ -1963,7 +2372,9 @@ async function startAgentRouter() {
|
|
|
1963
2372
|
reviewDeps,
|
|
1964
2373
|
{
|
|
1965
2374
|
agentId,
|
|
1966
|
-
session
|
|
2375
|
+
session,
|
|
2376
|
+
usageTracker,
|
|
2377
|
+
usageLimits: config.usageLimits
|
|
1967
2378
|
},
|
|
1968
2379
|
{
|
|
1969
2380
|
maxConsecutiveErrors: config.maxConsecutiveErrors,
|
|
@@ -1974,7 +2385,8 @@ async function startAgentRouter() {
|
|
|
1974
2385
|
synthesizeRepos: agentConfig?.synthesize_repos,
|
|
1975
2386
|
githubUsername,
|
|
1976
2387
|
label,
|
|
1977
|
-
apiKey: config.apiKey
|
|
2388
|
+
apiKey: config.apiKey,
|
|
2389
|
+
usageLimits: config.usageLimits
|
|
1978
2390
|
}
|
|
1979
2391
|
);
|
|
1980
2392
|
router.stop();
|
|
@@ -2021,6 +2433,7 @@ function startAgentByIndex(config, agentIndex, pollIntervalMs, auth, githubUsern
|
|
|
2021
2433
|
routerRelay.start();
|
|
2022
2434
|
}
|
|
2023
2435
|
const session = createSessionTracker();
|
|
2436
|
+
const usageTracker = new UsageTracker();
|
|
2024
2437
|
const model = agentConfig?.model ?? "unknown";
|
|
2025
2438
|
const tool = agentConfig?.tool ?? "unknown";
|
|
2026
2439
|
const roles = agentConfig ? computeRoles(agentConfig) : void 0;
|
|
@@ -2029,7 +2442,7 @@ function startAgentByIndex(config, agentIndex, pollIntervalMs, auth, githubUsern
|
|
|
2029
2442
|
config.platformUrl,
|
|
2030
2443
|
{ model, tool },
|
|
2031
2444
|
reviewDeps,
|
|
2032
|
-
{ agentId, session },
|
|
2445
|
+
{ agentId, session, usageTracker, usageLimits: config.usageLimits },
|
|
2033
2446
|
{
|
|
2034
2447
|
pollIntervalMs,
|
|
2035
2448
|
maxConsecutiveErrors: config.maxConsecutiveErrors,
|
|
@@ -2040,7 +2453,8 @@ function startAgentByIndex(config, agentIndex, pollIntervalMs, auth, githubUsern
|
|
|
2040
2453
|
synthesizeRepos: agentConfig?.synthesize_repos,
|
|
2041
2454
|
githubUsername,
|
|
2042
2455
|
label,
|
|
2043
|
-
apiKey: config.apiKey
|
|
2456
|
+
apiKey: config.apiKey,
|
|
2457
|
+
usageLimits: config.usageLimits
|
|
2044
2458
|
}
|
|
2045
2459
|
).finally(() => {
|
|
2046
2460
|
routerRelay?.stop();
|
|
@@ -2115,7 +2529,7 @@ agentCommand.command("start").description("Start agents in polling mode").option
|
|
|
2115
2529
|
});
|
|
2116
2530
|
|
|
2117
2531
|
// src/index.ts
|
|
2118
|
-
var program = new Command2().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version("0.
|
|
2532
|
+
var program = new Command2().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version("0.15.0");
|
|
2119
2533
|
program.addCommand(agentCommand);
|
|
2120
2534
|
program.action(() => {
|
|
2121
2535
|
startAgentRouter();
|