@goonnguyen/human-mcp 2.4.0 → 2.5.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.
Files changed (2) hide show
  1. package/dist/index.js +120 -35
  2. package/package.json +8 -8
package/dist/index.js CHANGED
@@ -160065,41 +160065,98 @@ function getCloudflareR2() {
160065
160065
  // src/tools/eyes/processors/image.ts
160066
160066
  async function processImage(model, source, options) {
160067
160067
  const startTime = Date.now();
160068
- try {
160069
- logger2.debug(`Processing image: ${source.substring(0, 50)}...`);
160070
- const { imageData, mimeType } = await loadImage(source, options.fetchTimeout);
160071
- const prompt = createPrompt(options);
160072
- const response = await model.generateContent([
160073
- { text: prompt },
160074
- {
160075
- inlineData: {
160076
- mimeType,
160077
- data: imageData
160068
+ const maxRetries = 3;
160069
+ let lastError = null;
160070
+ for (let attempt = 1;attempt <= maxRetries; attempt++) {
160071
+ try {
160072
+ logger2.debug(`Processing image (attempt ${attempt}/${maxRetries}): ${source.substring(0, 50)}...`);
160073
+ const { imageData, mimeType } = await loadImage(source, options.fetchTimeout);
160074
+ const prompt = createPrompt(options);
160075
+ logger2.debug(`Generated prompt for analysis: ${prompt.substring(0, 100)}...`);
160076
+ logger2.debug(`Image data size: ${imageData.length} characters, MIME type: ${mimeType}`);
160077
+ const response = await model.generateContent([
160078
+ { text: prompt },
160079
+ {
160080
+ inlineData: {
160081
+ mimeType,
160082
+ data: imageData
160083
+ }
160078
160084
  }
160085
+ ]);
160086
+ const result = await response.response;
160087
+ const analysisText = result.text();
160088
+ logger2.debug(`Gemini response received. Text length: ${analysisText ? analysisText.length : 0}`);
160089
+ if (!analysisText || analysisText.trim().length === 0) {
160090
+ const errorMsg = `Gemini returned empty response on attempt ${attempt}/${maxRetries}`;
160091
+ logger2.warn(errorMsg);
160092
+ if (attempt === maxRetries) {
160093
+ logger2.info("Using fallback analysis due to empty Gemini response");
160094
+ const fallbackAnalysis = "Image was processed but detailed analysis is unavailable. This may be due to API limitations or content restrictions.";
160095
+ return {
160096
+ description: "Image analysis completed with limited results",
160097
+ analysis: fallbackAnalysis,
160098
+ elements: [],
160099
+ insights: ["Gemini API returned empty response", "Consider retrying the analysis"],
160100
+ recommendations: ["Check image format and content", "Verify API key and quotas"],
160101
+ metadata: {
160102
+ processing_time_ms: Date.now() - startTime,
160103
+ model_used: model.model,
160104
+ attempts_made: maxRetries,
160105
+ status: "partial_success"
160106
+ }
160107
+ };
160108
+ }
160109
+ const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
160110
+ logger2.debug(`Retrying in ${delay}ms...`);
160111
+ await new Promise((resolve) => setTimeout(resolve, delay));
160112
+ continue;
160079
160113
  }
160080
- ]);
160081
- const result = await response.response;
160082
- const analysisText = result.text();
160083
- if (!analysisText) {
160084
- throw new ProcessingError("No analysis result from Gemini");
160085
- }
160086
- const parsed = parseAnalysisResponse(analysisText);
160087
- const processingTime = Date.now() - startTime;
160088
- return {
160089
- description: parsed.description || "Image analysis completed",
160090
- analysis: parsed.analysis || analysisText,
160091
- elements: parsed.elements || [],
160092
- insights: parsed.insights || [],
160093
- recommendations: parsed.recommendations || [],
160094
- metadata: {
160095
- processing_time_ms: processingTime,
160096
- model_used: model.model
160114
+ const parsed = parseAnalysisResponse(analysisText);
160115
+ const processingTime = Date.now() - startTime;
160116
+ logger2.info(`Image analysis successful on attempt ${attempt}. Processing time: ${processingTime}ms`);
160117
+ return {
160118
+ description: parsed.description || "Image analysis completed",
160119
+ analysis: parsed.analysis || analysisText,
160120
+ elements: parsed.elements || [],
160121
+ insights: parsed.insights || [],
160122
+ recommendations: parsed.recommendations || [],
160123
+ metadata: {
160124
+ processing_time_ms: processingTime,
160125
+ model_used: model.model,
160126
+ attempts_made: attempt,
160127
+ status: "success"
160128
+ }
160129
+ };
160130
+ } catch (error) {
160131
+ lastError = error instanceof Error ? error : new Error("Unknown error");
160132
+ logger2.warn(`Image processing attempt ${attempt} failed:`, lastError.message);
160133
+ if (attempt < maxRetries && isRetryableError(lastError)) {
160134
+ const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
160135
+ logger2.debug(`Retrying in ${delay}ms...`);
160136
+ await new Promise((resolve) => setTimeout(resolve, delay));
160137
+ continue;
160138
+ } else if (attempt === maxRetries) {
160139
+ break;
160097
160140
  }
160098
- };
160099
- } catch (error) {
160100
- logger2.error("Image processing error:", error);
160101
- throw new ProcessingError(`Failed to process image: ${error instanceof Error ? error.message : "Unknown error"}`);
160141
+ }
160102
160142
  }
160143
+ logger2.error("Image processing failed after all retries:", lastError);
160144
+ throw new ProcessingError(`Failed to process image after ${maxRetries} attempts: ${lastError?.message || "Unknown error"}`);
160145
+ }
160146
+ function isRetryableError(error) {
160147
+ const retryableMessages = [
160148
+ "timeout",
160149
+ "network",
160150
+ "rate limit",
160151
+ "temporary",
160152
+ "429",
160153
+ "500",
160154
+ "502",
160155
+ "503",
160156
+ "504"
160157
+ ];
160158
+ const errorMessage = error.message.toLowerCase();
160159
+ return retryableMessages.some((msg) => errorMessage.includes(msg));
160103
160160
  }
160104
160161
  async function loadImage(source, fetchTimeout) {
160105
160162
  if (source.match(/^\[Image #\d+\]$/)) {
@@ -164048,11 +164105,35 @@ async function registerVisionTools(server, geminiClient, config) {
164048
164105
  return await handleAnalyze(geminiClient, args, config);
164049
164106
  } catch (error) {
164050
164107
  const mcpError = handleError(error);
164051
- logger2.error(`Tool eyes_analyze error:`, mcpError);
164108
+ logger2.error(`Tool eyes_analyze error:`, {
164109
+ message: mcpError.message,
164110
+ code: mcpError.code,
164111
+ args,
164112
+ timestamp: new Date().toISOString(),
164113
+ stackTrace: error instanceof Error ? error.stack : "No stack trace available"
164114
+ });
164115
+ let userMessage = mcpError.message;
164116
+ if (mcpError.message.includes("No analysis result from Gemini")) {
164117
+ userMessage = `The image analysis service returned an empty response. This can happen due to:
164118
+ ` + `• API rate limits or quota exceeded
164119
+ ` + `• Image content restrictions
164120
+ ` + `• Temporary service issues
164121
+ ` + `• Network connectivity problems
164122
+
164123
+ ` + "Please try again in a few moments, or check if your image meets the requirements.";
164124
+ } else if (mcpError.message.includes("Failed to process image after")) {
164125
+ userMessage = `Image processing failed after multiple attempts. This could be due to:
164126
+ ` + `• Network connectivity issues
164127
+ ` + `• API service unavailability
164128
+ ` + `• Image format or size issues
164129
+ ` + `• Rate limiting
164130
+
164131
+ ` + "Please check your internet connection and try again.";
164132
+ }
164052
164133
  return {
164053
164134
  content: [{
164054
164135
  type: "text",
164055
- text: `Error: ${mcpError.message}`
164136
+ text: `Error: ${userMessage}`
164056
164137
  }],
164057
164138
  isError: true
164058
164139
  };
@@ -164216,10 +164297,13 @@ async function registerDocumentTools(server, geminiClient, config) {
164216
164297
  async function handleAnalyze(geminiClient, args, config) {
164217
164298
  const input = EyesInputSchema.parse(args);
164218
164299
  const { source, type, detail_level } = input;
164219
- logger2.info(`Analyzing ${type} with detail level: ${detail_level}`);
164300
+ const customPrompt = "prompt" in input ? input.prompt : undefined;
164301
+ logger2.info(`Analyzing ${type} with detail level: ${detail_level}, source: ${source.substring(0, 50)}...`);
164220
164302
  const model = geminiClient.getModel(detail_level || "detailed");
164221
164303
  const options = {
164222
- ...input,
164304
+ analysis_type: "general",
164305
+ detail_level: detail_level || "detailed",
164306
+ specific_focus: customPrompt,
164223
164307
  fetchTimeout: config.server.fetchTimeout
164224
164308
  };
164225
164309
  let result;
@@ -164236,6 +164320,7 @@ async function handleAnalyze(geminiClient, args, config) {
164236
164320
  default:
164237
164321
  throw new Error(`Unsupported media type: ${type}`);
164238
164322
  }
164323
+ logger2.info(`Analysis completed for ${type}. Processing time: ${result.metadata.processing_time_ms}ms`);
164239
164324
  return {
164240
164325
  content: [
164241
164326
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goonnguyen/human-mcp",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "Human MCP: Bringing Human Capabilities to Coding Agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -9,13 +9,13 @@
9
9
  "human-mcp": "bin/human-mcp.js"
10
10
  },
11
11
  "scripts": {
12
- "dev": "bun run --watch src/index.ts",
13
- "build": "bun build src/index.ts --target=node --outdir=dist",
14
- "start": "bun run dist/index.js",
15
- "test": "bun test tests/unit/ && bun test tests/integration/sse-transport.test.ts && bun test tests/integration/server.test.ts && bun test tests/integration/http-transport-files.test.ts",
16
- "test:parallel": "bun test",
17
- "typecheck": "tsc --noEmit",
18
- "inspector": "mcp-inspector stdio -- bun run src/index.ts"
12
+ "dev": "bun run --watch src/index.ts 2>&1 | tee -a ./logs.txt",
13
+ "build": "bun build src/index.ts --target=node --outdir=dist 2>&1 | tee -a ./logs.txt",
14
+ "start": "bun run dist/index.js 2>&1 | tee -a ./logs.txt",
15
+ "test": "bun test tests/unit/ && bun test tests/integration/sse-transport.test.ts && bun test tests/integration/server.test.ts && bun test tests/integration/http-transport-files.test.ts 2>&1 | tee -a ./logs.txt",
16
+ "test:parallel": "bun test 2>&1 | tee -a ./logs.txt",
17
+ "typecheck": "tsc --noEmit 2>&1 | tee -a ./logs.txt",
18
+ "inspector": "mcp-inspector stdio -- bun run src/index.ts 2>&1 | tee -a ./logs.txt"
19
19
  },
20
20
  "dependencies": {
21
21
  "@aws-sdk/client-s3": "^3.888.0",