@j0hanz/code-review-analyst-mcp 1.2.1 → 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 +135 -67
- 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 +5 -1
- package/dist/lib/tool-factory.js +48 -54
- 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 +30 -25
- 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 +26 -20
- package/dist/tools/generate-test-plan.js +34 -28
- package/dist/tools/index.js +9 -2
- package/dist/tools/inspect-code-quality.js +46 -40
- package/dist/tools/suggest-search-replace.js +34 -27
- 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,9 +169,9 @@ 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
|
}
|
|
@@ -183,7 +234,7 @@ function toNumericCode(candidate) {
|
|
|
183
234
|
if (typeof candidate === 'number' && Number.isFinite(candidate)) {
|
|
184
235
|
return candidate;
|
|
185
236
|
}
|
|
186
|
-
if (typeof candidate === 'string' &&
|
|
237
|
+
if (typeof candidate === 'string' && DIGITS_ONLY_PATTERN.test(candidate)) {
|
|
187
238
|
return Number.parseInt(candidate, 10);
|
|
188
239
|
}
|
|
189
240
|
return undefined;
|
|
@@ -195,43 +246,37 @@ function toUpperStringCode(candidate) {
|
|
|
195
246
|
const normalized = candidate.trim().toUpperCase();
|
|
196
247
|
return normalized.length > 0 ? normalized : undefined;
|
|
197
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
|
+
}
|
|
198
267
|
function getNumericErrorCode(error) {
|
|
199
268
|
const record = getNestedError(error);
|
|
200
269
|
if (!record) {
|
|
201
270
|
return undefined;
|
|
202
271
|
}
|
|
203
|
-
|
|
204
|
-
if (fromStatus !== undefined) {
|
|
205
|
-
return fromStatus;
|
|
206
|
-
}
|
|
207
|
-
const fromStatusCode = toNumericCode(record.statusCode);
|
|
208
|
-
if (fromStatusCode !== undefined) {
|
|
209
|
-
return fromStatusCode;
|
|
210
|
-
}
|
|
211
|
-
const fromCode = toNumericCode(record.code);
|
|
212
|
-
if (fromCode !== undefined) {
|
|
213
|
-
return fromCode;
|
|
214
|
-
}
|
|
215
|
-
return undefined;
|
|
272
|
+
return findFirstNumericCode(record, ['status', 'statusCode', 'code']);
|
|
216
273
|
}
|
|
217
274
|
function getTransientErrorCode(error) {
|
|
218
275
|
const record = getNestedError(error);
|
|
219
276
|
if (!record) {
|
|
220
277
|
return undefined;
|
|
221
278
|
}
|
|
222
|
-
|
|
223
|
-
if (fromCode !== undefined) {
|
|
224
|
-
return fromCode;
|
|
225
|
-
}
|
|
226
|
-
const fromStatus = toUpperStringCode(record.status);
|
|
227
|
-
if (fromStatus !== undefined) {
|
|
228
|
-
return fromStatus;
|
|
229
|
-
}
|
|
230
|
-
const fromStatusText = toUpperStringCode(record.statusText);
|
|
231
|
-
if (fromStatusText !== undefined) {
|
|
232
|
-
return fromStatusText;
|
|
233
|
-
}
|
|
234
|
-
return undefined;
|
|
279
|
+
return findFirstStringCode(record, ['code', 'status', 'statusText']);
|
|
235
280
|
}
|
|
236
281
|
function shouldRetry(error) {
|
|
237
282
|
const numericCode = getNumericErrorCode(error);
|
|
@@ -254,21 +299,25 @@ function getRetryDelayMs(attempt) {
|
|
|
254
299
|
return Math.min(RETRY_DELAY_MAX_MS, boundedDelay + jitter);
|
|
255
300
|
}
|
|
256
301
|
function buildGenerationConfig(request, abortSignal) {
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const thinkingConfig = getThinkingConfig(request.thinkingBudget);
|
|
261
|
-
const safetySettings = getSafetySettings(getSafetyThreshold());
|
|
262
|
-
return {
|
|
302
|
+
const includeThoughts = request.includeThoughts ?? getDefaultIncludeThoughts();
|
|
303
|
+
const thinkingConfig = getThinkingConfig(request.thinkingBudget, includeThoughts);
|
|
304
|
+
const config = {
|
|
263
305
|
temperature: request.temperature ?? 0.2,
|
|
264
306
|
maxOutputTokens: request.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS,
|
|
265
307
|
responseMimeType: 'application/json',
|
|
266
308
|
responseSchema: request.responseSchema,
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
309
|
+
safetySettings: getSafetySettings(getSafetyThreshold()),
|
|
310
|
+
topP: 0.95,
|
|
311
|
+
topK: 40,
|
|
270
312
|
abortSignal,
|
|
271
313
|
};
|
|
314
|
+
if (request.systemInstruction) {
|
|
315
|
+
config.systemInstruction = request.systemInstruction;
|
|
316
|
+
}
|
|
317
|
+
if (thinkingConfig) {
|
|
318
|
+
config.thinkingConfig = thinkingConfig;
|
|
319
|
+
}
|
|
320
|
+
return config;
|
|
272
321
|
}
|
|
273
322
|
function combineSignals(signal, requestSignal) {
|
|
274
323
|
return requestSignal ? AbortSignal.any([signal, requestSignal]) : signal;
|
|
@@ -284,6 +333,12 @@ function parseStructuredResponse(responseText) {
|
|
|
284
333
|
throw new Error(`Model produced invalid JSON: ${getErrorMessage(error)}`);
|
|
285
334
|
}
|
|
286
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
|
+
}
|
|
287
342
|
async function generateContentWithTimeout(request, model, timeoutMs) {
|
|
288
343
|
const controller = new AbortController();
|
|
289
344
|
const timeout = setTimeout(() => {
|
|
@@ -303,7 +358,7 @@ async function generateContentWithTimeout(request, model, timeoutMs) {
|
|
|
303
358
|
throw new Error('Gemini request was cancelled.');
|
|
304
359
|
}
|
|
305
360
|
if (controller.signal.aborted) {
|
|
306
|
-
throw new Error(
|
|
361
|
+
throw new Error(formatTimeoutErrorMessage(timeoutMs), { cause: error });
|
|
307
362
|
}
|
|
308
363
|
throw error;
|
|
309
364
|
}
|
|
@@ -325,7 +380,7 @@ async function executeAttempt(request, model, timeoutMs, attempt, onLog) {
|
|
|
325
380
|
});
|
|
326
381
|
return parseStructuredResponse(response.text);
|
|
327
382
|
}
|
|
328
|
-
async function waitBeforeRetry(attempt, error, onLog) {
|
|
383
|
+
async function waitBeforeRetry(attempt, error, onLog, requestSignal) {
|
|
329
384
|
const delayMs = getRetryDelayMs(attempt);
|
|
330
385
|
const reason = getErrorMessage(error);
|
|
331
386
|
await emitGeminiLog(onLog, 'warning', {
|
|
@@ -336,7 +391,20 @@ async function waitBeforeRetry(attempt, error, onLog) {
|
|
|
336
391
|
reason,
|
|
337
392
|
},
|
|
338
393
|
});
|
|
339
|
-
|
|
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
|
+
}
|
|
340
408
|
}
|
|
341
409
|
async function throwGeminiFailure(maxRetries, lastError, onLog) {
|
|
342
410
|
const attempts = maxRetries + 1;
|
|
@@ -358,27 +426,29 @@ async function runWithRetries(request, model, timeoutMs, maxRetries, onLog) {
|
|
|
358
426
|
}
|
|
359
427
|
catch (error) {
|
|
360
428
|
lastError = error;
|
|
361
|
-
if (attempt
|
|
429
|
+
if (!canRetryAttempt(attempt, maxRetries, error)) {
|
|
362
430
|
break;
|
|
363
431
|
}
|
|
364
|
-
await waitBeforeRetry(attempt, error, onLog);
|
|
432
|
+
await waitBeforeRetry(attempt, error, onLog, request.signal);
|
|
365
433
|
}
|
|
366
434
|
}
|
|
367
435
|
return throwGeminiFailure(maxRetries, lastError, onLog);
|
|
368
436
|
}
|
|
437
|
+
function canRetryAttempt(attempt, maxRetries, error) {
|
|
438
|
+
return attempt < maxRetries && shouldRetry(error);
|
|
439
|
+
}
|
|
369
440
|
async function waitForConcurrencySlot(limit, requestSignal) {
|
|
370
441
|
const waitLimitMs = concurrencyWaitMsConfig.get();
|
|
371
442
|
const pollMs = concurrencyPollMsConfig.get();
|
|
372
|
-
const
|
|
443
|
+
const deadline = performance.now() + waitLimitMs;
|
|
373
444
|
while (activeCalls >= limit) {
|
|
374
445
|
if (requestSignal?.aborted) {
|
|
375
446
|
throw new Error('Gemini request was cancelled.');
|
|
376
447
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
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));
|
|
380
450
|
}
|
|
381
|
-
await sleep(pollMs, undefined,
|
|
451
|
+
await sleep(pollMs, undefined, SLEEP_UNREF_OPTIONS);
|
|
382
452
|
}
|
|
383
453
|
}
|
|
384
454
|
export async function generateStructuredJson(request) {
|
|
@@ -390,9 +460,7 @@ export async function generateStructuredJson(request) {
|
|
|
390
460
|
await waitForConcurrencySlot(limit, request.signal);
|
|
391
461
|
activeCalls += 1;
|
|
392
462
|
try {
|
|
393
|
-
return await geminiContext.run({ requestId: nextRequestId(), model },
|
|
394
|
-
return runWithRetries(request, model, timeoutMs, maxRetries, onLog);
|
|
395
|
-
});
|
|
463
|
+
return await geminiContext.run({ requestId: nextRequestId(), model }, () => runWithRetries(request, model, timeoutMs, maxRetries, onLog));
|
|
396
464
|
}
|
|
397
465
|
finally {
|
|
398
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[];
|