@j0hanz/code-review-analyst-mcp 1.5.3 → 1.6.1
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 +1 -35
- package/dist/lib/cli.d.ts +1 -0
- package/dist/lib/cli.js +35 -0
- package/dist/lib/context-budget.js +3 -3
- package/dist/lib/diff-budget.js +3 -6
- package/dist/lib/diff-cleaner.js +40 -15
- package/dist/lib/diff-parser.js +37 -50
- package/dist/lib/diff-store.js +34 -12
- package/dist/lib/env-config.js +4 -3
- package/dist/lib/errors.js +7 -5
- package/dist/lib/gemini-schema.js +2 -0
- package/dist/lib/gemini.js +106 -96
- package/dist/lib/model-config.js +2 -4
- package/dist/lib/tool-contracts.d.ts +10 -3
- package/dist/lib/tool-contracts.js +16 -2
- package/dist/lib/tool-factory.d.ts +31 -0
- package/dist/lib/tool-factory.js +219 -128
- package/dist/lib/tool-response.d.ts +3 -1
- package/dist/lib/tool-response.js +8 -8
- package/dist/prompts/index.js +23 -31
- package/dist/resources/index.js +8 -4
- package/dist/resources/instructions.js +30 -25
- package/dist/resources/tool-info.js +4 -11
- package/dist/schemas/inputs.js +8 -8
- package/dist/schemas/outputs.js +42 -66
- package/dist/server.js +13 -26
- package/dist/tools/analyze-complexity.js +9 -16
- package/dist/tools/analyze-pr-impact.js +8 -15
- package/dist/tools/detect-api-breaking.js +10 -17
- package/dist/tools/generate-diff.js +17 -4
- package/dist/tools/generate-review-summary.js +8 -14
- package/dist/tools/generate-test-plan.js +9 -15
- package/dist/tools/index.js +1 -4
- package/dist/tools/inspect-code-quality.js +19 -26
- package/dist/tools/suggest-search-replace.js +9 -15
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,43 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import {
|
|
3
|
+
import { parseCommandLineArgs } from './lib/cli.js';
|
|
4
4
|
import { getErrorMessage } from './lib/errors.js';
|
|
5
5
|
import { createServer } from './server.js';
|
|
6
6
|
const SHUTDOWN_SIGNALS = ['SIGINT', 'SIGTERM'];
|
|
7
|
-
const ARG_OPTION_MODEL = 'model';
|
|
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
|
-
];
|
|
14
|
-
const CLI_OPTIONS = {
|
|
15
|
-
[ARG_OPTION_MODEL]: {
|
|
16
|
-
type: 'string',
|
|
17
|
-
short: 'm',
|
|
18
|
-
},
|
|
19
|
-
[ARG_OPTION_MAX_DIFF_CHARS]: {
|
|
20
|
-
type: 'string',
|
|
21
|
-
},
|
|
22
|
-
};
|
|
23
|
-
function setStringEnv(name, value) {
|
|
24
|
-
if (typeof value === 'string') {
|
|
25
|
-
process.env[name] = value;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function applyCliEnvironmentOverrides(values) {
|
|
29
|
-
for (const mapping of CLI_ENV_MAPPINGS) {
|
|
30
|
-
setStringEnv(mapping.envVar, values[mapping.option]);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
function parseCommandLineArgs() {
|
|
34
|
-
const { values } = parseArgs({
|
|
35
|
-
args: process.argv.slice(PROCESS_ARGS_START_INDEX),
|
|
36
|
-
options: CLI_OPTIONS,
|
|
37
|
-
strict: false,
|
|
38
|
-
});
|
|
39
|
-
applyCliEnvironmentOverrides(values);
|
|
40
|
-
}
|
|
41
7
|
let shuttingDown = false;
|
|
42
8
|
async function shutdown(server) {
|
|
43
9
|
if (shuttingDown) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function parseCommandLineArgs(): void;
|
package/dist/lib/cli.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
const ARG_OPTION_MODEL = 'model';
|
|
3
|
+
const ARG_OPTION_MAX_DIFF_CHARS = 'max-diff-chars';
|
|
4
|
+
const PROCESS_ARGS_START_INDEX = 2;
|
|
5
|
+
const CLI_ENV_MAPPINGS = [
|
|
6
|
+
{ option: ARG_OPTION_MODEL, envVar: 'GEMINI_MODEL' },
|
|
7
|
+
{ option: ARG_OPTION_MAX_DIFF_CHARS, envVar: 'MAX_DIFF_CHARS' },
|
|
8
|
+
];
|
|
9
|
+
const CLI_OPTIONS = {
|
|
10
|
+
[ARG_OPTION_MODEL]: {
|
|
11
|
+
type: 'string',
|
|
12
|
+
short: 'm',
|
|
13
|
+
},
|
|
14
|
+
[ARG_OPTION_MAX_DIFF_CHARS]: {
|
|
15
|
+
type: 'string',
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
function setStringEnv(name, value) {
|
|
19
|
+
if (typeof value === 'string') {
|
|
20
|
+
process.env[name] = value;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function applyCliEnvironmentOverrides(values) {
|
|
24
|
+
for (const mapping of CLI_ENV_MAPPINGS) {
|
|
25
|
+
setStringEnv(mapping.envVar, values[mapping.option]);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function parseCommandLineArgs() {
|
|
29
|
+
const { values } = parseArgs({
|
|
30
|
+
args: process.argv.slice(PROCESS_ARGS_START_INDEX),
|
|
31
|
+
options: CLI_OPTIONS,
|
|
32
|
+
strict: false,
|
|
33
|
+
});
|
|
34
|
+
applyCliEnvironmentOverrides(values);
|
|
35
|
+
}
|
|
@@ -5,11 +5,11 @@ 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
7
|
function computeFilesSize(files) {
|
|
8
|
-
let
|
|
8
|
+
let total = 0;
|
|
9
9
|
for (const file of files) {
|
|
10
|
-
|
|
10
|
+
total += file.content.length;
|
|
11
11
|
}
|
|
12
|
-
return
|
|
12
|
+
return total;
|
|
13
13
|
}
|
|
14
14
|
function createContextBudgetMessage(size, max) {
|
|
15
15
|
return `Combined context size ${size} chars exceeds limit of ${max} chars.`;
|
package/dist/lib/diff-budget.js
CHANGED
|
@@ -12,7 +12,7 @@ export function resetMaxDiffCharsCacheForTesting() {
|
|
|
12
12
|
}
|
|
13
13
|
export function exceedsDiffBudget(diff) {
|
|
14
14
|
const maxChars = getMaxDiffChars();
|
|
15
|
-
return
|
|
15
|
+
return diff.length > maxChars;
|
|
16
16
|
}
|
|
17
17
|
function formatDiffBudgetError(diffLength, maxChars) {
|
|
18
18
|
return `diff exceeds max allowed size (${numberFormatter.format(diffLength)} chars > ${numberFormatter.format(maxChars)} chars)`;
|
|
@@ -21,14 +21,11 @@ export function getDiffBudgetError(diffLength, maxChars = getMaxDiffChars()) {
|
|
|
21
21
|
return formatDiffBudgetError(diffLength, maxChars);
|
|
22
22
|
}
|
|
23
23
|
const BUDGET_ERROR_META = { retryable: false, kind: 'budget' };
|
|
24
|
-
function getDiffLength(diff) {
|
|
25
|
-
return diff.length;
|
|
26
|
-
}
|
|
27
24
|
export function validateDiffBudget(diff) {
|
|
28
|
-
const providedChars =
|
|
25
|
+
const providedChars = diff.length;
|
|
29
26
|
const maxChars = getMaxDiffChars();
|
|
30
27
|
if (providedChars <= maxChars) {
|
|
31
28
|
return undefined;
|
|
32
29
|
}
|
|
33
|
-
return createErrorToolResponse('E_INPUT_TOO_LARGE',
|
|
30
|
+
return createErrorToolResponse('E_INPUT_TOO_LARGE', getDiffBudgetError(providedChars, maxChars), { providedChars, maxChars }, BUDGET_ERROR_META);
|
|
34
31
|
}
|
package/dist/lib/diff-cleaner.js
CHANGED
|
@@ -18,6 +18,29 @@ const BINARY_FILE_LINE = /^Binary files .+ differ$/m;
|
|
|
18
18
|
const GIT_BINARY_PATCH = /^GIT binary patch/m;
|
|
19
19
|
const HAS_HUNK = /^@@/m;
|
|
20
20
|
const HAS_OLD_MODE = /^old mode /m;
|
|
21
|
+
function shouldKeepSection(section) {
|
|
22
|
+
if (!section.trim()) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (BINARY_FILE_LINE.test(section)) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (GIT_BINARY_PATCH.test(section)) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
if (HAS_OLD_MODE.test(section) && !HAS_HUNK.test(section)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
function processSection(raw, start, end, sections) {
|
|
37
|
+
if (end > start) {
|
|
38
|
+
const section = raw.slice(start, end);
|
|
39
|
+
if (shouldKeepSection(section)) {
|
|
40
|
+
sections.push(section);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
21
44
|
/**
|
|
22
45
|
* Split raw unified diff into per-file sections and strip:
|
|
23
46
|
* - Binary file sections ("Binary files a/... and b/... differ")
|
|
@@ -30,21 +53,23 @@ const HAS_OLD_MODE = /^old mode /m;
|
|
|
30
53
|
export function cleanDiff(raw) {
|
|
31
54
|
if (!raw)
|
|
32
55
|
return '';
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
const sections = [];
|
|
57
|
+
let lastIndex = 0;
|
|
58
|
+
let nextIndex = raw.startsWith('diff --git ')
|
|
59
|
+
? 0
|
|
60
|
+
: raw.indexOf('\ndiff --git ');
|
|
61
|
+
if (nextIndex === -1) {
|
|
62
|
+
processSection(raw, 0, raw.length, sections);
|
|
63
|
+
return sections.join('').trim();
|
|
64
|
+
}
|
|
65
|
+
while (nextIndex !== -1) {
|
|
66
|
+
const matchIndex = nextIndex === 0 ? 0 : nextIndex + 1; // +1 to skip \n
|
|
67
|
+
processSection(raw, lastIndex, matchIndex, sections);
|
|
68
|
+
lastIndex = matchIndex;
|
|
69
|
+
nextIndex = raw.indexOf('\ndiff --git ', lastIndex);
|
|
70
|
+
}
|
|
71
|
+
processSection(raw, lastIndex, raw.length, sections);
|
|
72
|
+
return sections.join('').trim();
|
|
48
73
|
}
|
|
49
74
|
export function isEmptyDiff(diff) {
|
|
50
75
|
return diff.trim().length === 0;
|
package/dist/lib/diff-parser.js
CHANGED
|
@@ -6,10 +6,7 @@ const EMPTY_STATS = Object.freeze({ files: 0, added: 0, deleted: 0 });
|
|
|
6
6
|
const PATH_SORTER = (left, right) => left.localeCompare(right);
|
|
7
7
|
/** Parse unified diff string into structured file list. */
|
|
8
8
|
export function parseDiffFiles(diff) {
|
|
9
|
-
|
|
10
|
-
return [];
|
|
11
|
-
}
|
|
12
|
-
return parseDiff(diff);
|
|
9
|
+
return diff ? parseDiff(diff) : [];
|
|
13
10
|
}
|
|
14
11
|
function cleanPath(path) {
|
|
15
12
|
if (path.startsWith('a/') || path.startsWith('b/')) {
|
|
@@ -27,43 +24,26 @@ function resolveChangedPath(file) {
|
|
|
27
24
|
return undefined;
|
|
28
25
|
}
|
|
29
26
|
function sortPaths(paths) {
|
|
30
|
-
if (paths.size === 0) {
|
|
31
|
-
return EMPTY_PATHS;
|
|
32
|
-
}
|
|
33
27
|
return Array.from(paths).sort(PATH_SORTER);
|
|
34
28
|
}
|
|
35
|
-
function
|
|
29
|
+
function calculateStats(files) {
|
|
36
30
|
let added = 0;
|
|
37
31
|
let deleted = 0;
|
|
38
|
-
const paths = options.needPaths ? new Set() : undefined;
|
|
39
|
-
const summaries = options.needSummaries
|
|
40
|
-
? new Array(files.length)
|
|
41
|
-
: undefined;
|
|
42
|
-
let index = 0;
|
|
43
32
|
for (const file of files) {
|
|
44
33
|
added += file.additions;
|
|
45
34
|
deleted += file.deletions;
|
|
46
|
-
if (options.needPaths || options.needSummaries) {
|
|
47
|
-
const path = resolveChangedPath(file);
|
|
48
|
-
if (paths && path) {
|
|
49
|
-
paths.add(path);
|
|
50
|
-
}
|
|
51
|
-
if (summaries) {
|
|
52
|
-
summaries[index] =
|
|
53
|
-
`${path ?? UNKNOWN_PATH} (+${file.additions} -${file.deletions})`;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
index += 1;
|
|
57
35
|
}
|
|
58
|
-
return {
|
|
59
|
-
added,
|
|
60
|
-
deleted,
|
|
61
|
-
paths: paths ?? new Set(),
|
|
62
|
-
summaries: summaries ?? [],
|
|
63
|
-
};
|
|
36
|
+
return { files: files.length, added, deleted };
|
|
64
37
|
}
|
|
65
|
-
function
|
|
66
|
-
|
|
38
|
+
function getUniquePaths(files) {
|
|
39
|
+
const paths = new Set();
|
|
40
|
+
for (const file of files) {
|
|
41
|
+
const path = resolveChangedPath(file);
|
|
42
|
+
if (path) {
|
|
43
|
+
paths.add(path);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return paths;
|
|
67
47
|
}
|
|
68
48
|
export function computeDiffStatsAndSummaryFromFiles(files) {
|
|
69
49
|
if (files.length === 0) {
|
|
@@ -72,14 +52,27 @@ export function computeDiffStatsAndSummaryFromFiles(files) {
|
|
|
72
52
|
summary: NO_FILES_CHANGED,
|
|
73
53
|
};
|
|
74
54
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
55
|
+
let added = 0;
|
|
56
|
+
let deleted = 0;
|
|
57
|
+
const summaries = [];
|
|
58
|
+
const MAX_SUMMARY_FILES = 40;
|
|
59
|
+
let i = 0;
|
|
60
|
+
for (const file of files) {
|
|
61
|
+
added += file.additions;
|
|
62
|
+
deleted += file.deletions;
|
|
63
|
+
if (i < MAX_SUMMARY_FILES) {
|
|
64
|
+
const path = resolveChangedPath(file) ?? UNKNOWN_PATH;
|
|
65
|
+
summaries.push(`${path} (+${file.additions} -${file.deletions})`);
|
|
66
|
+
}
|
|
67
|
+
i++;
|
|
68
|
+
}
|
|
69
|
+
if (files.length > MAX_SUMMARY_FILES) {
|
|
70
|
+
summaries.push(`... and ${files.length - MAX_SUMMARY_FILES} more files`);
|
|
71
|
+
}
|
|
72
|
+
const stats = { files: files.length, added, deleted };
|
|
80
73
|
return {
|
|
81
74
|
stats,
|
|
82
|
-
summary: `${
|
|
75
|
+
summary: `${summaries.join(', ')} [${stats.files} files, +${stats.added} -${stats.deleted}]`,
|
|
83
76
|
};
|
|
84
77
|
}
|
|
85
78
|
export function computeDiffStatsAndPathsFromFiles(files) {
|
|
@@ -89,13 +82,11 @@ export function computeDiffStatsAndPathsFromFiles(files) {
|
|
|
89
82
|
paths: EMPTY_PATHS,
|
|
90
83
|
};
|
|
91
84
|
}
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
needSummaries: false,
|
|
95
|
-
});
|
|
85
|
+
const stats = calculateStats(files);
|
|
86
|
+
const paths = sortPaths(getUniquePaths(files));
|
|
96
87
|
return {
|
|
97
|
-
stats
|
|
98
|
-
paths
|
|
88
|
+
stats,
|
|
89
|
+
paths,
|
|
99
90
|
};
|
|
100
91
|
}
|
|
101
92
|
/** Extract all unique changed file paths (renamed: returns new path). */
|
|
@@ -103,7 +94,7 @@ export function extractChangedPathsFromFiles(files) {
|
|
|
103
94
|
if (files.length === 0) {
|
|
104
95
|
return EMPTY_PATHS;
|
|
105
96
|
}
|
|
106
|
-
return sortPaths(
|
|
97
|
+
return sortPaths(getUniquePaths(files));
|
|
107
98
|
}
|
|
108
99
|
/** Extract all unique changed file paths (renamed: returns new path). */
|
|
109
100
|
export function extractChangedPaths(diff) {
|
|
@@ -113,11 +104,7 @@ export function computeDiffStatsFromFiles(files) {
|
|
|
113
104
|
if (files.length === 0) {
|
|
114
105
|
return EMPTY_STATS;
|
|
115
106
|
}
|
|
116
|
-
|
|
117
|
-
needPaths: false,
|
|
118
|
-
needSummaries: false,
|
|
119
|
-
});
|
|
120
|
-
return buildStats(files.length, computed.added, computed.deleted);
|
|
107
|
+
return calculateStats(files);
|
|
121
108
|
}
|
|
122
109
|
/** Count changed files, added lines, and deleted lines. */
|
|
123
110
|
export function computeDiffStats(diff) {
|
package/dist/lib/diff-store.js
CHANGED
|
@@ -1,32 +1,54 @@
|
|
|
1
|
+
import { createCachedEnvInt } from './env-config.js';
|
|
1
2
|
import { createErrorToolResponse } from './tool-response.js';
|
|
2
3
|
export const DIFF_RESOURCE_URI = 'diff://current';
|
|
4
|
+
const diffCacheTtlMs = createCachedEnvInt('DIFF_CACHE_TTL_MS', 60 * 60 * 1_000 // 1 hour default
|
|
5
|
+
);
|
|
3
6
|
const diffSlots = new Map();
|
|
4
7
|
let sendResourceUpdated;
|
|
8
|
+
function setDiffSlot(key, data) {
|
|
9
|
+
if (data) {
|
|
10
|
+
diffSlots.set(key, data);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
diffSlots.delete(key);
|
|
14
|
+
}
|
|
15
|
+
function notifyDiffUpdated() {
|
|
16
|
+
void sendResourceUpdated?.({ uri: DIFF_RESOURCE_URI }).catch(() => {
|
|
17
|
+
// Ignore errors sending resource-updated, which can happen if the server is not fully initialized yet.
|
|
18
|
+
});
|
|
19
|
+
}
|
|
5
20
|
/** Call once during server setup so the store can emit resource-updated notifications. */
|
|
6
21
|
export function initDiffStore(server) {
|
|
7
22
|
const inner = server.server;
|
|
8
|
-
|
|
23
|
+
if (typeof inner?.sendResourceUpdated === 'function') {
|
|
24
|
+
sendResourceUpdated = inner.sendResourceUpdated.bind(inner);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
console.error('[diff-store] sendResourceUpdated not available — diff resource notifications disabled.');
|
|
28
|
+
}
|
|
9
29
|
}
|
|
10
30
|
export function storeDiff(data, key = process.cwd()) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
// Ignore errors sending resource-updated, which can happen if the server is not fully initialized yet.
|
|
14
|
-
});
|
|
31
|
+
setDiffSlot(key, data);
|
|
32
|
+
notifyDiffUpdated();
|
|
15
33
|
}
|
|
16
34
|
export function getDiff(key = process.cwd()) {
|
|
17
|
-
|
|
35
|
+
const slot = diffSlots.get(key);
|
|
36
|
+
if (!slot) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const age = Date.now() - new Date(slot.generatedAt).getTime();
|
|
40
|
+
if (age > diffCacheTtlMs.get()) {
|
|
41
|
+
diffSlots.delete(key);
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
return slot;
|
|
18
45
|
}
|
|
19
46
|
export function hasDiff(key = process.cwd()) {
|
|
20
47
|
return diffSlots.has(key);
|
|
21
48
|
}
|
|
22
49
|
/** Test-only: directly set or clear the diff slot without emitting resource-updated. */
|
|
23
50
|
export function setDiffForTesting(data, key = process.cwd()) {
|
|
24
|
-
|
|
25
|
-
diffSlots.set(key, data);
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
diffSlots.delete(key);
|
|
29
|
-
}
|
|
51
|
+
setDiffSlot(key, data);
|
|
30
52
|
}
|
|
31
53
|
export function createNoDiffError() {
|
|
32
54
|
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' });
|
package/dist/lib/env-config.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
function parsePositiveInteger(value) {
|
|
2
|
-
|
|
2
|
+
const normalized = value.trim();
|
|
3
|
+
if (normalized.length === 0) {
|
|
3
4
|
return undefined;
|
|
4
5
|
}
|
|
5
|
-
const parsed = Number.parseInt(
|
|
6
|
-
if (Number.
|
|
6
|
+
const parsed = Number.parseInt(normalized, 10);
|
|
7
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
7
8
|
return undefined;
|
|
8
9
|
}
|
|
9
10
|
return parsed;
|
package/dist/lib/errors.js
CHANGED
|
@@ -4,16 +4,18 @@ export const RETRYABLE_UPSTREAM_ERROR_PATTERN = /(429|500|502|503|504|rate.?limi
|
|
|
4
4
|
function isObjectRecord(value) {
|
|
5
5
|
return typeof value === 'object' && value !== null;
|
|
6
6
|
}
|
|
7
|
-
function
|
|
7
|
+
function getStringProperty(value, key) {
|
|
8
8
|
if (!isObjectRecord(value) || !(key in value)) {
|
|
9
|
-
return
|
|
9
|
+
return undefined;
|
|
10
10
|
}
|
|
11
11
|
const record = value;
|
|
12
|
-
|
|
12
|
+
const property = record[key];
|
|
13
|
+
return typeof property === 'string' ? property : undefined;
|
|
13
14
|
}
|
|
14
15
|
export function getErrorMessage(error) {
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
const message = getStringProperty(error, 'message');
|
|
17
|
+
if (message !== undefined) {
|
|
18
|
+
return message;
|
|
17
19
|
}
|
|
18
20
|
if (typeof error === 'string') {
|
|
19
21
|
return error;
|