@j0hanz/code-review-analyst-mcp 1.2.0 → 1.3.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 +12 -3
- package/dist/instructions.md +4 -146
- package/dist/lib/context-budget.d.ts +2 -2
- package/dist/lib/context-budget.js +12 -6
- package/dist/lib/diff-budget.js +6 -2
- package/dist/lib/diff-parser.js +31 -36
- package/dist/lib/env-config.d.ts +1 -0
- package/dist/lib/env-config.js +9 -3
- package/dist/lib/errors.d.ts +1 -0
- package/dist/lib/errors.js +5 -5
- package/dist/lib/gemini-schema.js +2 -1
- package/dist/lib/gemini.js +139 -68
- package/dist/lib/model-config.d.ts +14 -2
- package/dist/lib/model-config.js +30 -6
- package/dist/lib/tool-contracts.d.ts +245 -0
- package/dist/lib/tool-contracts.js +302 -0
- package/dist/lib/tool-factory.d.ts +7 -1
- package/dist/lib/tool-factory.js +85 -53
- package/dist/lib/tool-response.js +10 -12
- package/dist/lib/types.d.ts +3 -3
- package/dist/prompts/index.js +47 -41
- package/dist/resources/index.d.ts +1 -1
- package/dist/resources/index.js +80 -18
- package/dist/resources/instructions.d.ts +1 -0
- package/dist/resources/instructions.js +59 -0
- package/dist/resources/server-config.d.ts +1 -0
- package/dist/resources/server-config.js +70 -0
- package/dist/resources/tool-catalog.d.ts +1 -0
- package/dist/resources/tool-catalog.js +39 -0
- package/dist/resources/tool-info.d.ts +5 -0
- package/dist/resources/tool-info.js +122 -0
- package/dist/resources/workflows.d.ts +1 -0
- package/dist/resources/workflows.js +72 -0
- package/dist/schemas/inputs.d.ts +8 -0
- package/dist/schemas/inputs.js +20 -18
- package/dist/schemas/outputs.d.ts +17 -1
- package/dist/schemas/outputs.js +84 -52
- package/dist/server.js +25 -26
- package/dist/tools/analyze-complexity.d.ts +2 -0
- package/dist/tools/analyze-complexity.js +45 -0
- package/dist/tools/analyze-pr-impact.js +31 -26
- package/dist/tools/detect-api-breaking.d.ts +2 -0
- package/dist/tools/detect-api-breaking.js +42 -0
- package/dist/tools/generate-review-summary.js +27 -21
- package/dist/tools/generate-test-plan.js +35 -29
- package/dist/tools/index.js +9 -2
- package/dist/tools/inspect-code-quality.js +51 -41
- package/dist/tools/suggest-search-replace.js +34 -24
- package/package.json +1 -1
package/dist/lib/gemini.js
CHANGED
|
@@ -9,22 +9,31 @@ import { createCachedEnvInt } from './env-config.js';
|
|
|
9
9
|
import { getErrorMessage, RETRYABLE_UPSTREAM_ERROR_PATTERN } from './errors.js';
|
|
10
10
|
// Lazy-cached: first call happens after parseCommandLineArgs() sets GEMINI_MODEL.
|
|
11
11
|
let _defaultModel;
|
|
12
|
+
const DEFAULT_MODEL = 'gemini-2.5-flash';
|
|
13
|
+
const GEMINI_MODEL_ENV_VAR = 'GEMINI_MODEL';
|
|
14
|
+
const GEMINI_HARM_BLOCK_THRESHOLD_ENV_VAR = 'GEMINI_HARM_BLOCK_THRESHOLD';
|
|
15
|
+
const GEMINI_INCLUDE_THOUGHTS_ENV_VAR = 'GEMINI_INCLUDE_THOUGHTS';
|
|
16
|
+
const GEMINI_API_KEY_ENV_VAR = 'GEMINI_API_KEY';
|
|
17
|
+
const GOOGLE_API_KEY_ENV_VAR = 'GOOGLE_API_KEY';
|
|
12
18
|
function getDefaultModel() {
|
|
13
19
|
if (_defaultModel !== undefined)
|
|
14
20
|
return _defaultModel;
|
|
15
|
-
const value = process.env
|
|
21
|
+
const value = process.env[GEMINI_MODEL_ENV_VAR] ?? DEFAULT_MODEL;
|
|
16
22
|
_defaultModel = value;
|
|
17
23
|
return value;
|
|
18
24
|
}
|
|
19
|
-
const DEFAULT_MAX_RETRIES =
|
|
20
|
-
const DEFAULT_TIMEOUT_MS =
|
|
25
|
+
const DEFAULT_MAX_RETRIES = 3;
|
|
26
|
+
const DEFAULT_TIMEOUT_MS = 90_000;
|
|
21
27
|
const DEFAULT_MAX_OUTPUT_TOKENS = 16_384;
|
|
22
28
|
const RETRY_DELAY_BASE_MS = 300;
|
|
23
29
|
const RETRY_DELAY_MAX_MS = 5_000;
|
|
24
30
|
const RETRY_JITTER_RATIO = 0.2;
|
|
25
31
|
const DEFAULT_SAFETY_THRESHOLD = HarmBlockThreshold.BLOCK_NONE;
|
|
32
|
+
const DEFAULT_INCLUDE_THOUGHTS = false;
|
|
26
33
|
const UNKNOWN_REQUEST_CONTEXT_VALUE = 'unknown';
|
|
27
34
|
const RETRYABLE_NUMERIC_CODES = new Set([429, 500, 502, 503, 504]);
|
|
35
|
+
const DIGITS_ONLY_PATTERN = /^\d+$/;
|
|
36
|
+
const SLEEP_UNREF_OPTIONS = { ref: false };
|
|
28
37
|
const maxConcurrentCallsConfig = createCachedEnvInt('MAX_CONCURRENT_CALLS', 10);
|
|
29
38
|
const concurrencyWaitMsConfig = createCachedEnvInt('MAX_CONCURRENT_CALLS_WAIT_MS', 2_000);
|
|
30
39
|
const concurrencyPollMsConfig = createCachedEnvInt('MAX_CONCURRENT_CALLS_POLL_MS', 25);
|
|
@@ -54,9 +63,11 @@ const SAFETY_THRESHOLD_BY_NAME = {
|
|
|
54
63
|
};
|
|
55
64
|
let cachedSafetyThresholdEnv;
|
|
56
65
|
let cachedSafetyThreshold = DEFAULT_SAFETY_THRESHOLD;
|
|
66
|
+
let cachedIncludeThoughtsEnv;
|
|
67
|
+
let cachedIncludeThoughts = DEFAULT_INCLUDE_THOUGHTS;
|
|
57
68
|
const safetySettingsCache = new Map();
|
|
58
69
|
function getSafetyThreshold() {
|
|
59
|
-
const threshold = process.env
|
|
70
|
+
const threshold = process.env[GEMINI_HARM_BLOCK_THRESHOLD_ENV_VAR];
|
|
60
71
|
if (threshold === cachedSafetyThresholdEnv) {
|
|
61
72
|
return cachedSafetyThreshold;
|
|
62
73
|
}
|
|
@@ -65,30 +76,70 @@ function getSafetyThreshold() {
|
|
|
65
76
|
cachedSafetyThreshold = DEFAULT_SAFETY_THRESHOLD;
|
|
66
77
|
return cachedSafetyThreshold;
|
|
67
78
|
}
|
|
68
|
-
const
|
|
69
|
-
if (
|
|
70
|
-
cachedSafetyThreshold =
|
|
71
|
-
SAFETY_THRESHOLD_BY_NAME[normalizedThreshold];
|
|
79
|
+
const parsedThreshold = parseSafetyThreshold(threshold);
|
|
80
|
+
if (parsedThreshold) {
|
|
81
|
+
cachedSafetyThreshold = parsedThreshold;
|
|
72
82
|
return cachedSafetyThreshold;
|
|
73
83
|
}
|
|
74
84
|
cachedSafetyThreshold = DEFAULT_SAFETY_THRESHOLD;
|
|
75
85
|
return cachedSafetyThreshold;
|
|
76
86
|
}
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
function parseSafetyThreshold(threshold) {
|
|
88
|
+
const normalizedThreshold = threshold.trim().toUpperCase();
|
|
89
|
+
if (!(normalizedThreshold in SAFETY_THRESHOLD_BY_NAME)) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
return SAFETY_THRESHOLD_BY_NAME[normalizedThreshold];
|
|
93
|
+
}
|
|
94
|
+
function getThinkingConfig(thinkingBudget, includeThoughts) {
|
|
95
|
+
if (thinkingBudget === undefined) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
if (includeThoughts) {
|
|
99
|
+
return { includeThoughts: true, thinkingBudget };
|
|
100
|
+
}
|
|
101
|
+
return { thinkingBudget };
|
|
102
|
+
}
|
|
103
|
+
function parseBooleanEnv(value) {
|
|
104
|
+
const normalized = value.trim().toLowerCase();
|
|
105
|
+
if (normalized.length === 0) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
if (normalized === '1' ||
|
|
109
|
+
normalized === 'true' ||
|
|
110
|
+
normalized === 'yes' ||
|
|
111
|
+
normalized === 'on') {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
if (normalized === '0' ||
|
|
115
|
+
normalized === 'false' ||
|
|
116
|
+
normalized === 'no' ||
|
|
117
|
+
normalized === 'off') {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
function getDefaultIncludeThoughts() {
|
|
123
|
+
const value = process.env[GEMINI_INCLUDE_THOUGHTS_ENV_VAR];
|
|
124
|
+
if (value === cachedIncludeThoughtsEnv) {
|
|
125
|
+
return cachedIncludeThoughts;
|
|
126
|
+
}
|
|
127
|
+
cachedIncludeThoughtsEnv = value;
|
|
128
|
+
if (!value) {
|
|
129
|
+
cachedIncludeThoughts = DEFAULT_INCLUDE_THOUGHTS;
|
|
130
|
+
return cachedIncludeThoughts;
|
|
131
|
+
}
|
|
132
|
+
cachedIncludeThoughts = parseBooleanEnv(value) ?? DEFAULT_INCLUDE_THOUGHTS;
|
|
133
|
+
return cachedIncludeThoughts;
|
|
81
134
|
}
|
|
82
135
|
function getSafetySettings(threshold) {
|
|
83
136
|
const cached = safetySettingsCache.get(threshold);
|
|
84
137
|
if (cached) {
|
|
85
138
|
return cached;
|
|
86
139
|
}
|
|
87
|
-
const settings = new Array(
|
|
88
|
-
let index = 0;
|
|
140
|
+
const settings = new Array();
|
|
89
141
|
for (const category of SAFETY_CATEGORIES) {
|
|
90
|
-
settings
|
|
91
|
-
index += 1;
|
|
142
|
+
settings.push({ category, threshold });
|
|
92
143
|
}
|
|
93
144
|
safetySettingsCache.set(threshold, settings);
|
|
94
145
|
return settings;
|
|
@@ -118,14 +169,17 @@ export function getCurrentRequestId() {
|
|
|
118
169
|
return context?.requestId ?? UNKNOWN_REQUEST_CONTEXT_VALUE;
|
|
119
170
|
}
|
|
120
171
|
function getApiKey() {
|
|
121
|
-
const apiKey = process.env
|
|
172
|
+
const apiKey = process.env[GEMINI_API_KEY_ENV_VAR] ?? process.env[GOOGLE_API_KEY_ENV_VAR];
|
|
122
173
|
if (!apiKey) {
|
|
123
|
-
throw new Error(
|
|
174
|
+
throw new Error(`Missing ${GEMINI_API_KEY_ENV_VAR} or ${GOOGLE_API_KEY_ENV_VAR}.`);
|
|
124
175
|
}
|
|
125
176
|
return apiKey;
|
|
126
177
|
}
|
|
127
178
|
function getClient() {
|
|
128
|
-
cachedClient ??= new GoogleGenAI({
|
|
179
|
+
cachedClient ??= new GoogleGenAI({
|
|
180
|
+
apiKey: getApiKey(),
|
|
181
|
+
apiVersion: 'v1beta',
|
|
182
|
+
});
|
|
129
183
|
return cachedClient;
|
|
130
184
|
}
|
|
131
185
|
export function setClientForTesting(client) {
|
|
@@ -180,7 +234,7 @@ function toNumericCode(candidate) {
|
|
|
180
234
|
if (typeof candidate === 'number' && Number.isFinite(candidate)) {
|
|
181
235
|
return candidate;
|
|
182
236
|
}
|
|
183
|
-
if (typeof candidate === 'string' &&
|
|
237
|
+
if (typeof candidate === 'string' && DIGITS_ONLY_PATTERN.test(candidate)) {
|
|
184
238
|
return Number.parseInt(candidate, 10);
|
|
185
239
|
}
|
|
186
240
|
return undefined;
|
|
@@ -192,43 +246,37 @@ function toUpperStringCode(candidate) {
|
|
|
192
246
|
const normalized = candidate.trim().toUpperCase();
|
|
193
247
|
return normalized.length > 0 ? normalized : undefined;
|
|
194
248
|
}
|
|
249
|
+
function findFirstNumericCode(record, keys) {
|
|
250
|
+
for (const key of keys) {
|
|
251
|
+
const numericCode = toNumericCode(record[key]);
|
|
252
|
+
if (numericCode !== undefined) {
|
|
253
|
+
return numericCode;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
function findFirstStringCode(record, keys) {
|
|
259
|
+
for (const key of keys) {
|
|
260
|
+
const stringCode = toUpperStringCode(record[key]);
|
|
261
|
+
if (stringCode !== undefined) {
|
|
262
|
+
return stringCode;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
195
267
|
function getNumericErrorCode(error) {
|
|
196
268
|
const record = getNestedError(error);
|
|
197
269
|
if (!record) {
|
|
198
270
|
return undefined;
|
|
199
271
|
}
|
|
200
|
-
|
|
201
|
-
if (fromStatus !== undefined) {
|
|
202
|
-
return fromStatus;
|
|
203
|
-
}
|
|
204
|
-
const fromStatusCode = toNumericCode(record.statusCode);
|
|
205
|
-
if (fromStatusCode !== undefined) {
|
|
206
|
-
return fromStatusCode;
|
|
207
|
-
}
|
|
208
|
-
const fromCode = toNumericCode(record.code);
|
|
209
|
-
if (fromCode !== undefined) {
|
|
210
|
-
return fromCode;
|
|
211
|
-
}
|
|
212
|
-
return undefined;
|
|
272
|
+
return findFirstNumericCode(record, ['status', 'statusCode', 'code']);
|
|
213
273
|
}
|
|
214
274
|
function getTransientErrorCode(error) {
|
|
215
275
|
const record = getNestedError(error);
|
|
216
276
|
if (!record) {
|
|
217
277
|
return undefined;
|
|
218
278
|
}
|
|
219
|
-
|
|
220
|
-
if (fromCode !== undefined) {
|
|
221
|
-
return fromCode;
|
|
222
|
-
}
|
|
223
|
-
const fromStatus = toUpperStringCode(record.status);
|
|
224
|
-
if (fromStatus !== undefined) {
|
|
225
|
-
return fromStatus;
|
|
226
|
-
}
|
|
227
|
-
const fromStatusText = toUpperStringCode(record.statusText);
|
|
228
|
-
if (fromStatusText !== undefined) {
|
|
229
|
-
return fromStatusText;
|
|
230
|
-
}
|
|
231
|
-
return undefined;
|
|
279
|
+
return findFirstStringCode(record, ['code', 'status', 'statusText']);
|
|
232
280
|
}
|
|
233
281
|
function shouldRetry(error) {
|
|
234
282
|
const numericCode = getNumericErrorCode(error);
|
|
@@ -251,21 +299,25 @@ function getRetryDelayMs(attempt) {
|
|
|
251
299
|
return Math.min(RETRY_DELAY_MAX_MS, boundedDelay + jitter);
|
|
252
300
|
}
|
|
253
301
|
function buildGenerationConfig(request, abortSignal) {
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const thinkingConfig = getThinkingConfig(request.thinkingBudget);
|
|
258
|
-
const safetySettings = getSafetySettings(getSafetyThreshold());
|
|
259
|
-
return {
|
|
302
|
+
const includeThoughts = request.includeThoughts ?? getDefaultIncludeThoughts();
|
|
303
|
+
const thinkingConfig = getThinkingConfig(request.thinkingBudget, includeThoughts);
|
|
304
|
+
const config = {
|
|
260
305
|
temperature: request.temperature ?? 0.2,
|
|
261
306
|
maxOutputTokens: request.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS,
|
|
262
307
|
responseMimeType: 'application/json',
|
|
263
308
|
responseSchema: request.responseSchema,
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
309
|
+
safetySettings: getSafetySettings(getSafetyThreshold()),
|
|
310
|
+
topP: 0.95,
|
|
311
|
+
topK: 40,
|
|
267
312
|
abortSignal,
|
|
268
313
|
};
|
|
314
|
+
if (request.systemInstruction) {
|
|
315
|
+
config.systemInstruction = request.systemInstruction;
|
|
316
|
+
}
|
|
317
|
+
if (thinkingConfig) {
|
|
318
|
+
config.thinkingConfig = thinkingConfig;
|
|
319
|
+
}
|
|
320
|
+
return config;
|
|
269
321
|
}
|
|
270
322
|
function combineSignals(signal, requestSignal) {
|
|
271
323
|
return requestSignal ? AbortSignal.any([signal, requestSignal]) : signal;
|
|
@@ -281,6 +333,12 @@ function parseStructuredResponse(responseText) {
|
|
|
281
333
|
throw new Error(`Model produced invalid JSON: ${getErrorMessage(error)}`);
|
|
282
334
|
}
|
|
283
335
|
}
|
|
336
|
+
function formatTimeoutErrorMessage(timeoutMs) {
|
|
337
|
+
return `Gemini request timed out after ${formatNumber(timeoutMs)}ms.`;
|
|
338
|
+
}
|
|
339
|
+
function formatConcurrencyLimitErrorMessage(limit, waitLimitMs) {
|
|
340
|
+
return `Too many concurrent Gemini calls (limit: ${formatNumber(limit)}; waited ${formatNumber(waitLimitMs)}ms).`;
|
|
341
|
+
}
|
|
284
342
|
async function generateContentWithTimeout(request, model, timeoutMs) {
|
|
285
343
|
const controller = new AbortController();
|
|
286
344
|
const timeout = setTimeout(() => {
|
|
@@ -300,7 +358,7 @@ async function generateContentWithTimeout(request, model, timeoutMs) {
|
|
|
300
358
|
throw new Error('Gemini request was cancelled.');
|
|
301
359
|
}
|
|
302
360
|
if (controller.signal.aborted) {
|
|
303
|
-
throw new Error(
|
|
361
|
+
throw new Error(formatTimeoutErrorMessage(timeoutMs), { cause: error });
|
|
304
362
|
}
|
|
305
363
|
throw error;
|
|
306
364
|
}
|
|
@@ -322,7 +380,7 @@ async function executeAttempt(request, model, timeoutMs, attempt, onLog) {
|
|
|
322
380
|
});
|
|
323
381
|
return parseStructuredResponse(response.text);
|
|
324
382
|
}
|
|
325
|
-
async function waitBeforeRetry(attempt, error, onLog) {
|
|
383
|
+
async function waitBeforeRetry(attempt, error, onLog, requestSignal) {
|
|
326
384
|
const delayMs = getRetryDelayMs(attempt);
|
|
327
385
|
const reason = getErrorMessage(error);
|
|
328
386
|
await emitGeminiLog(onLog, 'warning', {
|
|
@@ -333,7 +391,20 @@ async function waitBeforeRetry(attempt, error, onLog) {
|
|
|
333
391
|
reason,
|
|
334
392
|
},
|
|
335
393
|
});
|
|
336
|
-
|
|
394
|
+
if (requestSignal?.aborted) {
|
|
395
|
+
throw new Error('Gemini request was cancelled.');
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
await sleep(delayMs, undefined, requestSignal
|
|
399
|
+
? { ...SLEEP_UNREF_OPTIONS, signal: requestSignal }
|
|
400
|
+
: SLEEP_UNREF_OPTIONS);
|
|
401
|
+
}
|
|
402
|
+
catch (sleepError) {
|
|
403
|
+
if (requestSignal?.aborted) {
|
|
404
|
+
throw new Error('Gemini request was cancelled.');
|
|
405
|
+
}
|
|
406
|
+
throw sleepError;
|
|
407
|
+
}
|
|
337
408
|
}
|
|
338
409
|
async function throwGeminiFailure(maxRetries, lastError, onLog) {
|
|
339
410
|
const attempts = maxRetries + 1;
|
|
@@ -355,27 +426,29 @@ async function runWithRetries(request, model, timeoutMs, maxRetries, onLog) {
|
|
|
355
426
|
}
|
|
356
427
|
catch (error) {
|
|
357
428
|
lastError = error;
|
|
358
|
-
if (attempt
|
|
429
|
+
if (!canRetryAttempt(attempt, maxRetries, error)) {
|
|
359
430
|
break;
|
|
360
431
|
}
|
|
361
|
-
await waitBeforeRetry(attempt, error, onLog);
|
|
432
|
+
await waitBeforeRetry(attempt, error, onLog, request.signal);
|
|
362
433
|
}
|
|
363
434
|
}
|
|
364
435
|
return throwGeminiFailure(maxRetries, lastError, onLog);
|
|
365
436
|
}
|
|
437
|
+
function canRetryAttempt(attempt, maxRetries, error) {
|
|
438
|
+
return attempt < maxRetries && shouldRetry(error);
|
|
439
|
+
}
|
|
366
440
|
async function waitForConcurrencySlot(limit, requestSignal) {
|
|
367
441
|
const waitLimitMs = concurrencyWaitMsConfig.get();
|
|
368
442
|
const pollMs = concurrencyPollMsConfig.get();
|
|
369
|
-
const
|
|
443
|
+
const deadline = performance.now() + waitLimitMs;
|
|
370
444
|
while (activeCalls >= limit) {
|
|
371
445
|
if (requestSignal?.aborted) {
|
|
372
446
|
throw new Error('Gemini request was cancelled.');
|
|
373
447
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
throw new Error(`Too many concurrent Gemini calls (limit: ${formatNumber(limit)}; waited ${formatNumber(waitLimitMs)}ms).`);
|
|
448
|
+
if (performance.now() >= deadline) {
|
|
449
|
+
throw new Error(formatConcurrencyLimitErrorMessage(limit, waitLimitMs));
|
|
377
450
|
}
|
|
378
|
-
await sleep(pollMs, undefined,
|
|
451
|
+
await sleep(pollMs, undefined, SLEEP_UNREF_OPTIONS);
|
|
379
452
|
}
|
|
380
453
|
}
|
|
381
454
|
export async function generateStructuredJson(request) {
|
|
@@ -387,9 +460,7 @@ export async function generateStructuredJson(request) {
|
|
|
387
460
|
await waitForConcurrencySlot(limit, request.signal);
|
|
388
461
|
activeCalls += 1;
|
|
389
462
|
try {
|
|
390
|
-
return await geminiContext.run({ requestId: nextRequestId(), model },
|
|
391
|
-
return runWithRetries(request, model, timeoutMs, maxRetries, onLog);
|
|
392
|
-
});
|
|
463
|
+
return await geminiContext.run({ requestId: nextRequestId(), model }, () => runWithRetries(request, model, timeoutMs, maxRetries, onLog));
|
|
393
464
|
}
|
|
394
465
|
finally {
|
|
395
466
|
activeCalls -= 1;
|
|
@@ -3,9 +3,21 @@ export declare const FLASH_MODEL = "gemini-2.5-flash";
|
|
|
3
3
|
/** High-capability model for deep reasoning, quality inspection, and reliable code generation. */
|
|
4
4
|
export declare const PRO_MODEL = "gemini-2.5-pro";
|
|
5
5
|
/** Thinking budget (tokens) for Flash model thinking tasks (test plans, search/replace). */
|
|
6
|
-
export declare const FLASH_THINKING_BUDGET
|
|
6
|
+
export declare const FLASH_THINKING_BUDGET: 8192;
|
|
7
7
|
/** Thinking budget (tokens) for Pro model deep-analysis tasks (code quality inspection). */
|
|
8
|
-
export declare const PRO_THINKING_BUDGET
|
|
8
|
+
export declare const PRO_THINKING_BUDGET: 16384;
|
|
9
|
+
/** Output cap for Flash triage tools (impact, summary). */
|
|
10
|
+
export declare const FLASH_TRIAGE_MAX_OUTPUT_TOKENS: 2048;
|
|
11
|
+
/** Output cap for API breaking-change detection (migration guidance needs room). */
|
|
12
|
+
export declare const FLASH_API_BREAKING_MAX_OUTPUT_TOKENS: 4096;
|
|
13
|
+
/** Output cap for test-plan generation (includes pseudocode snippets). */
|
|
14
|
+
export declare const FLASH_TEST_PLAN_MAX_OUTPUT_TOKENS: 4096;
|
|
15
|
+
/** Output cap for Pro deep review findings. */
|
|
16
|
+
export declare const PRO_REVIEW_MAX_OUTPUT_TOKENS: 8192;
|
|
17
|
+
/** Output cap for Pro search/replace remediation blocks. */
|
|
18
|
+
export declare const PRO_PATCH_MAX_OUTPUT_TOKENS: 4096;
|
|
19
|
+
/** Output cap for Flash complexity analysis reports. */
|
|
20
|
+
export declare const FLASH_COMPLEXITY_MAX_OUTPUT_TOKENS: 2048;
|
|
9
21
|
/** Extended timeout for Pro model calls (ms). Pro thinks longer than Flash. */
|
|
10
22
|
export declare const DEFAULT_TIMEOUT_PRO_MS = 120000;
|
|
11
23
|
export declare const MODEL_TIMEOUT_MS: {
|
package/dist/lib/model-config.js
CHANGED
|
@@ -2,18 +2,42 @@
|
|
|
2
2
|
export const FLASH_MODEL = 'gemini-2.5-flash';
|
|
3
3
|
/** High-capability model for deep reasoning, quality inspection, and reliable code generation. */
|
|
4
4
|
export const PRO_MODEL = 'gemini-2.5-pro';
|
|
5
|
-
const
|
|
6
|
-
|
|
5
|
+
const THINKING_BUDGET_TOKENS = {
|
|
6
|
+
flash: 8_192,
|
|
7
|
+
pro: 16_384,
|
|
8
|
+
};
|
|
9
|
+
const OUTPUT_TOKEN_BUDGET = {
|
|
10
|
+
flashTriage: 2_048,
|
|
11
|
+
flashTestPlan: 4_096,
|
|
12
|
+
flashApiBreaking: 4_096,
|
|
13
|
+
flashComplexity: 2_048,
|
|
14
|
+
proReview: 8_192,
|
|
15
|
+
proPatch: 4_096,
|
|
16
|
+
};
|
|
17
|
+
const DEFAULT_DETECT_HINT = 'detect';
|
|
7
18
|
/** Thinking budget (tokens) for Flash model thinking tasks (test plans, search/replace). */
|
|
8
|
-
export const FLASH_THINKING_BUDGET =
|
|
19
|
+
export const FLASH_THINKING_BUDGET = THINKING_BUDGET_TOKENS.flash;
|
|
9
20
|
/** Thinking budget (tokens) for Pro model deep-analysis tasks (code quality inspection). */
|
|
10
|
-
export const PRO_THINKING_BUDGET =
|
|
21
|
+
export const PRO_THINKING_BUDGET = THINKING_BUDGET_TOKENS.pro;
|
|
22
|
+
/** Output cap for Flash triage tools (impact, summary). */
|
|
23
|
+
export const FLASH_TRIAGE_MAX_OUTPUT_TOKENS = OUTPUT_TOKEN_BUDGET.flashTriage;
|
|
24
|
+
/** Output cap for API breaking-change detection (migration guidance needs room). */
|
|
25
|
+
export const FLASH_API_BREAKING_MAX_OUTPUT_TOKENS = OUTPUT_TOKEN_BUDGET.flashApiBreaking;
|
|
26
|
+
/** Output cap for test-plan generation (includes pseudocode snippets). */
|
|
27
|
+
export const FLASH_TEST_PLAN_MAX_OUTPUT_TOKENS = OUTPUT_TOKEN_BUDGET.flashTestPlan;
|
|
28
|
+
/** Output cap for Pro deep review findings. */
|
|
29
|
+
export const PRO_REVIEW_MAX_OUTPUT_TOKENS = OUTPUT_TOKEN_BUDGET.proReview;
|
|
30
|
+
/** Output cap for Pro search/replace remediation blocks. */
|
|
31
|
+
export const PRO_PATCH_MAX_OUTPUT_TOKENS = OUTPUT_TOKEN_BUDGET.proPatch;
|
|
32
|
+
/** Output cap for Flash complexity analysis reports. */
|
|
33
|
+
export const FLASH_COMPLEXITY_MAX_OUTPUT_TOKENS = OUTPUT_TOKEN_BUDGET.flashComplexity;
|
|
11
34
|
/** Extended timeout for Pro model calls (ms). Pro thinks longer than Flash. */
|
|
12
35
|
export const DEFAULT_TIMEOUT_PRO_MS = 120_000;
|
|
13
36
|
export const MODEL_TIMEOUT_MS = {
|
|
14
37
|
defaultPro: DEFAULT_TIMEOUT_PRO_MS,
|
|
15
38
|
};
|
|
39
|
+
Object.freeze(MODEL_TIMEOUT_MS);
|
|
16
40
|
/** Default language hint when not specified by the user. Tells the model to auto-detect. */
|
|
17
|
-
export const DEFAULT_LANGUAGE =
|
|
41
|
+
export const DEFAULT_LANGUAGE = DEFAULT_DETECT_HINT;
|
|
18
42
|
/** Default test-framework hint when not specified by the user. Tells the model to auto-detect. */
|
|
19
|
-
export const DEFAULT_FRAMEWORK =
|
|
43
|
+
export const DEFAULT_FRAMEWORK = DEFAULT_DETECT_HINT;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
export interface ToolParameterContract {
|
|
2
|
+
name: string;
|
|
3
|
+
type: string;
|
|
4
|
+
required: boolean;
|
|
5
|
+
constraints: string;
|
|
6
|
+
description: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ToolContract {
|
|
9
|
+
name: string;
|
|
10
|
+
purpose: string;
|
|
11
|
+
model: string;
|
|
12
|
+
timeoutMs: number;
|
|
13
|
+
thinkingBudget?: number;
|
|
14
|
+
maxOutputTokens: number;
|
|
15
|
+
params: readonly ToolParameterContract[];
|
|
16
|
+
outputShape: string;
|
|
17
|
+
gotchas: readonly string[];
|
|
18
|
+
crossToolFlow: readonly string[];
|
|
19
|
+
constraints?: readonly string[];
|
|
20
|
+
}
|
|
21
|
+
export declare const TOOL_CONTRACTS: readonly [{
|
|
22
|
+
readonly name: "analyze_pr_impact";
|
|
23
|
+
readonly purpose: "Assess severity, categories, breaking changes, and rollback complexity.";
|
|
24
|
+
readonly model: "gemini-2.5-flash";
|
|
25
|
+
readonly timeoutMs: 90000;
|
|
26
|
+
readonly maxOutputTokens: 2048;
|
|
27
|
+
readonly params: readonly [{
|
|
28
|
+
readonly name: "diff";
|
|
29
|
+
readonly type: "string";
|
|
30
|
+
readonly required: true;
|
|
31
|
+
readonly constraints: "10-120K chars";
|
|
32
|
+
readonly description: "Unified diff text.";
|
|
33
|
+
}, {
|
|
34
|
+
readonly name: "repository";
|
|
35
|
+
readonly type: "string";
|
|
36
|
+
readonly required: true;
|
|
37
|
+
readonly constraints: "1-200 chars";
|
|
38
|
+
readonly description: "Repository identifier (org/repo).";
|
|
39
|
+
}, {
|
|
40
|
+
readonly name: "language";
|
|
41
|
+
readonly type: "string";
|
|
42
|
+
readonly required: false;
|
|
43
|
+
readonly constraints: "2-32 chars";
|
|
44
|
+
readonly description: "Primary language hint.";
|
|
45
|
+
}];
|
|
46
|
+
readonly outputShape: "{severity, categories[], summary, breakingChanges[], affectedAreas[], rollbackComplexity}";
|
|
47
|
+
readonly gotchas: readonly ["Flash triage tool optimized for speed.", "Diff-only analysis (no full-file context)."];
|
|
48
|
+
readonly crossToolFlow: readonly ["severity/categories feed triage and merge-gate decisions."];
|
|
49
|
+
}, {
|
|
50
|
+
readonly name: "generate_review_summary";
|
|
51
|
+
readonly purpose: "Produce PR summary, risk rating, and merge recommendation.";
|
|
52
|
+
readonly model: "gemini-2.5-flash";
|
|
53
|
+
readonly timeoutMs: 90000;
|
|
54
|
+
readonly maxOutputTokens: 2048;
|
|
55
|
+
readonly params: readonly [{
|
|
56
|
+
readonly name: "diff";
|
|
57
|
+
readonly type: "string";
|
|
58
|
+
readonly required: true;
|
|
59
|
+
readonly constraints: "10-120K chars";
|
|
60
|
+
readonly description: "Unified diff text.";
|
|
61
|
+
}, {
|
|
62
|
+
readonly name: "repository";
|
|
63
|
+
readonly type: "string";
|
|
64
|
+
readonly required: true;
|
|
65
|
+
readonly constraints: "1-200 chars";
|
|
66
|
+
readonly description: "Repository identifier (org/repo).";
|
|
67
|
+
}, {
|
|
68
|
+
readonly name: "language";
|
|
69
|
+
readonly type: "string";
|
|
70
|
+
readonly required: false;
|
|
71
|
+
readonly constraints: "2-32 chars";
|
|
72
|
+
readonly description: "Primary language hint.";
|
|
73
|
+
}];
|
|
74
|
+
readonly outputShape: "{summary, overallRisk, keyChanges[], recommendation, stats{filesChanged, linesAdded, linesRemoved}}";
|
|
75
|
+
readonly gotchas: readonly ["stats are computed locally from the diff.", "Flash triage tool optimized for speed."];
|
|
76
|
+
readonly crossToolFlow: readonly ["Use before deep review to decide whether Pro analysis is needed."];
|
|
77
|
+
}, {
|
|
78
|
+
readonly name: "inspect_code_quality";
|
|
79
|
+
readonly purpose: "Deep code review with optional full-file context.";
|
|
80
|
+
readonly model: "gemini-2.5-pro";
|
|
81
|
+
readonly timeoutMs: 120000;
|
|
82
|
+
readonly thinkingBudget: 16384;
|
|
83
|
+
readonly maxOutputTokens: 8192;
|
|
84
|
+
readonly params: readonly [{
|
|
85
|
+
readonly name: "diff";
|
|
86
|
+
readonly type: "string";
|
|
87
|
+
readonly required: true;
|
|
88
|
+
readonly constraints: "10-120K chars";
|
|
89
|
+
readonly description: "Unified diff text.";
|
|
90
|
+
}, {
|
|
91
|
+
readonly name: "repository";
|
|
92
|
+
readonly type: "string";
|
|
93
|
+
readonly required: true;
|
|
94
|
+
readonly constraints: "1-200 chars";
|
|
95
|
+
readonly description: "Repository identifier (org/repo).";
|
|
96
|
+
}, {
|
|
97
|
+
readonly name: "language";
|
|
98
|
+
readonly type: "string";
|
|
99
|
+
readonly required: false;
|
|
100
|
+
readonly constraints: "2-32 chars";
|
|
101
|
+
readonly description: "Primary language hint.";
|
|
102
|
+
}, {
|
|
103
|
+
readonly name: "focusAreas";
|
|
104
|
+
readonly type: "string[]";
|
|
105
|
+
readonly required: false;
|
|
106
|
+
readonly constraints: "1-12 items, 2-80 chars each";
|
|
107
|
+
readonly description: "Focused inspection categories.";
|
|
108
|
+
}, {
|
|
109
|
+
readonly name: "maxFindings";
|
|
110
|
+
readonly type: "number";
|
|
111
|
+
readonly required: false;
|
|
112
|
+
readonly constraints: "1-25";
|
|
113
|
+
readonly description: "Post-generation cap applied to findings.";
|
|
114
|
+
}, {
|
|
115
|
+
readonly name: "files";
|
|
116
|
+
readonly type: "object[]";
|
|
117
|
+
readonly required: false;
|
|
118
|
+
readonly constraints: "1-20 files, 100K chars/file";
|
|
119
|
+
readonly description: "Optional full file content context.";
|
|
120
|
+
}];
|
|
121
|
+
readonly outputShape: "{summary, overallRisk, findings[], testsNeeded[], contextualInsights[], totalFindings}";
|
|
122
|
+
readonly gotchas: readonly ["Combined diff + file context is bounded by MAX_CONTEXT_CHARS.", "maxFindings caps output after generation."];
|
|
123
|
+
readonly crossToolFlow: readonly ["findings[].title -> suggest_search_replace.findingTitle", "findings[].explanation -> suggest_search_replace.findingDetails"];
|
|
124
|
+
readonly constraints: readonly ["Context budget (diff + files) < 500K chars."];
|
|
125
|
+
}, {
|
|
126
|
+
readonly name: "suggest_search_replace";
|
|
127
|
+
readonly purpose: "Generate verbatim search/replace fix blocks for one finding.";
|
|
128
|
+
readonly model: "gemini-2.5-pro";
|
|
129
|
+
readonly timeoutMs: 120000;
|
|
130
|
+
readonly thinkingBudget: 16384;
|
|
131
|
+
readonly maxOutputTokens: 4096;
|
|
132
|
+
readonly params: readonly [{
|
|
133
|
+
readonly name: "diff";
|
|
134
|
+
readonly type: "string";
|
|
135
|
+
readonly required: true;
|
|
136
|
+
readonly constraints: "10-120K chars";
|
|
137
|
+
readonly description: "Unified diff containing the target issue.";
|
|
138
|
+
}, {
|
|
139
|
+
readonly name: "findingTitle";
|
|
140
|
+
readonly type: "string";
|
|
141
|
+
readonly required: true;
|
|
142
|
+
readonly constraints: "3-160 chars";
|
|
143
|
+
readonly description: "Short finding title.";
|
|
144
|
+
}, {
|
|
145
|
+
readonly name: "findingDetails";
|
|
146
|
+
readonly type: "string";
|
|
147
|
+
readonly required: true;
|
|
148
|
+
readonly constraints: "10-3000 chars";
|
|
149
|
+
readonly description: "Detailed finding context.";
|
|
150
|
+
}];
|
|
151
|
+
readonly outputShape: "{summary, blocks[], validationChecklist[]}";
|
|
152
|
+
readonly gotchas: readonly ["One finding per call to avoid mixed patch intent.", "search must be exact whitespace-preserving match."];
|
|
153
|
+
readonly crossToolFlow: readonly ["Consumes findings from inspect_code_quality for targeted fixes."];
|
|
154
|
+
readonly constraints: readonly ["One finding per call; verbatim search match required."];
|
|
155
|
+
}, {
|
|
156
|
+
readonly name: "generate_test_plan";
|
|
157
|
+
readonly purpose: "Generate prioritized test cases and coverage guidance.";
|
|
158
|
+
readonly model: "gemini-2.5-flash";
|
|
159
|
+
readonly timeoutMs: 90000;
|
|
160
|
+
readonly thinkingBudget: 8192;
|
|
161
|
+
readonly maxOutputTokens: 4096;
|
|
162
|
+
readonly params: readonly [{
|
|
163
|
+
readonly name: "diff";
|
|
164
|
+
readonly type: "string";
|
|
165
|
+
readonly required: true;
|
|
166
|
+
readonly constraints: "10-120K chars";
|
|
167
|
+
readonly description: "Unified diff text.";
|
|
168
|
+
}, {
|
|
169
|
+
readonly name: "repository";
|
|
170
|
+
readonly type: "string";
|
|
171
|
+
readonly required: true;
|
|
172
|
+
readonly constraints: "1-200 chars";
|
|
173
|
+
readonly description: "Repository identifier (org/repo).";
|
|
174
|
+
}, {
|
|
175
|
+
readonly name: "language";
|
|
176
|
+
readonly type: "string";
|
|
177
|
+
readonly required: false;
|
|
178
|
+
readonly constraints: "2-32 chars";
|
|
179
|
+
readonly description: "Primary language hint.";
|
|
180
|
+
}, {
|
|
181
|
+
readonly name: "testFramework";
|
|
182
|
+
readonly type: "string";
|
|
183
|
+
readonly required: false;
|
|
184
|
+
readonly constraints: "1-50 chars";
|
|
185
|
+
readonly description: "Framework hint (jest, vitest, pytest, node:test).";
|
|
186
|
+
}, {
|
|
187
|
+
readonly name: "maxTestCases";
|
|
188
|
+
readonly type: "number";
|
|
189
|
+
readonly required: false;
|
|
190
|
+
readonly constraints: "1-30";
|
|
191
|
+
readonly description: "Post-generation cap applied to test cases.";
|
|
192
|
+
}];
|
|
193
|
+
readonly outputShape: "{summary, testCases[], coverageSummary}";
|
|
194
|
+
readonly gotchas: readonly ["maxTestCases caps output after generation."];
|
|
195
|
+
readonly crossToolFlow: readonly ["Pair with inspect_code_quality to validate high-risk paths."];
|
|
196
|
+
}, {
|
|
197
|
+
readonly name: "analyze_time_space_complexity";
|
|
198
|
+
readonly purpose: "Analyze Big-O complexity and detect degradations in changed code.";
|
|
199
|
+
readonly model: "gemini-2.5-flash";
|
|
200
|
+
readonly timeoutMs: 90000;
|
|
201
|
+
readonly thinkingBudget: 8192;
|
|
202
|
+
readonly maxOutputTokens: 2048;
|
|
203
|
+
readonly params: readonly [{
|
|
204
|
+
readonly name: "diff";
|
|
205
|
+
readonly type: "string";
|
|
206
|
+
readonly required: true;
|
|
207
|
+
readonly constraints: "10-120K chars";
|
|
208
|
+
readonly description: "Unified diff text.";
|
|
209
|
+
}, {
|
|
210
|
+
readonly name: "language";
|
|
211
|
+
readonly type: "string";
|
|
212
|
+
readonly required: false;
|
|
213
|
+
readonly constraints: "2-32 chars";
|
|
214
|
+
readonly description: "Primary language hint.";
|
|
215
|
+
}];
|
|
216
|
+
readonly outputShape: "{timeComplexity, spaceComplexity, explanation, potentialBottlenecks[], isDegradation}";
|
|
217
|
+
readonly gotchas: readonly ["Analyzes only changed code visible in the diff."];
|
|
218
|
+
readonly crossToolFlow: readonly ["Use for algorithmic/performance-sensitive changes."];
|
|
219
|
+
}, {
|
|
220
|
+
readonly name: "detect_api_breaking_changes";
|
|
221
|
+
readonly purpose: "Detect breaking API/interface changes in a diff.";
|
|
222
|
+
readonly model: "gemini-2.5-flash";
|
|
223
|
+
readonly timeoutMs: 90000;
|
|
224
|
+
readonly maxOutputTokens: 4096;
|
|
225
|
+
readonly params: readonly [{
|
|
226
|
+
readonly name: "diff";
|
|
227
|
+
readonly type: "string";
|
|
228
|
+
readonly required: true;
|
|
229
|
+
readonly constraints: "10-120K chars";
|
|
230
|
+
readonly description: "Unified diff text.";
|
|
231
|
+
}, {
|
|
232
|
+
readonly name: "language";
|
|
233
|
+
readonly type: "string";
|
|
234
|
+
readonly required: false;
|
|
235
|
+
readonly constraints: "2-32 chars";
|
|
236
|
+
readonly description: "Primary language hint.";
|
|
237
|
+
}];
|
|
238
|
+
readonly outputShape: "{hasBreakingChanges, breakingChanges[]}";
|
|
239
|
+
readonly gotchas: readonly ["Targets public API contracts over internal refactors."];
|
|
240
|
+
readonly crossToolFlow: readonly ["Run before merge for API-surface-sensitive changes."];
|
|
241
|
+
}];
|
|
242
|
+
export declare function getToolContracts(): readonly ToolContract[];
|
|
243
|
+
export declare function getToolContract(toolName: string): ToolContract | undefined;
|
|
244
|
+
export declare function requireToolContract(toolName: string): ToolContract;
|
|
245
|
+
export declare function getToolContractNames(): string[];
|