@goonnguyen/human-mcp 2.8.3 → 2.8.4

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 +525 -0
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -167915,6 +167915,263 @@ function estimateVideoSize(duration, aspectRatio) {
167915
167915
  // src/tools/hands/processors/image-editor.ts
167916
167916
  init_logger();
167917
167917
  init_image_loader();
167918
+ async function editImage(geminiClient, options, config) {
167919
+ const startTime = Date.now();
167920
+ try {
167921
+ const processedInputImage = await processImageForEditing(options.inputImage);
167922
+ const editingPrompt = buildEditingPrompt(options);
167923
+ logger2.info(`Image editing operation: ${options.operation}`);
167924
+ logger2.info(`Editing prompt: "${editingPrompt}"`);
167925
+ const model = geminiClient.getImageGenerationModel();
167926
+ const requestContent = await buildRequestContent(options, processedInputImage, editingPrompt);
167927
+ const response = await model.generateContent(requestContent);
167928
+ const result = response.response;
167929
+ const candidates = result.candidates;
167930
+ logger2.debug(`Gemini API response structure: ${JSON.stringify({
167931
+ hasCandidates: !!candidates,
167932
+ candidatesLength: candidates?.length,
167933
+ firstCandidate: candidates?.[0] ? {
167934
+ hasContent: !!candidates[0].content,
167935
+ hasParts: !!candidates[0].content?.parts,
167936
+ partsLength: candidates[0].content?.parts?.length
167937
+ } : null
167938
+ })}`);
167939
+ if (!candidates || candidates.length === 0) {
167940
+ logger2.error("No candidates in Gemini response. Full response:", JSON.stringify(result, null, 2));
167941
+ throw new Error("No image candidates returned from Gemini API. This may indicate the API doesn't support image editing yet, or the request format is incorrect.");
167942
+ }
167943
+ const candidate = candidates[0];
167944
+ if (!candidate || !candidate.content) {
167945
+ logger2.error("Invalid candidate structure:", JSON.stringify(candidate, null, 2));
167946
+ throw new Error("Invalid response format from Gemini API: missing candidate content");
167947
+ }
167948
+ if (!candidate.content.parts || candidate.content.parts.length === 0) {
167949
+ logger2.error("No parts in candidate content:", JSON.stringify(candidate.content, null, 2));
167950
+ throw new Error("Invalid response format from Gemini API: missing content parts. Note: Gemini image editing may not be available in the current API version.");
167951
+ }
167952
+ let imageData = null;
167953
+ let mimeType = "image/jpeg";
167954
+ logger2.debug(`Searching for image data in ${candidate.content.parts.length} parts`);
167955
+ for (const part of candidate.content.parts) {
167956
+ logger2.debug(`Part type: ${JSON.stringify(Object.keys(part))}`);
167957
+ if ("inlineData" in part && part.inlineData) {
167958
+ imageData = part.inlineData.data;
167959
+ mimeType = part.inlineData.mimeType || "image/jpeg";
167960
+ logger2.info(`Found image data: ${imageData.length} bytes, type: ${mimeType}`);
167961
+ break;
167962
+ }
167963
+ }
167964
+ if (!imageData) {
167965
+ logger2.error("No image data found in response parts:", JSON.stringify(candidate.content.parts, null, 2));
167966
+ throw new Error("No image data found in Gemini response. The API may have returned text instead of an edited image.");
167967
+ }
167968
+ const processingTime = Date.now() - startTime;
167969
+ let resultData;
167970
+ let format;
167971
+ let filePath;
167972
+ let fileName;
167973
+ let fileUrl;
167974
+ let fileSize;
167975
+ const shouldSaveFile = options.saveToFile !== false;
167976
+ const shouldUploadToR2 = options.uploadToR2 === true;
167977
+ if (shouldSaveFile && config) {
167978
+ try {
167979
+ const savedFile = await saveBase64ToFile(imageData, mimeType, config, {
167980
+ prefix: options.filePrefix || `edited-${options.operation}`,
167981
+ directory: options.saveDirectory,
167982
+ uploadToR2: shouldUploadToR2
167983
+ });
167984
+ filePath = savedFile.filePath;
167985
+ fileName = savedFile.fileName;
167986
+ fileUrl = savedFile.url;
167987
+ fileSize = savedFile.size;
167988
+ logger2.info(`Edited image saved to file: ${filePath}`);
167989
+ if (options.outputFormat === "url") {
167990
+ resultData = fileUrl || filePath || `data:${mimeType};base64,${imageData}`;
167991
+ format = fileUrl ? "url" : "file_path";
167992
+ } else {
167993
+ resultData = `data:${mimeType};base64,${imageData}`;
167994
+ format = "base64_data_uri";
167995
+ }
167996
+ } catch (error) {
167997
+ logger2.warn(`Failed to save edited image file: ${error}. Falling back to base64 only.`);
167998
+ resultData = `data:${mimeType};base64,${imageData}`;
167999
+ format = "base64_data_uri";
168000
+ }
168001
+ } else {
168002
+ if (options.outputFormat === "base64") {
168003
+ resultData = `data:${mimeType};base64,${imageData}`;
168004
+ format = "base64_data_uri";
168005
+ } else {
168006
+ resultData = `data:${mimeType};base64,${imageData}`;
168007
+ format = "base64_data_uri";
168008
+ logger2.warn("URL output format requested but file saving disabled. Returning base64 data URI");
168009
+ }
168010
+ }
168011
+ return {
168012
+ editedImageData: resultData,
168013
+ format,
168014
+ operation: options.operation,
168015
+ processingTime,
168016
+ originalSize: estimateImageSize2(processedInputImage.data),
168017
+ editedSize: estimateImageSize2(imageData),
168018
+ filePath,
168019
+ fileName,
168020
+ fileUrl,
168021
+ fileSize,
168022
+ metadata: {
168023
+ prompt: options.prompt,
168024
+ operation: options.operation,
168025
+ strength: options.strength,
168026
+ guidanceScale: options.guidanceScale,
168027
+ seed: options.seed
168028
+ }
168029
+ };
168030
+ } catch (error) {
168031
+ const processingTime = Date.now() - startTime;
168032
+ logger2.error(`Image editing failed after ${processingTime}ms:`, error);
168033
+ if (error instanceof Error) {
168034
+ if (error.message.includes("API key")) {
168035
+ throw new Error("Invalid or missing Google AI API key. Please check your GOOGLE_GEMINI_API_KEY environment variable.");
168036
+ }
168037
+ if (error.message.includes("quota") || error.message.includes("rate limit")) {
168038
+ throw new Error("API quota exceeded or rate limit reached. Please try again later.");
168039
+ }
168040
+ if (error.message.includes("safety") || error.message.includes("policy")) {
168041
+ throw new Error("Image editing blocked due to safety policies. Please modify your request and try again.");
168042
+ }
168043
+ throw new Error(`Image editing failed: ${error.message}`);
168044
+ }
168045
+ throw new Error("Image editing failed due to an unexpected error");
168046
+ }
168047
+ }
168048
+ async function processImageForEditing(inputImage) {
168049
+ try {
168050
+ const result = await loadImageForProcessing(inputImage, {
168051
+ fetchTimeout: 30000,
168052
+ maxWidth: 1024,
168053
+ maxHeight: 1024,
168054
+ quality: 85
168055
+ });
168056
+ return {
168057
+ data: result.data,
168058
+ mimeType: result.mimeType
168059
+ };
168060
+ } catch (error) {
168061
+ throw new Error(`Failed to process input image: ${error instanceof Error ? error.message : error}`);
168062
+ }
168063
+ }
168064
+ function buildEditingPrompt(options) {
168065
+ let prompt = options.prompt;
168066
+ switch (options.operation) {
168067
+ case "inpaint":
168068
+ if (options.maskPrompt) {
168069
+ prompt = `Using the provided image, ${prompt}. Focus on ${options.maskPrompt}. Keep all other parts of the image unchanged.`;
168070
+ } else {
168071
+ prompt = `Using the provided image, ${prompt}. Ensure the changes blend naturally with the existing image style, lighting, and perspective.`;
168072
+ }
168073
+ break;
168074
+ case "outpaint":
168075
+ const direction = options.expandDirection || "all directions";
168076
+ prompt = `Expand the provided image ${direction === "all" ? "in all directions" : `to the ${direction}`} and add: ${prompt}. Match the original image's style, lighting, and perspective. Seamlessly blend the new content with the existing image.`;
168077
+ if (options.expansionRatio && options.expansionRatio !== 1.5) {
168078
+ prompt += ` Expand by approximately ${Math.round(options.expansionRatio * 100)}%.`;
168079
+ }
168080
+ break;
168081
+ case "style_transfer":
168082
+ prompt = `Transform the provided image to have this style: ${prompt}. Maintain the original composition, objects, and structure while applying the new artistic style.`;
168083
+ if (options.styleStrength) {
168084
+ const strength = options.styleStrength > 0.8 ? "strongly" : options.styleStrength > 0.5 ? "moderately" : "subtly";
168085
+ prompt += ` Apply the style ${strength}.`;
168086
+ }
168087
+ break;
168088
+ case "object_manipulation":
168089
+ if (options.targetObject) {
168090
+ const action = options.manipulationType || "modify";
168091
+ prompt = `In the provided image, ${action} the ${options.targetObject}: ${prompt}`;
168092
+ if (options.targetPosition) {
168093
+ prompt += `. Position: ${options.targetPosition}`;
168094
+ }
168095
+ prompt += `. Keep all other elements unchanged.`;
168096
+ }
168097
+ break;
168098
+ case "multi_image_compose":
168099
+ prompt = `Combine the provided images: ${prompt}`;
168100
+ if (options.compositionLayout) {
168101
+ prompt += `. Use a ${options.compositionLayout} layout`;
168102
+ }
168103
+ prompt += `. Ensure natural blending and consistent lighting across the composition.`;
168104
+ break;
168105
+ }
168106
+ if (options.quality === "high") {
168107
+ prompt += " Generate a high-quality result with fine details and professional finish.";
168108
+ } else if (options.quality === "draft") {
168109
+ prompt += " Provide a quick draft version.";
168110
+ }
168111
+ if (options.negativePrompt) {
168112
+ prompt += ` Do not include: ${options.negativePrompt}.`;
168113
+ }
168114
+ return prompt;
168115
+ }
168116
+ async function buildRequestContent(options, processedInputImage, editingPrompt) {
168117
+ const content = [
168118
+ {
168119
+ inlineData: {
168120
+ data: processedInputImage.data,
168121
+ mimeType: processedInputImage.mimeType
168122
+ }
168123
+ },
168124
+ {
168125
+ text: editingPrompt
168126
+ }
168127
+ ];
168128
+ if (options.operation === "style_transfer" && options.styleImage) {
168129
+ try {
168130
+ const processedStyle = await processImageForEditing(options.styleImage);
168131
+ content.push({
168132
+ inlineData: {
168133
+ data: processedStyle.data,
168134
+ mimeType: processedStyle.mimeType
168135
+ }
168136
+ });
168137
+ } catch (error) {
168138
+ logger2.warn(`Failed to process style image: ${error}. Proceeding without style reference.`);
168139
+ }
168140
+ }
168141
+ if (options.operation === "multi_image_compose" && options.secondaryImages) {
168142
+ let imageCount = 1;
168143
+ for (const secondaryImage of options.secondaryImages) {
168144
+ if (imageCount >= 3) {
168145
+ logger2.warn("Gemini supports up to 3 images. Skipping additional images.");
168146
+ break;
168147
+ }
168148
+ try {
168149
+ const processedSecondary = await processImageForEditing(secondaryImage);
168150
+ content.push({
168151
+ inlineData: {
168152
+ data: processedSecondary.data,
168153
+ mimeType: processedSecondary.mimeType
168154
+ }
168155
+ });
168156
+ imageCount++;
168157
+ } catch (error) {
168158
+ logger2.warn(`Failed to process secondary image: ${error}. Skipping this image.`);
168159
+ }
168160
+ }
168161
+ }
168162
+ return content;
168163
+ }
168164
+ function estimateImageSize2(base64Data) {
168165
+ const dataLength = base64Data.length;
168166
+ const estimatedBytes = dataLength * 3 / 4;
168167
+ if (estimatedBytes < 1e5) {
168168
+ return "512x512";
168169
+ } else if (estimatedBytes < 400000) {
168170
+ return "1024x1024";
168171
+ } else {
168172
+ return "1024x1024+";
168173
+ }
168174
+ }
167918
168175
 
167919
168176
  // src/tools/hands/index.ts
167920
168177
  init_logger();
@@ -168070,6 +168327,177 @@ async function registerHandsTool(server, config) {
168070
168327
  };
168071
168328
  }
