@j0hanz/code-review-analyst-mcp 1.2.1 → 1.4.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/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-cleaner.d.ts +12 -0
- package/dist/lib/diff-cleaner.js +51 -0
- package/dist/lib/diff-parser.js +31 -36
- package/dist/lib/diff-store.d.ts +22 -0
- package/dist/lib/diff-store.js +28 -0
- 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 +222 -0
- package/dist/lib/tool-contracts.js +289 -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 +99 -18
- package/dist/resources/instructions.d.ts +1 -0
- package/dist/resources/instructions.js +69 -0
- package/dist/resources/server-config.d.ts +1 -0
- package/dist/resources/server-config.js +71 -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 +6 -5
- package/dist/schemas/inputs.js +17 -29
- package/dist/schemas/outputs.d.ts +17 -1
- package/dist/schemas/outputs.js +84 -52
- package/dist/server.js +28 -27
- package/dist/tools/analyze-complexity.d.ts +2 -0
- package/dist/tools/analyze-complexity.js +51 -0
- package/dist/tools/analyze-pr-impact.js +32 -20
- package/dist/tools/detect-api-breaking.d.ts +2 -0
- package/dist/tools/detect-api-breaking.js +48 -0
- package/dist/tools/generate-diff.d.ts +2 -0
- package/dist/tools/generate-diff.js +71 -0
- package/dist/tools/generate-review-summary.js +34 -29
- package/dist/tools/generate-test-plan.js +38 -28
- package/dist/tools/index.js +11 -2
- package/dist/tools/inspect-code-quality.js +47 -36
- package/dist/tools/suggest-search-replace.js +34 -20
- package/package.json +1 -2
- package/dist/instructions.md +0 -149
package/dist/index.js
CHANGED
|
@@ -6,6 +6,11 @@ import { createServer } from './server.js';
|
|
|
6
6
|
const SHUTDOWN_SIGNALS = ['SIGINT', 'SIGTERM'];
|
|
7
7
|
const ARG_OPTION_MODEL = 'model';
|
|
8
8
|
const ARG_OPTION_MAX_DIFF_CHARS = 'max-diff-chars';
|
|
9
|
+
const PROCESS_ARGS_START_INDEX = 2;
|
|
10
|
+
const CLI_ENV_MAPPINGS = [
|
|
11
|
+
{ option: ARG_OPTION_MODEL, envVar: 'GEMINI_MODEL' },
|
|
12
|
+
{ option: ARG_OPTION_MAX_DIFF_CHARS, envVar: 'MAX_DIFF_CHARS' },
|
|
13
|
+
];
|
|
9
14
|
const CLI_OPTIONS = {
|
|
10
15
|
[ARG_OPTION_MODEL]: {
|
|
11
16
|
type: 'string',
|
|
@@ -20,14 +25,18 @@ function setStringEnv(name, value) {
|
|
|
20
25
|
process.env[name] = value;
|
|
21
26
|
}
|
|
22
27
|
}
|
|
28
|
+
function applyCliEnvironmentOverrides(values) {
|
|
29
|
+
for (const mapping of CLI_ENV_MAPPINGS) {
|
|
30
|
+
setStringEnv(mapping.envVar, values[mapping.option]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
23
33
|
function parseCommandLineArgs() {
|
|
24
34
|
const { values } = parseArgs({
|
|
25
|
-
args: process.argv.slice(
|
|
35
|
+
args: process.argv.slice(PROCESS_ARGS_START_INDEX),
|
|
26
36
|
options: CLI_OPTIONS,
|
|
27
37
|
strict: false,
|
|
28
38
|
});
|
|
29
|
-
|
|
30
|
-
setStringEnv('MAX_DIFF_CHARS', values[ARG_OPTION_MAX_DIFF_CHARS]);
|
|
39
|
+
applyCliEnvironmentOverrides(values);
|
|
31
40
|
}
|
|
32
41
|
let shuttingDown = false;
|
|
33
42
|
async function shutdown(server) {
|
|
@@ -3,6 +3,6 @@ interface FileContent {
|
|
|
3
3
|
content: string;
|
|
4
4
|
}
|
|
5
5
|
export declare function resetMaxContextCharsCacheForTesting(): void;
|
|
6
|
-
export declare function computeContextSize(diff: string, files?: FileContent[]): number;
|
|
7
|
-
export declare function validateContextBudget(diff: string, files?: FileContent[]): ReturnType<typeof createErrorToolResponse> | undefined;
|
|
6
|
+
export declare function computeContextSize(diff: string, files?: readonly FileContent[]): number;
|
|
7
|
+
export declare function validateContextBudget(diff: string, files?: readonly FileContent[]): ReturnType<typeof createErrorToolResponse> | undefined;
|
|
8
8
|
export {};
|
|
@@ -4,6 +4,16 @@ const DEFAULT_MAX_CONTEXT_CHARS = 500_000;
|
|
|
4
4
|
const MAX_CONTEXT_CHARS_ENV_VAR = 'MAX_CONTEXT_CHARS';
|
|
5
5
|
const BUDGET_ERROR_META = { retryable: false, kind: 'budget' };
|
|
6
6
|
const contextCharsConfig = createCachedEnvInt(MAX_CONTEXT_CHARS_ENV_VAR, DEFAULT_MAX_CONTEXT_CHARS);
|
|
7
|
+
function computeFilesSize(files) {
|
|
8
|
+
let fileSize = 0;
|
|
9
|
+
for (const file of files) {
|
|
10
|
+
fileSize += file.content.length;
|
|
11
|
+
}
|
|
12
|
+
return fileSize;
|
|
13
|
+
}
|
|
14
|
+
function createContextBudgetMessage(size, max) {
|
|
15
|
+
return `Combined context size ${size} chars exceeds limit of ${max} chars.`;
|
|
16
|
+
}
|
|
7
17
|
export function resetMaxContextCharsCacheForTesting() {
|
|
8
18
|
contextCharsConfig.reset();
|
|
9
19
|
}
|
|
@@ -14,17 +24,13 @@ export function computeContextSize(diff, files) {
|
|
|
14
24
|
if (!files || files.length === 0) {
|
|
15
25
|
return diff.length;
|
|
16
26
|
}
|
|
17
|
-
|
|
18
|
-
for (const file of files) {
|
|
19
|
-
fileSize += file.content.length;
|
|
20
|
-
}
|
|
21
|
-
return diff.length + fileSize;
|
|
27
|
+
return diff.length + computeFilesSize(files);
|
|
22
28
|
}
|
|
23
29
|
export function validateContextBudget(diff, files) {
|
|
24
30
|
const size = computeContextSize(diff, files);
|
|
25
31
|
const max = getMaxContextChars();
|
|
26
32
|
if (size > max) {
|
|
27
|
-
return createErrorToolResponse('E_INPUT_TOO_LARGE',
|
|
33
|
+
return createErrorToolResponse('E_INPUT_TOO_LARGE', createContextBudgetMessage(size, max), { providedChars: size, maxChars: max }, BUDGET_ERROR_META);
|
|
28
34
|
}
|
|
29
35
|
return undefined;
|
|
30
36
|
}
|
package/dist/lib/diff-budget.js
CHANGED
|
@@ -11,7 +11,8 @@ export function resetMaxDiffCharsCacheForTesting() {
|
|
|
11
11
|
diffCharsConfig.reset();
|
|
12
12
|
}
|
|
13
13
|
export function exceedsDiffBudget(diff) {
|
|
14
|
-
|
|
14
|
+
const maxChars = getMaxDiffChars();
|
|
15
|
+
return getDiffLength(diff) > maxChars;
|
|
15
16
|
}
|
|
16
17
|
function formatDiffBudgetError(diffLength, maxChars) {
|
|
17
18
|
return `diff exceeds max allowed size (${numberFormatter.format(diffLength)} chars > ${numberFormatter.format(maxChars)} chars)`;
|
|
@@ -20,8 +21,11 @@ export function getDiffBudgetError(diffLength, maxChars = getMaxDiffChars()) {
|
|
|
20
21
|
return formatDiffBudgetError(diffLength, maxChars);
|
|
21
22
|
}
|
|
22
23
|
const BUDGET_ERROR_META = { retryable: false, kind: 'budget' };
|
|
24
|
+
function getDiffLength(diff) {
|
|
25
|
+
return diff.length;
|
|
26
|
+
}
|
|
23
27
|
export function validateDiffBudget(diff) {
|
|
24
|
-
const providedChars = diff
|
|
28
|
+
const providedChars = getDiffLength(diff);
|
|
25
29
|
const maxChars = getMaxDiffChars();
|
|
26
30
|
if (providedChars <= maxChars) {
|
|
27
31
|
return undefined;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const NOISY_EXCLUDE_PATHSPECS: readonly [":(exclude)package-lock.json", ":(exclude)yarn.lock", ":(exclude)pnpm-lock.yaml", ":(exclude)bun.lockb", ":(exclude)*.lock", ":(exclude)dist/", ":(exclude)build/", ":(exclude)out/", ":(exclude).next/", ":(exclude)coverage/", ":(exclude)*.min.js", ":(exclude)*.min.css", ":(exclude)*.map"];
|
|
2
|
+
/**
|
|
3
|
+
* Split raw unified diff into per-file sections and strip:
|
|
4
|
+
* - Binary file sections ("Binary files a/... and b/... differ")
|
|
5
|
+
* - GIT binary patch sections
|
|
6
|
+
* - Mode-only sections (permission changes with no content hunks)
|
|
7
|
+
*
|
|
8
|
+
* Does NOT modify content lines (+ / - / space) to preserve verbatim
|
|
9
|
+
* accuracy required by suggest_search_replace.
|
|
10
|
+
*/
|
|
11
|
+
export declare function cleanDiff(raw: string): string;
|
|
12
|
+
export declare function isEmptyDiff(diff: string): boolean;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export const NOISY_EXCLUDE_PATHSPECS = [
|
|
2
|
+
':(exclude)package-lock.json',
|
|
3
|
+
':(exclude)yarn.lock',
|
|
4
|
+
':(exclude)pnpm-lock.yaml',
|
|
5
|
+
':(exclude)bun.lockb',
|
|
6
|
+
':(exclude)*.lock',
|
|
7
|
+
':(exclude)dist/',
|
|
8
|
+
':(exclude)build/',
|
|
9
|
+
':(exclude)out/',
|
|
10
|
+
':(exclude).next/',
|
|
11
|
+
':(exclude)coverage/',
|
|
12
|
+
':(exclude)*.min.js',
|
|
13
|
+
':(exclude)*.min.css',
|
|
14
|
+
':(exclude)*.map',
|
|
15
|
+
];
|
|
16
|
+
// Regex patterns to identify noisy diff file sections.
|
|
17
|
+
const BINARY_FILE_LINE = /^Binary files .+ differ$/m;
|
|
18
|
+
const GIT_BINARY_PATCH = /^GIT binary patch/m;
|
|
19
|
+
const HAS_HUNK = /^@@/m;
|
|
20
|
+
const HAS_OLD_MODE = /^old mode /m;
|
|
21
|
+
/**
|
|
22
|
+
* Split raw unified diff into per-file sections and strip:
|
|
23
|
+
* - Binary file sections ("Binary files a/... and b/... differ")
|
|
24
|
+
* - GIT binary patch sections
|
|
25
|
+
* - Mode-only sections (permission changes with no content hunks)
|
|
26
|
+
*
|
|
27
|
+
* Does NOT modify content lines (+ / - / space) to preserve verbatim
|
|
28
|
+
* accuracy required by suggest_search_replace.
|
|
29
|
+
*/
|
|
30
|
+
export function cleanDiff(raw) {
|
|
31
|
+
if (!raw)
|
|
32
|
+
return '';
|
|
33
|
+
// Split on the start of each "diff --git" header, keeping the header.
|
|
34
|
+
const sections = raw.split(/(?=^diff --git )/m);
|
|
35
|
+
const cleaned = sections.filter((section) => {
|
|
36
|
+
if (!section.trim())
|
|
37
|
+
return false;
|
|
38
|
+
if (BINARY_FILE_LINE.test(section))
|
|
39
|
+
return false;
|
|
40
|
+
if (GIT_BINARY_PATCH.test(section))
|
|
41
|
+
return false;
|
|
42
|
+
// Drop mode-only sections that have no actual content hunks.
|
|
43
|
+
if (HAS_OLD_MODE.test(section) && !HAS_HUNK.test(section))
|
|
44
|
+
return false;
|
|
45
|
+
return true;
|
|
46
|
+
});
|
|
47
|
+
return cleaned.join('').trim();
|
|
48
|
+
}
|
|
49
|
+
export function isEmptyDiff(diff) {
|
|
50
|
+
return diff.trim().length === 0;
|
|
51
|
+
}
|
package/dist/lib/diff-parser.js
CHANGED
|
@@ -12,7 +12,6 @@ export function parseDiffFiles(diff) {
|
|
|
12
12
|
return parseDiff(diff);
|
|
13
13
|
}
|
|
14
14
|
function cleanPath(path) {
|
|
15
|
-
// Common git diff prefixes
|
|
16
15
|
if (path.startsWith('a/') || path.startsWith('b/')) {
|
|
17
16
|
return path.slice(2);
|
|
18
17
|
}
|
|
@@ -33,28 +32,40 @@ function sortPaths(paths) {
|
|
|
33
32
|
}
|
|
34
33
|
return Array.from(paths).sort(PATH_SORTER);
|
|
35
34
|
}
|
|
36
|
-
|
|
37
|
-
if (files.length === 0) {
|
|
38
|
-
return {
|
|
39
|
-
stats: EMPTY_STATS,
|
|
40
|
-
summary: NO_FILES_CHANGED,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
35
|
+
function buildDiffComputation(files) {
|
|
43
36
|
let added = 0;
|
|
44
37
|
let deleted = 0;
|
|
38
|
+
const paths = new Set();
|
|
45
39
|
const summaries = new Array(files.length);
|
|
46
40
|
let index = 0;
|
|
47
41
|
for (const file of files) {
|
|
48
42
|
added += file.additions;
|
|
49
43
|
deleted += file.deletions;
|
|
50
44
|
const path = resolveChangedPath(file);
|
|
45
|
+
if (path) {
|
|
46
|
+
paths.add(path);
|
|
47
|
+
}
|
|
51
48
|
summaries[index] =
|
|
52
49
|
`${path ?? UNKNOWN_PATH} (+${file.additions} -${file.deletions})`;
|
|
53
50
|
index += 1;
|
|
54
51
|
}
|
|
52
|
+
return { added, deleted, paths, summaries };
|
|
53
|
+
}
|
|
54
|
+
function buildStats(filesCount, added, deleted) {
|
|
55
|
+
return { files: filesCount, added, deleted };
|
|
56
|
+
}
|
|
57
|
+
export function computeDiffStatsAndSummaryFromFiles(files) {
|
|
58
|
+
if (files.length === 0) {
|
|
59
|
+
return {
|
|
60
|
+
stats: EMPTY_STATS,
|
|
61
|
+
summary: NO_FILES_CHANGED,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const computed = buildDiffComputation(files);
|
|
65
|
+
const stats = buildStats(files.length, computed.added, computed.deleted);
|
|
55
66
|
return {
|
|
56
|
-
stats
|
|
57
|
-
summary: `${summaries.join(', ')} [${files
|
|
67
|
+
stats,
|
|
68
|
+
summary: `${computed.summaries.join(', ')} [${stats.files} files, +${stats.added} -${stats.deleted}]`,
|
|
58
69
|
};
|
|
59
70
|
}
|
|
60
71
|
export function computeDiffStatsAndPathsFromFiles(files) {
|
|
@@ -64,45 +75,29 @@ export function computeDiffStatsAndPathsFromFiles(files) {
|
|
|
64
75
|
paths: EMPTY_PATHS,
|
|
65
76
|
};
|
|
66
77
|
}
|
|
67
|
-
|
|
68
|
-
let deleted = 0;
|
|
69
|
-
const paths = new Set();
|
|
70
|
-
for (const file of files) {
|
|
71
|
-
added += file.additions;
|
|
72
|
-
deleted += file.deletions;
|
|
73
|
-
const path = resolveChangedPath(file);
|
|
74
|
-
if (path) {
|
|
75
|
-
paths.add(path);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
+
const computed = buildDiffComputation(files);
|
|
78
79
|
return {
|
|
79
|
-
stats:
|
|
80
|
-
paths: sortPaths(paths),
|
|
80
|
+
stats: buildStats(files.length, computed.added, computed.deleted),
|
|
81
|
+
paths: sortPaths(computed.paths),
|
|
81
82
|
};
|
|
82
83
|
}
|
|
83
84
|
/** Extract all unique changed file paths (renamed: returns new path). */
|
|
84
85
|
export function extractChangedPathsFromFiles(files) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const path = resolveChangedPath(file);
|
|
88
|
-
if (path) {
|
|
89
|
-
paths.add(path);
|
|
90
|
-
}
|
|
86
|
+
if (files.length === 0) {
|
|
87
|
+
return EMPTY_PATHS;
|
|
91
88
|
}
|
|
92
|
-
return sortPaths(paths);
|
|
89
|
+
return sortPaths(buildDiffComputation(files).paths);
|
|
93
90
|
}
|
|
94
91
|
/** Extract all unique changed file paths (renamed: returns new path). */
|
|
95
92
|
export function extractChangedPaths(diff) {
|
|
96
93
|
return extractChangedPathsFromFiles(parseDiffFiles(diff));
|
|
97
94
|
}
|
|
98
95
|
export function computeDiffStatsFromFiles(files) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
for (const file of files) {
|
|
102
|
-
added += file.additions;
|
|
103
|
-
deleted += file.deletions;
|
|
96
|
+
if (files.length === 0) {
|
|
97
|
+
return EMPTY_STATS;
|
|
104
98
|
}
|
|
105
|
-
|
|
99
|
+
const computed = buildDiffComputation(files);
|
|
100
|
+
return buildStats(files.length, computed.added, computed.deleted);
|
|
106
101
|
}
|
|
107
102
|
/** Count changed files, added lines, and deleted lines. */
|
|
108
103
|
export function computeDiffStats(diff) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { createErrorToolResponse } from './tool-response.js';
|
|
3
|
+
export declare const DIFF_RESOURCE_URI = "diff://current";
|
|
4
|
+
export interface DiffStats {
|
|
5
|
+
files: number;
|
|
6
|
+
added: number;
|
|
7
|
+
deleted: number;
|
|
8
|
+
}
|
|
9
|
+
export interface DiffSlot {
|
|
10
|
+
diff: string;
|
|
11
|
+
stats: DiffStats;
|
|
12
|
+
generatedAt: string;
|
|
13
|
+
mode: string;
|
|
14
|
+
}
|
|
15
|
+
/** Call once during server setup so the store can emit resource-updated notifications. */
|
|
16
|
+
export declare function initDiffStore(server: McpServer): void;
|
|
17
|
+
export declare function storeDiff(data: DiffSlot): void;
|
|
18
|
+
export declare function getDiff(): DiffSlot | undefined;
|
|
19
|
+
export declare function hasDiff(): boolean;
|
|
20
|
+
/** Test-only: directly set or clear the diff slot without emitting resource-updated. */
|
|
21
|
+
export declare function setDiffForTesting(data: DiffSlot | undefined): void;
|
|
22
|
+
export declare function createNoDiffError(): ReturnType<typeof createErrorToolResponse>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createErrorToolResponse } from './tool-response.js';
|
|
2
|
+
export const DIFF_RESOURCE_URI = 'diff://current';
|
|
3
|
+
let slot;
|
|
4
|
+
let sendResourceUpdated;
|
|
5
|
+
/** Call once during server setup so the store can emit resource-updated notifications. */
|
|
6
|
+
export function initDiffStore(server) {
|
|
7
|
+
const inner = server.server;
|
|
8
|
+
sendResourceUpdated = inner.sendResourceUpdated.bind(inner);
|
|
9
|
+
}
|
|
10
|
+
export function storeDiff(data) {
|
|
11
|
+
slot = data;
|
|
12
|
+
void sendResourceUpdated?.({ uri: DIFF_RESOURCE_URI }).catch(() => {
|
|
13
|
+
// Notification is best-effort; never block the tool response.
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export function getDiff() {
|
|
17
|
+
return slot;
|
|
18
|
+
}
|
|
19
|
+
export function hasDiff() {
|
|
20
|
+
return slot !== undefined;
|
|
21
|
+
}
|
|
22
|
+
/** Test-only: directly set or clear the diff slot without emitting resource-updated. */
|
|
23
|
+
export function setDiffForTesting(data) {
|
|
24
|
+
slot = data;
|
|
25
|
+
}
|
|
26
|
+
export function createNoDiffError() {
|
|
27
|
+
return createErrorToolResponse('E_NO_DIFF', 'No diff cached. You must call the generate_diff tool before using any review tool. Run generate_diff with mode="unstaged" or mode="staged" to capture the current branch changes, then retry this tool.', undefined, { retryable: false, kind: 'validation' });
|
|
28
|
+
}
|
package/dist/lib/env-config.d.ts
CHANGED
package/dist/lib/env-config.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
function parsePositiveInteger(value) {
|
|
2
|
+
if (value.length === 0) {
|
|
3
|
+
return undefined;
|
|
4
|
+
}
|
|
2
5
|
const parsed = Number.parseInt(value, 10);
|
|
3
6
|
if (Number.isNaN(parsed) || parsed <= 0) {
|
|
4
7
|
return undefined;
|
|
5
8
|
}
|
|
6
9
|
return parsed;
|
|
7
10
|
}
|
|
8
|
-
|
|
11
|
+
function resolveEnvInt(envVar, defaultValue) {
|
|
12
|
+
const envValue = process.env[envVar] ?? '';
|
|
13
|
+
return parsePositiveInteger(envValue) ?? defaultValue;
|
|
14
|
+
}
|
|
15
|
+
/** Creates a cached integer value from an environment variable, with a default fallback. */
|
|
9
16
|
export function createCachedEnvInt(envVar, defaultValue) {
|
|
10
17
|
let cached;
|
|
11
18
|
return {
|
|
@@ -13,8 +20,7 @@ export function createCachedEnvInt(envVar, defaultValue) {
|
|
|
13
20
|
if (cached !== undefined) {
|
|
14
21
|
return cached;
|
|
15
22
|
}
|
|
16
|
-
|
|
17
|
-
cached = parsePositiveInteger(envValue) ?? defaultValue;
|
|
23
|
+
cached = resolveEnvInt(envVar, defaultValue);
|
|
18
24
|
return cached;
|
|
19
25
|
},
|
|
20
26
|
reset() {
|
package/dist/lib/errors.d.ts
CHANGED
package/dist/lib/errors.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { inspect } from 'node:util';
|
|
2
|
-
|
|
2
|
+
/** Matches transient upstream provider failures that are typically safe to retry. */
|
|
3
3
|
export const RETRYABLE_UPSTREAM_ERROR_PATTERN = /(429|500|502|503|504|rate.?limit|quota|overload|unavailable|gateway|timeout|timed.out|connection|reset|econn|enotfound|temporary|transient|invalid.json)/i;
|
|
4
|
-
function
|
|
5
|
-
return typeof value === '
|
|
4
|
+
function isObjectRecord(value) {
|
|
5
|
+
return typeof value === 'object' && value !== null;
|
|
6
6
|
}
|
|
7
7
|
function hasStringProperty(value, key) {
|
|
8
|
-
if (
|
|
8
|
+
if (!isObjectRecord(value) || !(key in value)) {
|
|
9
9
|
return false;
|
|
10
10
|
}
|
|
11
11
|
const record = value;
|
|
@@ -15,7 +15,7 @@ export function getErrorMessage(error) {
|
|
|
15
15
|
if (hasStringProperty(error, 'message')) {
|
|
16
16
|
return error.message;
|
|
17
17
|
}
|
|
18
|
-
if (
|
|
18
|
+
if (typeof error === 'string') {
|
|
19
19
|
return error;
|
|
20
20
|
}
|
|
21
21
|
return inspect(error, { depth: 3, breakLength: 120 });
|
|
@@ -39,8 +39,9 @@ function stripConstraintValue(value) {
|
|
|
39
39
|
export function stripJsonSchemaConstraints(schema) {
|
|
40
40
|
const result = {};
|
|
41
41
|
for (const [key, value] of Object.entries(schema)) {
|
|
42
|
-
if (CONSTRAINT_KEYS.has(key))
|
|
42
|
+
if (CONSTRAINT_KEYS.has(key)) {
|
|
43
43
|
continue;
|
|
44
|
+
}
|
|
44
45
|
// Relax integer → number so Gemini is not forced into integer-only
|
|
45
46
|
// output; the stricter result schema still validates integrality.
|
|
46
47
|
if (key === 'type' && value === INTEGER_JSON_TYPE) {
|