octocode-cli 1.5.1 → 1.5.3
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/README.md +28 -9
- package/out/chunks/chunk-375ZNU3A.js +317 -0
- package/out/chunks/{chunk-V64AXUWB.js → chunk-AYILFPX4.js} +1 -1
- package/out/chunks/chunk-BRHVQB64.js +10 -0
- package/out/chunks/{command-help-specs-JZXVSLZ5.js → chunk-KWJUBVXJ.js} +1 -1
- package/out/chunks/chunk-OMXWZJBJ.js +100 -0
- package/out/chunks/command-help-specs-RVJK6V6M.js +8 -0
- package/out/chunks/commands-JFDCC4Q7.js +8 -0
- package/out/chunks/{help-P7TCOYAJ.js → help-H3WGUJO7.js} +1 -1
- package/out/chunks/main-help-BUTDD4IR.js +10 -0
- package/out/chunks/{spinner-URV2OX6O.js → spinner-DXD4JQIU.js} +1 -1
- package/out/chunks/tool-command-L6PVHUNS.js +8 -0
- package/out/octocode-cli.js +1 -1
- package/package.json +2 -2
- package/skills/octocode-brainstorming/scripts/tavily-search.mjs +1 -10
- package/skills/octocode-chrome-devtools/scripts/cdp-runner.mjs +0 -2
- package/skills/octocode-chrome-devtools/scripts/cdp-sandbox.mjs +1 -5
- package/skills/octocode-chrome-devtools/scripts/cdp-template.mjs +1 -10
- package/skills/octocode-chrome-devtools/scripts/open-browser.mjs +0 -3
- package/skills/octocode-chrome-devtools/scripts/sourcemap-resolver.mjs +1 -4
- package/skills/octocode-chrome-devtools/scripts/undercover.mjs +0 -2
- package/skills/octocode-engineer/SKILL.md +7 -33
- package/skills/octocode-engineer/references/cli-reference.md +5 -11
- package/skills/octocode-engineer/references/externals.md +0 -4
- package/skills/octocode-engineer/references/quality-indicators.md +10 -50
- package/skills/octocode-engineer/references/tool-workflows.md +10 -13
- package/skills/octocode-engineer/src/analysis/dependency-summary.test.ts +2 -2
- package/skills/octocode-engineer/src/ast/metrics.test.ts +2 -2
- package/skills/octocode-engineer/src/ast/search-main.ts +2 -15
- package/skills/octocode-engineer/src/ast/search.ts +1 -7
- package/skills/octocode-engineer/src/ast/tree-sitter.test.ts +4 -4
- package/skills/octocode-engineer/src/ast/tree-sitter.ts +2 -2
- package/skills/octocode-engineer/src/collectors/chains.ts +3 -10
- package/skills/octocode-engineer/src/collectors/prototype-pollution.ts +3 -3
- package/skills/octocode-engineer/src/collectors/security.ts +3 -3
- package/skills/octocode-engineer/src/common/ensure-deps.ts +3 -20
- package/skills/octocode-engineer/src/detectors/code-quality.ts +0 -1
- package/skills/octocode-engineer/src/index.test.ts +1 -1
- package/skills/octocode-engineer/src/index.ts +1 -11
- package/skills/octocode-engineer/src/pipeline/affected.ts +2 -7
- package/skills/octocode-engineer/src/pipeline/baseline.ts +2 -8
- package/skills/octocode-engineer/src/pipeline/cache.ts +2 -2
- package/skills/octocode-engineer/src/pipeline/config-loader.test.ts +1 -2
- package/skills/octocode-engineer/src/pipeline/config-loader.ts +0 -9
- package/skills/octocode-engineer/src/pipeline/create-options.ts +2 -15
- package/skills/octocode-engineer/src/pipeline/reporters.ts +2 -12
- package/skills/octocode-engineer/src/reporting/summary-md.test.ts +2 -2
- package/skills/octocode-engineer/src/run.ts +1 -7
- package/skills/octocode-engineer/src/types/constants.ts +0 -5
- package/skills/octocode-engineer/src/types/core.ts +0 -5
- package/skills/octocode-news/eslint.config.mjs +1 -6
- package/skills/octocode-news/src/rss-core.ts +2 -4
- package/skills/octocode-research/ecosystem.config.cjs +0 -31
- package/skills/octocode-research/package.json +1 -1
- package/skills/octocode-research/scripts/server.js +272 -230
- package/skills/octocode-research/src/__tests__/integration/circuitBreaker.test.ts +0 -24
- package/skills/octocode-research/src/__tests__/integration/routes.test.ts +0 -10
- package/skills/octocode-research/src/__tests__/integration/serverLifecycle.test.ts +5 -22
- package/skills/octocode-research/src/__tests__/integration/toolsRoutes.test.ts +0 -27
- package/skills/octocode-research/src/__tests__/unit/circuitBreaker.test.ts +0 -29
- package/skills/octocode-research/src/__tests__/unit/errorHandler.test.ts +1 -12
- package/skills/octocode-research/src/__tests__/unit/httpPreprocess.test.ts +1 -9
- package/skills/octocode-research/src/__tests__/unit/logger.test.ts +0 -7
- package/skills/octocode-research/src/__tests__/unit/queryParser.test.ts +1 -7
- package/skills/octocode-research/src/__tests__/unit/responseBuilder.test.ts +0 -11
- package/skills/octocode-research/src/__tests__/unit/retry.test.ts +1 -18
- package/skills/octocode-research/src/__tests__/unit/schemas.test.ts +2 -25
- package/skills/octocode-research/src/index.ts +0 -27
- package/skills/octocode-research/src/mcpCache.ts +3 -18
- package/skills/octocode-research/src/middleware/errorHandler.ts +1 -4
- package/skills/octocode-research/src/middleware/logger.ts +1 -6
- package/skills/octocode-research/src/middleware/queryParser.ts +3 -22
- package/skills/octocode-research/src/routes/github.ts +0 -11
- package/skills/octocode-research/src/routes/local.ts +0 -12
- package/skills/octocode-research/src/routes/lsp.ts +1 -10
- package/skills/octocode-research/src/routes/package.ts +0 -2
- package/skills/octocode-research/src/routes/prompts.ts +2 -32
- package/skills/octocode-research/src/routes/tools.ts +12 -177
- package/skills/octocode-research/src/server-init.ts +0 -32
- package/skills/octocode-research/src/server.ts +8 -37
- package/skills/octocode-research/src/types/errorGuards.ts +0 -15
- package/skills/octocode-research/src/types/guards.ts +6 -27
- package/skills/octocode-research/src/types/mcp.ts +1 -8
- package/skills/octocode-research/src/types/responses.ts +0 -15
- package/skills/octocode-research/src/utils/asyncTimeout.ts +1 -63
- package/skills/octocode-research/src/utils/circuitBreaker.ts +43 -166
- package/skills/octocode-research/src/utils/colors.ts +3 -17
- package/skills/octocode-research/src/utils/errorQueue.ts +6 -28
- package/skills/octocode-research/src/utils/logger.ts +11 -93
- package/skills/octocode-research/src/utils/resilience.ts +22 -77
- package/skills/octocode-research/src/utils/responseBuilder.ts +26 -78
- package/skills/octocode-research/src/utils/responseFactory.ts +0 -7
- package/skills/octocode-research/src/utils/responseParser.ts +11 -59
- package/skills/octocode-research/src/utils/retry.ts +12 -75
- package/skills/octocode-research/src/utils/routeFactory.ts +10 -61
- package/skills/octocode-research/src/utils/url.ts +0 -5
- package/skills/octocode-research/src/validation/httpPreprocess.ts +15 -61
- package/skills/octocode-research/src/validation/schemas.ts +14 -101
- package/skills/octocode-research/src/validation/toolCallSchema.ts +5 -65
- package/skills/octocode-research/tsdown.config.ts +2 -7
- package/skills/octocode-search-skill/SKILL.md +9 -2
- package/skills/octocode-search-skill/references/discovery-surfaces.md +2 -1
- package/skills/octocode-slides/scripts/navbridge.js +1 -14
- package/skills/octocode-slides/scripts/presenter.js +2 -61
- package/skills/octocode-stats/scripts/build_dashboard.mjs +1 -19
- package/out/chunks/chunk-7476PETK.js +0 -309
- package/out/chunks/chunk-OQBJTZWK.js +0 -60
- package/out/chunks/chunk-UCZCF3BQ.js +0 -9
- package/out/chunks/commands-XBFPLHSQ.js +0 -8
- package/out/chunks/main-help-ULF5PAQY.js +0 -10
- package/out/chunks/tool-command-M6VA7P2F.js +0 -8
- package/skills/octocode-research/AGENTS.md +0 -349
|
@@ -1,29 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Research-specific response builders for octocode-research skill.
|
|
3
|
-
*
|
|
4
|
-
* Provides high-level response helpers that wrap the MCP role-based response API
|
|
5
|
-
* with research-specific patterns and formatting.
|
|
6
|
-
*
|
|
7
|
-
* NOTE: The MCP tools already provide rich reasoning context via:
|
|
8
|
-
* - mainResearchGoal, researchGoal, reasoning in each result
|
|
9
|
-
* - hasResultsStatusHints, emptyStatusHints, errorStatusHints for guidance
|
|
10
|
-
* - Dynamic per-tool hints based on context
|
|
11
|
-
*
|
|
12
|
-
* This module focuses on formatting and presentation, NOT duplicating reasoning.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
1
|
import {
|
|
16
2
|
createRoleBasedResult,
|
|
17
3
|
QuickResult,
|
|
18
4
|
StatusEmoji,
|
|
19
5
|
} from 'octocode-mcp/public';
|
|
20
6
|
|
|
21
|
-
// CallToolResult type from MCP SDK (re-exported via octocode-mcp)
|
|
22
7
|
type CallToolResult = ReturnType<typeof createRoleBasedResult>;
|
|
23
8
|
|
|
24
|
-
|
|
25
|
-
* Pagination info for paginated responses
|
|
26
|
-
*/
|
|
9
|
+
|
|
27
10
|
interface PaginationInfo {
|
|
28
11
|
page: number;
|
|
29
12
|
total: number;
|
|
@@ -32,9 +15,7 @@ interface PaginationInfo {
|
|
|
32
15
|
totalItems?: number;
|
|
33
16
|
}
|
|
34
17
|
|
|
35
|
-
|
|
36
|
-
* Detailed match location info (preserved from MCP response)
|
|
37
|
-
*/
|
|
18
|
+
|
|
38
19
|
interface MatchLocation {
|
|
39
20
|
line: number;
|
|
40
21
|
column?: number;
|
|
@@ -43,49 +24,40 @@ interface MatchLocation {
|
|
|
43
24
|
charOffset?: number;
|
|
44
25
|
}
|
|
45
26
|
|
|
46
|
-
|
|
47
|
-
* File match info for search results
|
|
48
|
-
*/
|
|
27
|
+
|
|
49
28
|
interface FileMatch {
|
|
50
29
|
path: string;
|
|
51
30
|
matches?: number;
|
|
52
31
|
line?: number;
|
|
53
32
|
preview?: string;
|
|
54
33
|
repo?: string;
|
|
55
|
-
|
|
34
|
+
|
|
56
35
|
allMatches?: MatchLocation[];
|
|
57
36
|
}
|
|
58
37
|
|
|
59
|
-
|
|
60
|
-
* Research context from MCP response
|
|
61
|
-
*/
|
|
38
|
+
|
|
62
39
|
interface ResearchContext {
|
|
63
40
|
mainResearchGoal?: string;
|
|
64
41
|
researchGoal?: string;
|
|
65
42
|
reasoning?: string;
|
|
66
43
|
}
|
|
67
44
|
|
|
68
|
-
|
|
69
|
-
* Research-specific response builders
|
|
70
|
-
*/
|
|
45
|
+
|
|
71
46
|
export const ResearchResponse = {
|
|
72
|
-
|
|
73
|
-
* Search results with navigation hints
|
|
74
|
-
*/
|
|
47
|
+
|
|
75
48
|
searchResults(results: {
|
|
76
49
|
files: FileMatch[];
|
|
77
50
|
totalMatches: number;
|
|
78
51
|
pagination?: PaginationInfo;
|
|
79
52
|
searchPattern?: string;
|
|
80
53
|
isLocal?: boolean;
|
|
81
|
-
|
|
54
|
+
|
|
82
55
|
mcpHints?: string[];
|
|
83
|
-
|
|
56
|
+
|
|
84
57
|
research?: ResearchContext;
|
|
85
58
|
}): CallToolResult {
|
|
86
59
|
const { files, totalMatches, pagination, searchPattern, mcpHints = [] } = results;
|
|
87
60
|
|
|
88
|
-
// Build summary
|
|
89
61
|
const patternInfo = searchPattern ? ` for "${searchPattern}"` : '';
|
|
90
62
|
const summary =
|
|
91
63
|
files.length > 0
|
|
@@ -100,7 +72,6 @@ export const ResearchResponse = {
|
|
|
100
72
|
(files.length > 10 ? `\n... and ${files.length - 10} more files` : '')
|
|
101
73
|
: `No matches found${patternInfo}`;
|
|
102
74
|
|
|
103
|
-
// Build hints - start with MCP hints, add pagination info
|
|
104
75
|
const hints: string[] = [...mcpHints];
|
|
105
76
|
if (files.length > 10) {
|
|
106
77
|
hints.push(`Showing 10 of ${files.length} files`);
|
|
@@ -109,9 +80,7 @@ export const ResearchResponse = {
|
|
|
109
80
|
hints.push(`Next page: page=${pagination.page + 1}`);
|
|
110
81
|
}
|
|
111
82
|
|
|
112
|
-
// Return appropriate response type
|
|
113
83
|
if (files.length === 0) {
|
|
114
|
-
// For empty results, MCP already provides relevant hints
|
|
115
84
|
const emptyHints = mcpHints.length > 0 ? mcpHints : [
|
|
116
85
|
'Try broader search terms',
|
|
117
86
|
'Check spelling and case sensitivity',
|
|
@@ -127,9 +96,7 @@ export const ResearchResponse = {
|
|
|
127
96
|
return QuickResult.success(summary, results, hints);
|
|
128
97
|
},
|
|
129
98
|
|
|
130
|
-
|
|
131
|
-
* File content with context
|
|
132
|
-
*/
|
|
99
|
+
|
|
133
100
|
fileContent(result: {
|
|
134
101
|
path: string;
|
|
135
102
|
content: string;
|
|
@@ -137,9 +104,9 @@ export const ResearchResponse = {
|
|
|
137
104
|
language?: string;
|
|
138
105
|
totalLines?: number;
|
|
139
106
|
isPartial?: boolean;
|
|
140
|
-
|
|
107
|
+
|
|
141
108
|
mcpHints?: string[];
|
|
142
|
-
|
|
109
|
+
|
|
143
110
|
research?: ResearchContext;
|
|
144
111
|
}): CallToolResult {
|
|
145
112
|
const { path, content, lines, language, totalLines, isPartial, mcpHints = [] } = result;
|
|
@@ -147,10 +114,8 @@ export const ResearchResponse = {
|
|
|
147
114
|
const lineInfo = lines ? ` (lines ${lines.start}-${lines.end})` : '';
|
|
148
115
|
const lang = language || detectLanguage(path);
|
|
149
116
|
|
|
150
|
-
// Format content with code fence
|
|
151
117
|
const formattedContent = `📄 ${path}${lineInfo}\n\n\`\`\`${lang}\n${content}\n\`\`\``;
|
|
152
118
|
|
|
153
|
-
// Build hints - start with MCP hints, add contextual info
|
|
154
119
|
const hints: string[] = [...mcpHints];
|
|
155
120
|
hints.push('Content retrieved successfully');
|
|
156
121
|
if (lines) {
|
|
@@ -169,16 +134,14 @@ export const ResearchResponse = {
|
|
|
169
134
|
});
|
|
170
135
|
},
|
|
171
136
|
|
|
172
|
-
|
|
173
|
-
* LSP definition/reference/call hierarchy results
|
|
174
|
-
*/
|
|
137
|
+
|
|
175
138
|
lspResult(result: {
|
|
176
139
|
symbol: string;
|
|
177
140
|
locations: Array<{ uri: string; line: number; preview?: string }>;
|
|
178
141
|
type: 'definition' | 'references' | 'calls' | 'incoming' | 'outgoing';
|
|
179
|
-
|
|
142
|
+
|
|
180
143
|
mcpHints?: string[];
|
|
181
|
-
|
|
144
|
+
|
|
182
145
|
research?: ResearchContext;
|
|
183
146
|
}): CallToolResult {
|
|
184
147
|
const { symbol, locations, type, mcpHints = [] } = result;
|
|
@@ -212,7 +175,6 @@ export const ResearchResponse = {
|
|
|
212
175
|
.join('\n')
|
|
213
176
|
: `No ${type} found for "${symbol}"`;
|
|
214
177
|
|
|
215
|
-
// Build hints - start with MCP hints, add contextual info
|
|
216
178
|
const hints: string[] = [...mcpHints];
|
|
217
179
|
if (locations.length > 0) {
|
|
218
180
|
hints.push('Use returned line numbers for further navigation');
|
|
@@ -235,9 +197,7 @@ export const ResearchResponse = {
|
|
|
235
197
|
});
|
|
236
198
|
},
|
|
237
199
|
|
|
238
|
-
|
|
239
|
-
* Repository structure view
|
|
240
|
-
*/
|
|
200
|
+
|
|
241
201
|
repoStructure(result: {
|
|
242
202
|
path: string;
|
|
243
203
|
structure: { files: string[]; folders: string[] };
|
|
@@ -246,9 +206,9 @@ export const ResearchResponse = {
|
|
|
246
206
|
totalFolders?: number;
|
|
247
207
|
owner?: string;
|
|
248
208
|
repo?: string;
|
|
249
|
-
|
|
209
|
+
|
|
250
210
|
mcpHints?: string[];
|
|
251
|
-
|
|
211
|
+
|
|
252
212
|
research?: ResearchContext;
|
|
253
213
|
}): CallToolResult {
|
|
254
214
|
const { path, structure, depth, totalFiles, totalFolders, owner, repo, mcpHints = [] } =
|
|
@@ -269,7 +229,6 @@ export const ResearchResponse = {
|
|
|
269
229
|
? `Files:\n${fileList.map(f => ` 📄 ${f}`).join('\n')}`
|
|
270
230
|
: 'No files in this directory');
|
|
271
231
|
|
|
272
|
-
// Build hints - start with MCP hints, add contextual info
|
|
273
232
|
const hints: string[] = [...mcpHints];
|
|
274
233
|
if (depth === 1) {
|
|
275
234
|
hints.push('Use depth=2 to see nested contents');
|
|
@@ -301,9 +260,7 @@ export const ResearchResponse = {
|
|
|
301
260
|
});
|
|
302
261
|
},
|
|
303
262
|
|
|
304
|
-
|
|
305
|
-
* Package search results
|
|
306
|
-
*/
|
|
263
|
+
|
|
307
264
|
packageSearch(result: {
|
|
308
265
|
packages: Array<{
|
|
309
266
|
name: string;
|
|
@@ -313,9 +270,9 @@ export const ResearchResponse = {
|
|
|
313
270
|
}>;
|
|
314
271
|
registry: 'npm' | 'pypi';
|
|
315
272
|
query?: string;
|
|
316
|
-
|
|
273
|
+
|
|
317
274
|
mcpHints?: string[];
|
|
318
|
-
|
|
275
|
+
|
|
319
276
|
research?: ResearchContext;
|
|
320
277
|
}): CallToolResult {
|
|
321
278
|
const { packages, registry, query, mcpHints = [] } = result;
|
|
@@ -333,7 +290,6 @@ export const ResearchResponse = {
|
|
|
333
290
|
.join('\n')
|
|
334
291
|
: `No packages found${queryInfo} on ${registry.toUpperCase()}`;
|
|
335
292
|
|
|
336
|
-
// Build hints - start with MCP hints, add contextual info
|
|
337
293
|
const hints: string[] = [...mcpHints];
|
|
338
294
|
if (packages.length > 0) {
|
|
339
295
|
hints.push('Use repository URL with githubViewRepoStructure to explore source');
|
|
@@ -350,9 +306,7 @@ export const ResearchResponse = {
|
|
|
350
306
|
return QuickResult.success(summary, result, hints);
|
|
351
307
|
},
|
|
352
308
|
|
|
353
|
-
|
|
354
|
-
* Pull request search results
|
|
355
|
-
*/
|
|
309
|
+
|
|
356
310
|
pullRequests(result: {
|
|
357
311
|
prs: Array<{
|
|
358
312
|
number: number;
|
|
@@ -363,9 +317,9 @@ export const ResearchResponse = {
|
|
|
363
317
|
}>;
|
|
364
318
|
repo?: string;
|
|
365
319
|
pagination?: PaginationInfo;
|
|
366
|
-
|
|
320
|
+
|
|
367
321
|
mcpHints?: string[];
|
|
368
|
-
|
|
322
|
+
|
|
369
323
|
research?: ResearchContext;
|
|
370
324
|
}): CallToolResult {
|
|
371
325
|
const { prs, repo, pagination, mcpHints = [] } = result;
|
|
@@ -383,7 +337,6 @@ export const ResearchResponse = {
|
|
|
383
337
|
.join('\n')
|
|
384
338
|
: `No pull requests found${repoInfo}`;
|
|
385
339
|
|
|
386
|
-
// Build hints - start with MCP hints, add contextual info
|
|
387
340
|
const hints: string[] = [...mcpHints];
|
|
388
341
|
if (prs.length > 0) {
|
|
389
342
|
hints.push('Use prNumber with type="fullContent" to see full diff');
|
|
@@ -404,9 +357,7 @@ export const ResearchResponse = {
|
|
|
404
357
|
return QuickResult.success(summary, result, hints);
|
|
405
358
|
},
|
|
406
359
|
|
|
407
|
-
|
|
408
|
-
* Generic bulk operation result
|
|
409
|
-
*/
|
|
360
|
+
|
|
410
361
|
bulkResult(result: {
|
|
411
362
|
results: Array<{ status: string; data?: unknown; error?: string }>;
|
|
412
363
|
operation: string;
|
|
@@ -452,9 +403,7 @@ export const ResearchResponse = {
|
|
|
452
403
|
},
|
|
453
404
|
};
|
|
454
405
|
|
|
455
|
-
|
|
456
|
-
* Detect language from file extension
|
|
457
|
-
*/
|
|
406
|
+
|
|
458
407
|
function detectLanguage(path: string): string {
|
|
459
408
|
const ext = path.split('.').pop()?.toLowerCase() || '';
|
|
460
409
|
const langMap: Record<string, string> = {
|
|
@@ -491,5 +440,4 @@ function detectLanguage(path: string): string {
|
|
|
491
440
|
return langMap[ext] || '';
|
|
492
441
|
}
|
|
493
442
|
|
|
494
|
-
// Re-export utilities for convenience
|
|
495
443
|
export { QuickResult, detectLanguage as detectLanguageFromPath };
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Response utilities for route handlers.
|
|
3
|
-
* Provides type-safe extractors and helpers for processing MCP responses.
|
|
4
|
-
*
|
|
5
|
-
* @module utils/responseFactory
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
import { isObject, hasProperty, isArray, hasStringProperty, hasNumberProperty } from '../types/guards.js';
|
|
9
2
|
|
|
10
3
|
export function safeString(obj: unknown, key: string, fallback = ''): string {
|
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parse MCP tool responses to extract structured data
|
|
3
|
-
*
|
|
4
|
-
* MCP tools return responses in this format:
|
|
5
|
-
* {
|
|
6
|
-
* content: [{ type: 'text', text: yamlString }],
|
|
7
|
-
* structuredContent?: object, // Not always present
|
|
8
|
-
* isError: boolean
|
|
9
|
-
* }
|
|
10
|
-
*
|
|
11
|
-
* The YAML contains:
|
|
12
|
-
* - instructions: string
|
|
13
|
-
* - results: [{ id, status, data, mainResearchGoal, researchGoal, reasoning }]
|
|
14
|
-
* - hasResultsStatusHints / emptyStatusHints / errorStatusHints: string[]
|
|
15
|
-
*
|
|
16
|
-
* This utility extracts data AND preserves the valuable MCP hints.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
1
|
import yaml from 'js-yaml';
|
|
20
2
|
|
|
21
3
|
interface McpToolResponse {
|
|
@@ -37,34 +19,32 @@ interface BulkResultItem {
|
|
|
37
19
|
research: ResearchContext;
|
|
38
20
|
}
|
|
39
21
|
|
|
40
|
-
|
|
41
|
-
* Parsed response with data, hints, and research context
|
|
42
|
-
*/
|
|
22
|
+
|
|
43
23
|
export interface ParsedResponse {
|
|
44
24
|
data: Record<string, unknown>;
|
|
45
25
|
isError: boolean;
|
|
46
|
-
|
|
26
|
+
|
|
47
27
|
hints: string[];
|
|
48
|
-
|
|
28
|
+
|
|
49
29
|
research: ResearchContext;
|
|
50
|
-
|
|
30
|
+
|
|
51
31
|
status: 'hasResults' | 'empty' | 'error' | 'unknown';
|
|
52
32
|
}
|
|
53
33
|
|
|
54
34
|
interface ParsedBulkResponse {
|
|
55
|
-
|
|
35
|
+
|
|
56
36
|
results: BulkResultItem[];
|
|
57
|
-
|
|
37
|
+
|
|
58
38
|
hints: {
|
|
59
39
|
hasResults: string[];
|
|
60
40
|
empty: string[];
|
|
61
41
|
error: string[];
|
|
62
42
|
};
|
|
63
|
-
|
|
43
|
+
|
|
64
44
|
instructions: string;
|
|
65
|
-
|
|
45
|
+
|
|
66
46
|
isError: boolean;
|
|
67
|
-
|
|
47
|
+
|
|
68
48
|
counts: {
|
|
69
49
|
total: number;
|
|
70
50
|
hasResults: number;
|
|
@@ -73,17 +53,7 @@ interface ParsedBulkResponse {
|
|
|
73
53
|
};
|
|
74
54
|
}
|
|
75
55
|
|
|
76
|
-
|
|
77
|
-
* Extract structured data from an MCP tool response
|
|
78
|
-
*
|
|
79
|
-
* Priority:
|
|
80
|
-
* 1. Use structuredContent if available (direct object access)
|
|
81
|
-
* 2. Parse YAML from content[0].text and extract results[0].data
|
|
82
|
-
*
|
|
83
|
-
* Also extracts:
|
|
84
|
-
* - MCP hints (hasResultsStatusHints, emptyStatusHints, errorStatusHints)
|
|
85
|
-
* - Research context (mainResearchGoal, researchGoal, reasoning)
|
|
86
|
-
*/
|
|
56
|
+
|
|
87
57
|
export function parseToolResponse(response: McpToolResponse): ParsedResponse {
|
|
88
58
|
const emptyResult: ParsedResponse = {
|
|
89
59
|
data: {},
|
|
@@ -93,23 +63,20 @@ export function parseToolResponse(response: McpToolResponse): ParsedResponse {
|
|
|
93
63
|
status: 'unknown',
|
|
94
64
|
};
|
|
95
65
|
|
|
96
|
-
// Option 1: structuredContent is available (preferred for data, but no hints)
|
|
97
66
|
if (response.structuredContent && typeof response.structuredContent === 'object') {
|
|
98
67
|
return {
|
|
99
68
|
data: response.structuredContent,
|
|
100
69
|
isError: Boolean(response.isError),
|
|
101
|
-
hints: [],
|
|
70
|
+
hints: [],
|
|
102
71
|
research: {},
|
|
103
72
|
status: 'unknown',
|
|
104
73
|
};
|
|
105
74
|
}
|
|
106
75
|
|
|
107
|
-
// Option 2: Parse YAML from content text (includes hints!)
|
|
108
76
|
if (response.content && response.content[0]?.text) {
|
|
109
77
|
try {
|
|
110
78
|
const parsed = yaml.load(response.content[0].text) as Record<string, unknown>;
|
|
111
79
|
|
|
112
|
-
// Extract hints based on status
|
|
113
80
|
let hints: string[] = [];
|
|
114
81
|
if (Array.isArray(parsed.hasResultsStatusHints)) {
|
|
115
82
|
hints = parsed.hasResultsStatusHints as string[];
|
|
@@ -119,12 +86,10 @@ export function parseToolResponse(response: McpToolResponse): ParsedResponse {
|
|
|
119
86
|
hints = parsed.errorStatusHints as string[];
|
|
120
87
|
}
|
|
121
88
|
|
|
122
|
-
// Extract data from results[0].data (bulk response format)
|
|
123
89
|
if (parsed && Array.isArray(parsed.results) && parsed.results.length > 0) {
|
|
124
90
|
const firstResult = parsed.results[0] as Record<string, unknown>;
|
|
125
91
|
const resultStatus = String(firstResult.status || 'unknown');
|
|
126
92
|
|
|
127
|
-
// Extract research context
|
|
128
93
|
const research: ResearchContext = {
|
|
129
94
|
mainResearchGoal: typeof firstResult.mainResearchGoal === 'string'
|
|
130
95
|
? firstResult.mainResearchGoal : undefined,
|
|
@@ -145,7 +110,6 @@ export function parseToolResponse(response: McpToolResponse): ParsedResponse {
|
|
|
145
110
|
}
|
|
146
111
|
}
|
|
147
112
|
|
|
148
|
-
// Fallback: return parsed object directly
|
|
149
113
|
return {
|
|
150
114
|
data: parsed || {},
|
|
151
115
|
isError: Boolean(response.isError),
|
|
@@ -154,22 +118,13 @@ export function parseToolResponse(response: McpToolResponse): ParsedResponse {
|
|
|
154
118
|
status: 'unknown',
|
|
155
119
|
};
|
|
156
120
|
} catch {
|
|
157
|
-
// YAML parsing failed, return empty
|
|
158
121
|
return emptyResult;
|
|
159
122
|
}
|
|
160
123
|
}
|
|
161
124
|
|
|
162
|
-
// No data found
|
|
163
125
|
return emptyResult;
|
|
164
126
|
}
|
|
165
127
|
|
|
166
|
-
/**
|
|
167
|
-
* Parse ALL results from a bulk MCP tool response.
|
|
168
|
-
* Use this when handling multiple queries to get all results.
|
|
169
|
-
*
|
|
170
|
-
* @param response - Raw MCP tool response
|
|
171
|
-
* @returns ParsedBulkResponse with all results and categorized hints
|
|
172
|
-
*/
|
|
173
128
|
export function parseToolResponseBulk(response: McpToolResponse): ParsedBulkResponse {
|
|
174
129
|
const emptyResult: ParsedBulkResponse = {
|
|
175
130
|
results: [],
|
|
@@ -179,7 +134,6 @@ export function parseToolResponseBulk(response: McpToolResponse): ParsedBulkResp
|
|
|
179
134
|
counts: { total: 0, hasResults: 0, empty: 0, error: 0 },
|
|
180
135
|
};
|
|
181
136
|
|
|
182
|
-
// Parse YAML from content text
|
|
183
137
|
if (!response.content || !response.content[0]?.text) {
|
|
184
138
|
return emptyResult;
|
|
185
139
|
}
|
|
@@ -191,7 +145,6 @@ export function parseToolResponseBulk(response: McpToolResponse): ParsedBulkResp
|
|
|
191
145
|
return emptyResult;
|
|
192
146
|
}
|
|
193
147
|
|
|
194
|
-
// Extract all results
|
|
195
148
|
const results: BulkResultItem[] = [];
|
|
196
149
|
let hasResultsCount = 0;
|
|
197
150
|
let emptyCount = 0;
|
|
@@ -219,7 +172,6 @@ export function parseToolResponseBulk(response: McpToolResponse): ParsedBulkResp
|
|
|
219
172
|
});
|
|
220
173
|
}
|
|
221
174
|
|
|
222
|
-
// Extract categorized hints
|
|
223
175
|
const hints = {
|
|
224
176
|
hasResults: Array.isArray(parsed.hasResultsStatusHints)
|
|
225
177
|
? (parsed.hasResultsStatusHints as string[])
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Retry utilities with exponential backoff for resilient tool calls.
|
|
3
|
-
*
|
|
4
|
-
* @module utils/retry
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import {
|
|
8
2
|
getErrorStatus,
|
|
9
3
|
hasStatusIn,
|
|
@@ -19,13 +13,9 @@ export interface RetryConfig {
|
|
|
19
13
|
retryOn: (error: unknown) => boolean;
|
|
20
14
|
}
|
|
21
15
|
|
|
22
|
-
|
|
23
|
-
* Pre-configured retry strategies for different tool categories
|
|
24
|
-
*/
|
|
16
|
+
|
|
25
17
|
export const RETRY_CONFIGS = {
|
|
26
|
-
|
|
27
|
-
* LSP tools - may need warm-up time
|
|
28
|
-
*/
|
|
18
|
+
|
|
29
19
|
lsp: {
|
|
30
20
|
maxAttempts: 3,
|
|
31
21
|
initialDelayMs: 500,
|
|
@@ -35,9 +25,7 @@ export const RETRY_CONFIGS = {
|
|
|
35
25
|
isLspNotReady(err) || isTimeout(err) || isConnectionRefused(err),
|
|
36
26
|
},
|
|
37
27
|
|
|
38
|
-
|
|
39
|
-
* GitHub API - rate limits and server errors
|
|
40
|
-
*/
|
|
28
|
+
|
|
41
29
|
github: {
|
|
42
30
|
maxAttempts: 3,
|
|
43
31
|
initialDelayMs: 1000,
|
|
@@ -47,9 +35,7 @@ export const RETRY_CONFIGS = {
|
|
|
47
35
|
isRateLimited(err) || isServerError(err) || isTimeout(err),
|
|
48
36
|
},
|
|
49
37
|
|
|
50
|
-
|
|
51
|
-
* Package APIs (npm/PyPI) - similar to GitHub
|
|
52
|
-
*/
|
|
38
|
+
|
|
53
39
|
package: {
|
|
54
40
|
maxAttempts: 3,
|
|
55
41
|
initialDelayMs: 1000,
|
|
@@ -59,9 +45,7 @@ export const RETRY_CONFIGS = {
|
|
|
59
45
|
isRateLimited(err) || isServerError(err) || isTimeout(err),
|
|
60
46
|
},
|
|
61
47
|
|
|
62
|
-
|
|
63
|
-
* Local file operations - quick retries
|
|
64
|
-
*/
|
|
48
|
+
|
|
65
49
|
local: {
|
|
66
50
|
maxAttempts: 2,
|
|
67
51
|
initialDelayMs: 100,
|
|
@@ -71,32 +55,12 @@ export const RETRY_CONFIGS = {
|
|
|
71
55
|
},
|
|
72
56
|
} as const satisfies Record<string, RetryConfig>;
|
|
73
57
|
|
|
74
|
-
|
|
75
|
-
* Context for retry logging
|
|
76
|
-
*/
|
|
58
|
+
|
|
77
59
|
interface RetryContext {
|
|
78
60
|
tool: string;
|
|
79
61
|
params?: unknown;
|
|
80
62
|
}
|
|
81
63
|
|
|
82
|
-
/**
|
|
83
|
-
* Execute an operation with retry logic and exponential backoff.
|
|
84
|
-
*
|
|
85
|
-
* @param operation - Async function to execute
|
|
86
|
-
* @param config - Retry configuration
|
|
87
|
-
* @param context - Optional context for logging
|
|
88
|
-
* @returns Result of the operation
|
|
89
|
-
* @throws Last error if all retries exhausted
|
|
90
|
-
*
|
|
91
|
-
* @example
|
|
92
|
-
* ```typescript
|
|
93
|
-
* const result = await withRetry(
|
|
94
|
-
* () => lspGotoDefinition({ queries }),
|
|
95
|
-
* RETRY_CONFIGS.lsp,
|
|
96
|
-
* { tool: 'lspGotoDefinition' }
|
|
97
|
-
* );
|
|
98
|
-
* ```
|
|
99
|
-
*/
|
|
100
64
|
export async function withRetry<T>(
|
|
101
65
|
operation: () => Promise<T>,
|
|
102
66
|
config: RetryConfig,
|
|
@@ -111,12 +75,10 @@ export async function withRetry<T>(
|
|
|
111
75
|
} catch (error) {
|
|
112
76
|
lastError = error;
|
|
113
77
|
|
|
114
|
-
// Don't retry if error type doesn't match or last attempt
|
|
115
78
|
if (!config.retryOn(error) || attempt === config.maxAttempts) {
|
|
116
79
|
throw error;
|
|
117
80
|
}
|
|
118
81
|
|
|
119
|
-
// Log retry attempt
|
|
120
82
|
const toolName = context?.tool || 'operation';
|
|
121
83
|
console.log(
|
|
122
84
|
`⟳ Retry ${attempt}/${config.maxAttempts} for ${toolName} in ${delay}ms`
|
|
@@ -130,11 +92,7 @@ export async function withRetry<T>(
|
|
|
130
92
|
throw lastError;
|
|
131
93
|
}
|
|
132
94
|
|
|
133
|
-
// =============================================================================
|
|
134
|
-
// Error Type Detection
|
|
135
|
-
// =============================================================================
|
|
136
95
|
|
|
137
|
-
// Error codes for reliable detection (check these first)
|
|
138
96
|
const RATE_LIMIT_CODES = [403, 429] as const;
|
|
139
97
|
const RATE_LIMIT_PATTERNS = [/rate\s*limit/i, /too\s*many\s*requests/i] as const;
|
|
140
98
|
|
|
@@ -148,70 +106,49 @@ const FILE_BUSY_CODES = ['EBUSY', 'EAGAIN', 'ENOTEMPTY'] as const;
|
|
|
148
106
|
|
|
149
107
|
const CONNECTION_REFUSED_CODES = ['ECONNREFUSED', 'ENOTFOUND', 'EHOSTUNREACH'] as const;
|
|
150
108
|
|
|
151
|
-
|
|
152
|
-
* Check if error indicates GitHub rate limiting
|
|
153
|
-
*/
|
|
109
|
+
|
|
154
110
|
function isRateLimited(err: unknown): boolean {
|
|
155
|
-
// Check status codes first (more reliable)
|
|
156
111
|
if (hasStatusIn(err, RATE_LIMIT_CODES)) {
|
|
157
112
|
return true;
|
|
158
113
|
}
|
|
159
114
|
|
|
160
|
-
// Fall back to message patterns
|
|
161
115
|
return messageMatches(err, RATE_LIMIT_PATTERNS);
|
|
162
116
|
}
|
|
163
117
|
|
|
164
|
-
|
|
165
|
-
* Check if error indicates LSP server not ready
|
|
166
|
-
*/
|
|
118
|
+
|
|
167
119
|
function isLspNotReady(err: unknown): boolean {
|
|
168
|
-
// Check error codes first (more reliable)
|
|
169
120
|
if (hasCodeIn(err, LSP_ERROR_CODES)) {
|
|
170
121
|
return true;
|
|
171
122
|
}
|
|
172
123
|
|
|
173
|
-
// Fall back to message patterns
|
|
174
124
|
return messageMatches(err, LSP_ERROR_PATTERNS);
|
|
175
125
|
}
|
|
176
126
|
|
|
177
|
-
|
|
178
|
-
* Check if error is a timeout
|
|
179
|
-
*/
|
|
127
|
+
|
|
180
128
|
function isTimeout(err: unknown): boolean {
|
|
181
|
-
// Check error codes first (more reliable)
|
|
182
129
|
if (hasCodeIn(err, TIMEOUT_CODES)) {
|
|
183
130
|
return true;
|
|
184
131
|
}
|
|
185
132
|
|
|
186
|
-
// Fall back to message patterns
|
|
187
133
|
return messageMatches(err, TIMEOUT_PATTERNS);
|
|
188
134
|
}
|
|
189
135
|
|
|
190
|
-
|
|
191
|
-
* Check if error is a server error (5xx)
|
|
192
|
-
*/
|
|
136
|
+
|
|
193
137
|
function isServerError(err: unknown): boolean {
|
|
194
138
|
const status = getErrorStatus(err);
|
|
195
139
|
return status !== undefined && status >= 500 && status < 600;
|
|
196
140
|
}
|
|
197
141
|
|
|
198
|
-
|
|
199
|
-
* Check if file is busy/locked
|
|
200
|
-
*/
|
|
142
|
+
|
|
201
143
|
function isFileBusy(err: unknown): boolean {
|
|
202
144
|
return hasCodeIn(err, FILE_BUSY_CODES);
|
|
203
145
|
}
|
|
204
146
|
|
|
205
|
-
|
|
206
|
-
* Check if connection was refused
|
|
207
|
-
*/
|
|
147
|
+
|
|
208
148
|
function isConnectionRefused(err: unknown): boolean {
|
|
209
149
|
return hasCodeIn(err, CONNECTION_REFUSED_CODES);
|
|
210
150
|
}
|
|
211
151
|
|
|
212
|
-
// =============================================================================
|
|
213
|
-
// Utilities
|
|
214
|
-
// =============================================================================
|
|
215
152
|
|
|
216
153
|
const sleep = (ms: number): Promise<void> =>
|
|
217
154
|
new Promise((resolve) => setTimeout(resolve, ms));
|