168072
168329
  });
168330
+ server.registerTool("gemini_edit_image", {
168331
+ title: "Gemini Image Editing Tool",
168332
+ description: "Edit images using AI with text-based instructions for inpainting, outpainting, style transfer, object manipulation, and composition. No masks required - just describe what you want to change.",
168333
+ inputSchema: {
168334
+ operation: exports_external.enum([
168335
+ "inpaint",
168336
+ "outpaint",
168337
+ "style_transfer",
168338
+ "object_manipulation",
168339
+ "multi_image_compose"
168340
+ ]).describe("Type of image editing operation to perform"),
168341
+ input_image: exports_external.string().describe("Base64 encoded image or file path to the input image"),
168342
+ prompt: exports_external.string().min(1, "Prompt cannot be empty").describe("Text description of the desired edit"),
168343
+ mask_image: exports_external.string().optional().describe("Base64 encoded mask image for inpainting (white = edit area, black = keep)"),
168344
+ mask_prompt: exports_external.string().optional().describe("Text description of the area to mask for editing"),
168345
+ expand_direction: exports_external.enum(["all", "left", "right", "top", "bottom", "horizontal", "vertical"]).optional().describe("Direction to expand the image"),
168346
+ expansion_ratio: exports_external.number().min(0.1).max(3).optional().default(1.5).describe("How much to expand the image (1.0 = no expansion)"),
168347
+ style_image: exports_external.string().optional().describe("Base64 encoded reference image for style transfer"),
168348
+ style_strength: exports_external.number().min(0.1).max(1).optional().default(0.7).describe("Strength of style application"),
168349
+ target_object: exports_external.string().optional().describe("Description of the object to manipulate"),
168350
+ manipulation_type: exports_external.enum(["move", "resize", "remove", "replace", "duplicate"]).optional().describe("Type of object manipulation"),
168351
+ target_position: exports_external.string().optional().describe("New position for the object (e.g., 'center', 'top-left')"),
168352
+ secondary_images: exports_external.array(exports_external.string()).optional().describe("Array of base64 encoded images for composition"),
168353
+ composition_layout: exports_external.enum(["blend", "collage", "overlay", "side_by_side"]).optional().describe("How to combine multiple images"),
168354
+ blend_mode: exports_external.enum(["normal", "multiply", "screen", "overlay", "soft_light"]).optional().describe("Blending mode for image composition"),
168355
+ negative_prompt: exports_external.string().optional().describe("What to avoid in the edited image"),
168356
+ strength: exports_external.number().min(0.1).max(1).optional().default(0.8).describe("Strength of the editing effect"),
168357
+ guidance_scale: exports_external.number().min(1).max(20).optional().default(7.5).describe("How closely to follow the prompt"),
168358
+ seed: exports_external.number().int().min(0).optional().describe("Random seed for reproducible results"),
168359
+ output_format: exports_external.enum(["base64", "url"]).optional().default("base64").describe("Output format for the edited image"),
168360
+ quality: exports_external.enum(["draft", "standard", "high"]).optional().default("standard").describe("Quality level of the editing")
168361
+ }
168362
+ }, async (args) => {
168363
+ try {
168364
+ return await handleImageEditing(geminiClient, args, config);
168365
+ } catch (error) {
168366
+ const mcpError = handleError(error);
168367
+ logger2.error(`Tool gemini_edit_image error:`, mcpError);
168368
+ return {
168369
+ content: [{
168370
+ type: "text",
168371
+ text: `Error: ${mcpError.message}`
168372
+ }],
168373
+ isError: true
168374
+ };
168375
+ }
168376
+ });
168377
+ server.registerTool("gemini_inpaint_image", {
168378
+ title: "Gemini Image Inpainting Tool",
168379
+ description: "Add or modify specific areas of an image using natural language descriptions. No mask required - just describe what to change and where.",
168380
+ inputSchema: {
168381
+ input_image: exports_external.string().describe("Base64 encoded image or file path to the input image"),
168382
+ prompt: exports_external.string().min(1, "Prompt cannot be empty").describe("Text description of what to add or change in the image"),
168383
+ mask_image: exports_external.string().optional().describe("(Optional) Base64 encoded mask image - not used by Gemini but kept for compatibility"),
168384
+ mask_prompt: exports_external.string().optional().describe("Text description of WHERE in the image to make changes (e.g., 'the empty space beside the cat', 'the top-left corner')"),
168385
+ negative_prompt: exports_external.string().optional().describe("What to avoid in the edited area"),
168386
+ strength: exports_external.number().min(0.1).max(1).optional().default(0.8).describe("Strength of the editing effect"),
168387
+ guidance_scale: exports_external.number().min(1).max(20).optional().default(7.5).describe("How closely to follow the prompt"),
168388
+ seed: exports_external.number().int().min(0).optional().describe("Random seed for reproducible results"),
168389
+ output_format: exports_external.enum(["base64", "url"]).optional().default("base64").describe("Output format"),
168390
+ quality: exports_external.enum(["draft", "standard", "high"]).optional().default("standard").describe("Quality level")
168391
+ }
168392
+ }, async (args) => {
168393
+ try {
168394
+ const inpaintArgs = { ...args, operation: "inpaint" };
168395
+ return await handleImageEditing(geminiClient, inpaintArgs, config);
168396
+ } catch (error) {
168397
+ const mcpError = handleError(error);
168398
+ logger2.error(`Tool gemini_inpaint_image error:`, mcpError);
168399
+ return {
168400
+ content: [{
168401
+ type: "text",
168402
+ text: `Error: ${mcpError.message}`
168403
+ }],
168404
+ isError: true
168405
+ };
168406
+ }
168407
+ });
168408
+ server.registerTool("gemini_outpaint_image", {
168409
+ title: "Gemini Image Outpainting Tool",
168410
+ description: "Expand an image beyond its original borders in specified directions",
168411
+ inputSchema: {
168412
+ input_image: exports_external.string().describe("Base64 encoded image or file path to the input image"),
168413
+ prompt: exports_external.string().min(1, "Prompt cannot be empty").describe("Text description of what to add in the expanded areas"),
168414
+ expand_direction: exports_external.enum(["all", "left", "right", "top", "bottom", "horizontal", "vertical"]).optional().default("all").describe("Direction to expand the image"),
168415
+ expansion_ratio: exports_external.number().min(0.1).max(3).optional().default(1.5).describe("How much to expand the image (1.0 = no expansion)"),
168416
+ negative_prompt: exports_external.string().optional().describe("What to avoid in the expanded areas"),
168417
+ strength: exports_external.number().min(0.1).max(1).optional().default(0.8).describe("Strength of the editing effect"),
168418
+ guidance_scale: exports_external.number().min(1).max(20).optional().default(7.5).describe("How closely to follow the prompt"),
168419
+ seed: exports_external.number().int().min(0).optional().describe("Random seed for reproducible results"),
168420
+ output_format: exports_external.enum(["base64", "url"]).optional().default("base64").describe("Output format"),
168421
+ quality: exports_external.enum(["draft", "standard", "high"]).optional().default("standard").describe("Quality level")
168422
+ }
168423
+ }, async (args) => {
168424
+ try {
168425
+ const outpaintArgs = { ...args, operation: "outpaint" };
168426
+ return await handleImageEditing(geminiClient, outpaintArgs, config);
168427
+ } catch (error) {
168428
+ const mcpError = handleError(error);
168429
+ logger2.error(`Tool gemini_outpaint_image error:`, mcpError);
168430
+ return {
168431
+ content: [{
168432
+ type: "text",
168433
+ text: `Error: ${mcpError.message}`
168434
+ }],
168435
+ isError: true
168436
+ };
168437
+ }
168438
+ });
168439
+ server.registerTool("gemini_style_transfer_image", {
168440
+ title: "Gemini Style Transfer Tool",
168441
+ description: "Transfer the style from one image to another using AI",
168442
+ inputSchema: {
168443
+ input_image: exports_external.string().describe("Base64 encoded image or file path to the input image"),
168444
+ prompt: exports_external.string().min(1, "Prompt cannot be empty").describe("Text description of the desired style"),
168445
+ style_image: exports_external.string().optional().describe("Base64 encoded reference image for style transfer"),
168446
+ style_strength: exports_external.number().min(0.1).max(1).optional().default(0.7).describe("Strength of style application"),
168447
+ negative_prompt: exports_external.string().optional().describe("What style elements to avoid"),
168448
+ guidance_scale: exports_external.number().min(1).max(20).optional().default(7.5).describe("How closely to follow the prompt"),
168449
+ seed: exports_external.number().int().min(0).optional().describe("Random seed for reproducible results"),
168450
+ output_format: exports_external.enum(["base64", "url"]).optional().default("base64").describe("Output format"),
168451
+ quality: exports_external.enum(["draft", "standard", "high"]).optional().default("standard").describe("Quality level")
168452
+ }
168453
+ }, async (args) => {
168454
+ try {
168455
+ const styleArgs = { ...args, operation: "style_transfer" };
168456
+ return await handleImageEditing(geminiClient, styleArgs, config);
168457
+ } catch (error) {
168458
+ const mcpError = handleError(error);
168459
+ logger2.error(`Tool gemini_style_transfer_image error:`, mcpError);
168460
+ return {
168461
+ content: [{
168462
+ type: "text",
168463
+ text: `Error: ${mcpError.message}`
168464
+ }],
168465
+ isError: true
168466
+ };
168467
+ }
168468
+ });
168469
+ server.registerTool("gemini_compose_images", {
168470
+ title: "Gemini Image Composition Tool",
168471
+ description: "Combine multiple images into a single composition using AI",
168472
+ inputSchema: {
168473
+ input_image: exports_external.string().describe("Base64 encoded primary image"),
168474
+ secondary_images: exports_external.array(exports_external.string()).describe("Array of base64 encoded secondary images to compose"),
168475
+ prompt: exports_external.string().min(1, "Prompt cannot be empty").describe("Text description of how to compose the images"),
168476
+ composition_layout: exports_external.enum(["blend", "collage", "overlay", "side_by_side"]).optional().default("blend").describe("How to combine the images"),
168477
+ blend_mode: exports_external.enum(["normal", "multiply", "screen", "overlay", "soft_light"]).optional().default("normal").describe("Blending mode for image composition"),
168478
+ negative_prompt: exports_external.string().optional().describe("What to avoid in the composition"),
168479
+ strength: exports_external.number().min(0.1).max(1).optional().default(0.8).describe("Strength of the composition effect"),
168480
+ guidance_scale: exports_external.number().min(1).max(20).optional().default(7.5).describe("How closely to follow the prompt"),
168481
+ seed: exports_external.number().int().min(0).optional().describe("Random seed for reproducible results"),
168482
+ output_format: exports_external.enum(["base64", "url"]).optional().default("base64").describe("Output format"),
168483
+ quality: exports_external.enum(["draft", "standard", "high"]).optional().default("standard").describe("Quality level")
168484
+ }
168485
+ }, async (args) => {
168486
+ try {
168487
+ const composeArgs = { ...args, operation: "multi_image_compose" };
168488
+ return await handleImageEditing(geminiClient, composeArgs, config);
168489
+ } catch (error) {
168490
+ const mcpError = handleError(error);
168491
+ logger2.error(`Tool gemini_compose_images error:`, mcpError);
168492
+ return {
168493
+ content: [{
168494
+ type: "text",
168495
+ text: `Error: ${mcpError.message}`
168496
+ }],
168497
+ isError: true
168498
+ };
168499
+ }
168500
+ });
168073
168501
  }
168074
168502
  async function handleImageGeneration(geminiClient, args, config) {
168075
168503
  const input = ImageGenerationInputSchema.parse(args);
@@ -168236,6 +168664,103 @@ async function handleImageToVideoGeneration(geminiClient, args, config) {
168236
168664
  isError: false
168237
168665
  };
168238
168666
  }
168667
+ async function handleImageEditing(geminiClient, args, config) {
168668
+ const input = ImageEditingInputSchema.parse(args);
168669
+ const {
168670
+ operation,
168671
+ input_image,
168672
+ prompt,
168673
+ mask_image,
168674
+ mask_prompt,
168675
+ expand_direction,
168676
+ expansion_ratio,
168677
+ style_image,
168678
+ style_strength,
168679
+ target_object,
168680
+ manipulation_type,
168681
+ target_position,
168682
+ secondary_images,
168683
+ composition_layout,
168684
+ blend_mode,
168685
+ negative_prompt,
168686
+ strength,
168687
+ guidance_scale,
168688
+ seed,
168689
+ output_format,
168690
+ quality
168691
+ } = input;
168692
+ logger2.info(`Editing image with operation: "${operation}" and prompt: "${prompt}"`);
168693
+ const editingOptions = {
168694
+ operation,
168695
+ inputImage: input_image,
168696
+ prompt,
168697
+ maskImage: mask_image,
168698
+ maskPrompt: mask_prompt,
168699
+ expandDirection: expand_direction,
168700
+ expansionRatio: expansion_ratio || 1.5,
168701
+ styleImage: style_image,
168702
+ styleStrength: style_strength || 0.7,
168703
+ targetObject: target_object,
168704
+ manipulationType: manipulation_type,
168705
+ targetPosition: target_position,
168706
+ secondaryImages: secondary_images,
168707
+ compositionLayout: composition_layout,
168708
+ blendMode: blend_mode,
168709
+ negativePrompt: negative_prompt,
168710
+ strength: strength || 0.8,
168711
+ guidanceScale: guidance_scale || 7.5,
168712
+ seed,
168713
+ outputFormat: output_format || "base64",
168714
+ quality: quality || "standard",
168715
+ fetchTimeout: config.server.fetchTimeout,
168716
+ saveToFile: true,
168717
+ uploadToR2: config.cloudflare?.accessKey ? true : false,
168718
+ filePrefix: `edited-${operation}`
168719
+ };
168720
+ const result = await editImage(geminiClient, editingOptions, config);
168721
+ let base64Data;
168722
+ let mimeType;
168723
+ if (result.editedImageData.startsWith("data:")) {
168724
+ const matches = result.editedImageData.match(/data:([^;]+);base64,(.+)/);
168725
+ if (matches && matches[1] && matches[2]) {
168726
+ mimeType = matches[1];
168727
+ base64Data = matches[2];
168728
+ }
168729
+ }
168730
+ const contextText = `✅ Image edited successfully using ${operation} operation
168731
+
168732
+ **Editing Details:**
168733
+ - Operation: ${operation}
168734
+ - Prompt: "${prompt}"
168735
+ - Format: ${result.format}
168736
+ - Original Size: ${result.originalSize}
168737
+ - Edited Size: ${result.editedSize}
168738
+ - Processing Time: ${result.processingTime}ms
168739
+ - Quality: ${quality}
168740
+ - Timestamp: ${new Date().toISOString()}${result.filePath ? `
168741
+
168742
+ **File Information:**
168743
+ - File Path: ${result.filePath}
168744
+ - File Name: ${result.fileName}
168745
+ - File Size: ${result.fileSize} bytes` : ""}${result.fileUrl ? `
168746
+ - Public URL: ${result.fileUrl}` : ""}${result.metadata ? `
168747
+
168748
+ **Operation Metadata:**
168749
+ - Strength: ${result.metadata.strength}
168750
+ - Guidance Scale: ${result.metadata.guidanceScale}
168751
+ - Seed: ${result.metadata.seed || "random"}` : ""}`;
168752
+ const formattedResponse = formatMediaResponse({
168753
+ url: result.fileUrl,
168754
+ filePath: result.filePath,
168755
+ base64: base64Data,
168756
+ mimeType,
168757
+ size: result.fileSize
168758
+ }, config, contextText);
168759
+ return {
168760
+ content: formattedResponse,
168761
+ isError: false
168762
+ };
168763
+ }
168239
168764
 
168240
168765
  // src/tools/mouth/schemas.ts
168241
168766
  var VoiceNames = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goonnguyen/human-mcp",
3
- "version": "2.8.3",
3
+ "version": "2.8.4",
4
4
  "description": "Human MCP: Bringing Human Capabilities to Coding Agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",