Skip to content

Conversation

@viniciusventura29
Copy link
Contributor

@viniciusventura29 viniciusventura29 commented Nov 6, 2025

wip

Summary by CodeRabbit

  • New Features

    • Added image generation tool with Gemini support
    • Implemented storage adapter for file system persistence
    • Added user authentication and tools infrastructure
    • Introduced contract management system for pricing
    • Added middleware pipeline (retry, logging, timeout)
  • Refactoring

    • Consolidated user tools into shared module
    • Centralized storage utilities
  • Documentation

    • Added comprehensive guides for image generators, storage adapters, and shared modules
  • Chores

    • New workspace "nanobanana" for MCP server
    • Updated dependencies and package exports
    • Added project configuration files

@coderabbitai
Copy link

coderabbitai bot commented Nov 6, 2025

Warning

Rate limit exceeded

@viktormarinho has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 20 minutes and 17 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between c647943 and 475a1f9.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (30)
  • nanobanana/package.json (1 hunks)
  • nanobanana/server/main.ts (1 hunks)
  • nanobanana/server/npm-shims.d.ts (1 hunks)
  • nanobanana/tsconfig.json (1 hunks)
  • nanobanana/wrangler.toml (1 hunks)
  • object-storage/package.json (1 hunks)
  • object-storage/server/main.ts (1 hunks)
  • object-storage/server/npm-shims.d.ts (1 hunks)
  • object-storage/server/tools/storage.ts (1 hunks)
  • object-storage/server/views.ts (1 hunks)
  • object-storage/tsconfig.json (1 hunks)
  • object-storage/view/src/lib/rpc.ts (1 hunks)
  • object-storage/wrangler.toml (1 hunks)
  • shared/image-generators/README.md (1 hunks)
  • shared/image-generators/base.ts (1 hunks)
  • shared/package.json (1 hunks)
  • shared/storage/README.md (1 hunks)
  • shared/tools/user.ts (2 hunks)
  • template-minimal/package.json (1 hunks)
  • template-minimal/server/main.ts (1 hunks)
  • template-minimal/server/npm-shims.d.ts (1 hunks)
  • template-minimal/tsconfig.json (1 hunks)
  • template-minimal/wrangler.toml (1 hunks)
  • template-with-view/package.json (1 hunks)
  • template-with-view/server/main.ts (1 hunks)
  • template-with-view/server/npm-shims.d.ts (1 hunks)
  • template-with-view/server/views.ts (1 hunks)
  • template-with-view/tsconfig.json (1 hunks)
  • template-with-view/view/src/lib/rpc.ts (1 hunks)
  • template-with-view/wrangler.toml (1 hunks)

Walkthrough

This PR consolidates and extends the shared infrastructure across the monorepo. It centralizes user tools into a shared module, introduces a comprehensive image generation tooling framework with middleware and storage adapters, establishes a storage abstraction layer, and adds a new "nanobanana" workspace implementing a Gemini-based image generation MCP server. Templates are refactored to use the centralized tools. Workspace configuration is updated with new exports and dependencies.

Changes

Cohort / File(s) Summary
Package & Workspace Configuration
package.json, shared/package.json
Added "nanobanana" workspace entry; added export mappings for ./tools/user, ./tools, ./image-generators, ./storage; added devDependencies for @deco/workers-runtime and zod.
Shared Tools Consolidation
object-storage/server/tools/index.ts, template-minimal/server/tools/index.ts, template-with-view/server/tools/index.ts, shared/tools/user.ts, shared/tools/index.ts
Moved userTools import/export from local ./user.ts to @decocms/mcps-shared/tools/user across all files; generified createGetUserTool with UserToolsEnv interface; created shared tools aggregation entry point.
Template Local Tools Removal
template-minimal/server/tools/user.ts, template-with-view/server/tools/user.ts
Deleted local createGetUserTool function and userTools array (now sourced from shared).
Shared Image Generators Module
shared/image-generators/base.ts, shared/image-generators/middleware.ts, shared/image-generators/storage.ts, shared/image-generators/index.ts, shared/image-generators/README.md
Added image generation tool framework with schemas (GenerateImageInputSchema, GenerateImageOutputSchema), contracts, middleware (withRetry, withLogging, withTimeout), image persistence (saveImage), and comprehensive documentation.
Shared Storage Abstraction
shared/storage/interface.ts, shared/storage/types.ts, shared/storage/adapters/filesystem.ts, shared/storage/index.ts, shared/storage/README.md
Introduced ObjectStorage interface, FileSystemBinding contract, FileSystemStorageAdapter implementation with presigned URL generation and object operations (list, metadata, delete), and storage documentation.
Nanobanana MCP Server
nanobanana/package.json, nanobanana/wrangler.toml, nanobanana/tsconfig.json, nanobanana/vite.config.ts, nanobanana/.gitignore, nanobanana/README.md
Added new workspace with Cloudflare Workers-based MCP server configuration, build tooling, and project documentation.
Nanobanana Server Implementation
nanobanana/server/main.ts, nanobanana/server/constants.ts, nanobanana/server/tools/index.ts, nanobanana/server/tools/gemini.ts, nanobanana/server/tools/utils/gemini.ts
Implemented MCP runtime, Gemini image generation tools, OpenRouter API client, and tool aggregation.
Nanobanana Generated Types
nanobanana/shared/deco.gen.ts
Generated comprehensive type definitions for Deco MCP contracts, FS operations, state schema, and environment interfaces.
Template Updates
template-minimal/server/main.ts, template-minimal/wrangler.toml
Removed views from runtime configuration; updated wrangler project name to "mcp-template-minimal".
TypeScript Configuration
object-storage/tsconfig.json, template-minimal/tsconfig.json, template-with-view/tsconfig.json
Added exclusions for @deco/workers-runtime paths from compilation scope.
Shared Documentation
shared/README.md
Added module overview documenting user tools, image generators, middlewares, and usage patterns.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant MCP as MCP Runtime
    participant Auth as Auth/Context
    participant Middleware as Middleware Pipeline<br/>(Retry/Logging/Timeout)
    participant Provider as Image Provider<br/>(Gemini)
    participant Storage as Storage<br/>(FileSystem)
    participant Contract as Contract<br/>Management

    Client->>MCP: Request Image Generation
    MCP->>Auth: Ensure Authenticated
    Auth-->>MCP: User Verified
    
    MCP->>Contract: Authorize Contract
    Contract-->>MCP: TransactionId
    
    MCP->>Middleware: Execute with Pipelines
    
    rect rgba(100, 150, 200, 0.3)
    Note over Middleware: withRetry Wrapper
    Middleware->>Middleware: Attempt (max retries)
    end
    
    rect rgba(100, 200, 150, 0.3)
    Note over Middleware: withTimeout Wrapper
    Middleware->>Provider: Call generateImage(prompt, baseUrl, aspectRatio)
    Provider-->>Middleware: GeminiResponse with image data
    end
    
    rect rgba(200, 150, 100, 0.3)
    Note over Middleware: withLogging Wrapper
    Middleware->>Middleware: Log execution details
    end
    
    Middleware->>Storage: Save Image to Presigned URL
    Storage-->>Middleware: { url, key }
    
    Middleware->>Contract: Settle Contract
    Contract-->>Middleware: Success
    
    Middleware-->>MCP: { image, finishReason }
    MCP-->>Client: Image Generation Result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Key areas requiring attention:
    • Image generation contract model and middleware composition in shared/image-generators/base.ts (logic density, error handling paths)
    • FileSystem storage adapter implementation and presigned URL handling in shared/storage/adapters/filesystem.ts
    • Gemini client integration and OpenRouter API request/response mapping in nanobanana/server/tools/utils/gemini.ts
    • Generated types and Env interface complexity in nanobanana/shared/deco.gen.ts
    • User tools generification impact across multiple files and inheritance of UserToolsEnv interface

Possibly related PRs

Poem

A garden of shared tools takes root so deep,
Where images bloom through Gemini's keep! 🎨
Contracts dance, storage glows bright,
Nanobanana shines—our new delight! 🍌✨
From templates to workspace, all unified,
This MCP symphony has arrived!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: adding a new 'gemini-nano-banana' workspace with initial setup, which aligns with the significant additions of the nanobanana workspace and related configuration files.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Nov 6, 2025

🚀 Preview Deployments Ready!

Your changes have been deployed to preview environments:

📦 object-storage

🔗 View Preview

📦 template-minimal

🔗 View Preview

These previews will be automatically updated with new commits to this PR.


Deployed from commit: 96de27e

… management integration

- Added constants for Gemini and OpenRouter base URLs.
- Implemented image generation functionality using the Gemini API.
- Introduced user tools for authentication and management, streamlining user-related operations.
- Updated tool exports to utilize shared user tools from the @decocms/mcps-shared package.
- Enhanced the image editor interface with new features and improved user experience.
- Refactored code for better organization and maintainability.
…tiple files

- Standardized formatting in gemini.ts, utils/gemini.ts, hooks.ts, and various shared modules.
- Added missing commas and removed unnecessary line breaks for better code clarity.
- Enhanced comments for improved documentation and understanding of the codebase.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (6)
gemini-nano-banana/server/views.ts (1)

16-25: Consider using environment variables for the view URL.

The hardcoded URL https://banana.deco.page appears to be a placeholder. Consider making this configurable via an environment variable to support different deployment environments (dev, staging, production).

Apply this diff to make the URL configurable:

-export const views: CreateMCPServerOptions<
-  Env,
-  typeof StateSchema
->["views"] = () => [
+export const views: CreateMCPServerOptions<
+  Env,
+  typeof StateSchema
+>["views"] = (env: Env) => [
   {
     title: "Image Editor",
     icon: "draw",
-    url: "https://banana.deco.page",
+    url: env.IMAGE_EDITOR_URL || "https://banana.deco.page",
   },
 ];

Then add IMAGE_EDITOR_URL?: string; to the Env type in main.ts.

shared/image-generators/base.ts (2)

34-40: Simplify nullable optional chain.

The chaining of .nullable().optional() can be simplified using .nullish() which is equivalent and more concise.

Apply this diff:

   baseImageUrl: z
     .string()
-    .nullable()
-    .optional()
+    .nullish()
     .describe(
       "The URL of an existing image to use as a base. Only use this if you want to generate an image based on an existing image (image-to-image generation)."
     ),

68-73: Consider typing the authentication return value.

The ensureAuthenticated method returns any, which loses type safety. If there's a known user type or authentication result type, consider using it here.

For example:

export interface ImageGeneratorEnv {
  DECO_CHAT_REQUEST_CONTEXT: {
    ensureAuthenticated: () => Promise<{ userId: string; [key: string]: unknown }>;
  };
  DECO_CHAT_WORKSPACE: string;
}

Note: Only apply if the actual return type is known and stable.

gemini-nano-banana/vite.config.ts (1)

36-37: Remove commented code.

The commented __filename and __dirname definitions should either be uncommented if needed or removed entirely to maintain code cleanliness.

Apply this diff to remove the commented code:

-    // '__filename': '""',
-    // '__dirname': '""',
gemini-nano-banana/view/src/components/error-boundary.tsx (1)

9-12: Consider explicit state type declaration.

While the implicit state type works, explicitly declaring it improves type safety and maintainability.

Apply this diff to add explicit typing:

+type ErrorBoundaryState = {
+  hasError: boolean;
+};
+
-export class ErrorBoundary extends React.Component<ErrorBoundaryProps> {
-  state = {
+export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
+  state: ErrorBoundaryState = {
     hasError: false,
   };
gemini-nano-banana/view/src/components/ui/popover.tsx (1)

12-38: LGTM! Tailwind v4 syntax is correct.

Both PopoverTrigger and PopoverContent are properly implemented:

  • Tailwind v4 syntax verified: outline-hidden and origin-(--radix-popover-content-transform-origin) with parentheses are correct.
  • Portal rendering and prop forwarding work as expected.
  • Default values for align and sideOffset are sensible.

Optionally, consider adding displayName properties to all components for improved debugging experience in React DevTools:

Popover.displayName = "Popover";
PopoverTrigger.displayName = "PopoverTrigger";
PopoverContent.displayName = "PopoverContent";
PopoverAnchor.displayName = "PopoverAnchor";
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 312a4e6 and 422dd98.

⛔ Files ignored due to path filters (11)
  • bun.lock is excluded by !**/*.lock
  • gemini-nano-banana/public/apple-touch-icon.png is excluded by !**/*.png
  • gemini-nano-banana/public/d.png is excluded by !**/*.png
  • gemini-nano-banana/public/favicon-16x16.png is excluded by !**/*.png
  • gemini-nano-banana/public/favicon-192x192.png is excluded by !**/*.png
  • gemini-nano-banana/public/favicon-32x32.png is excluded by !**/*.png
  • gemini-nano-banana/public/favicon-48x48.png is excluded by !**/*.png
  • gemini-nano-banana/public/favicon-512x512.png is excluded by !**/*.png
  • gemini-nano-banana/public/favicon.ico is excluded by !**/*.ico
  • gemini-nano-banana/public/favicon.png is excluded by !**/*.png
  • gemini-nano-banana/public/logo.png is excluded by !**/*.png
📒 Files selected for processing (46)
  • gemini-nano-banana/.gitignore (1 hunks)
  • gemini-nano-banana/.npmrc (1 hunks)
  • gemini-nano-banana/README.md (1 hunks)
  • gemini-nano-banana/index.html (1 hunks)
  • gemini-nano-banana/package.json (1 hunks)
  • gemini-nano-banana/public/manifest.json (1 hunks)
  • gemini-nano-banana/server/constants.ts (1 hunks)
  • gemini-nano-banana/server/main.ts (1 hunks)
  • gemini-nano-banana/server/tools/gemini.ts (1 hunks)
  • gemini-nano-banana/server/tools/index.ts (1 hunks)
  • gemini-nano-banana/server/tools/utils/gemini.ts (1 hunks)
  • gemini-nano-banana/server/views.ts (1 hunks)
  • gemini-nano-banana/shared/deco.gen.ts (1 hunks)
  • gemini-nano-banana/tsconfig.json (1 hunks)
  • gemini-nano-banana/view/components.json (1 hunks)
  • gemini-nano-banana/view/src/components/error-boundary.tsx (1 hunks)
  • gemini-nano-banana/view/src/components/logged-provider.tsx (1 hunks)
  • gemini-nano-banana/view/src/components/ui/button.tsx (1 hunks)
  • gemini-nano-banana/view/src/components/ui/collapsible.tsx (1 hunks)
  • gemini-nano-banana/view/src/components/ui/popover.tsx (1 hunks)
  • gemini-nano-banana/view/src/components/ui/sonner.tsx (1 hunks)
  • gemini-nano-banana/view/src/components/user-button.tsx (1 hunks)
  • gemini-nano-banana/view/src/hooks/useToolCalls.ts (1 hunks)
  • gemini-nano-banana/view/src/lib/hooks.ts (1 hunks)
  • gemini-nano-banana/view/src/lib/rpc-logged.ts (1 hunks)
  • gemini-nano-banana/view/src/lib/rpc.ts (1 hunks)
  • gemini-nano-banana/view/src/lib/utils.ts (1 hunks)
  • gemini-nano-banana/view/src/main.tsx (1 hunks)
  • gemini-nano-banana/view/src/routes/home.tsx (1 hunks)
  • gemini-nano-banana/view/src/styles.css (1 hunks)
  • gemini-nano-banana/vite.config.ts (1 hunks)
  • gemini-nano-banana/wrangler.toml (1 hunks)
  • object-storage/server/tools/index.ts (1 hunks)
  • package.json (1 hunks)
  • shared/README.md (1 hunks)
  • shared/image-generators/base.ts (1 hunks)
  • shared/image-generators/index.ts (1 hunks)
  • shared/image-generators/middleware.ts (1 hunks)
  • shared/image-generators/storage.ts (1 hunks)
  • shared/package.json (1 hunks)
  • shared/tools/index.ts (1 hunks)
  • shared/tools/user.ts (3 hunks)
  • template-minimal/server/tools/index.ts (1 hunks)
  • template-minimal/server/tools/user.ts (0 hunks)
  • template-with-view/server/tools/index.ts (1 hunks)
  • template-with-view/server/tools/user.ts (0 hunks)
💤 Files with no reviewable changes (2)
  • template-minimal/server/tools/user.ts
  • template-with-view/server/tools/user.ts
🧰 Additional context used
🧬 Code graph analysis (19)
gemini-nano-banana/view/src/components/ui/button.tsx (1)
gemini-nano-banana/view/src/lib/utils.ts (1)
  • cn (4-6)
gemini-nano-banana/view/src/components/ui/popover.tsx (1)
gemini-nano-banana/view/src/lib/utils.ts (1)
  • cn (4-6)
gemini-nano-banana/view/src/main.tsx (1)
gemini-nano-banana/view/src/components/ui/sonner.tsx (1)
  • Toaster (23-23)
gemini-nano-banana/view/src/components/user-button.tsx (2)
gemini-nano-banana/view/src/hooks/useToolCalls.ts (2)
  • ToolCall (3-8)
  • useToolCalls (13-54)
gemini-nano-banana/view/src/lib/hooks.ts (2)
  • User (7-12)
  • useOptionalUser (46-63)
gemini-nano-banana/server/main.ts (1)
gemini-nano-banana/shared/deco.gen.ts (2)
  • Env (154-181)
  • StateSchema (152-152)
gemini-nano-banana/server/tools/gemini.ts (5)
gemini-nano-banana/server/tools/utils/gemini.ts (2)
  • generateImage (138-167)
  • createGeminiClient (170-173)
gemini-nano-banana/server/main.ts (1)
  • Env (35-62)
shared/image-generators/base.ts (3)
  • GenerateImageInput (61-61)
  • GenerateImageOutput (62-62)
  • createImageGeneratorTool (108-122)
shared/image-generators/storage.ts (2)
  • extractImageData (150-161)
  • saveImageToFileSystem (78-142)
shared/image-generators/middleware.ts (3)
  • withContractManagement (177-211)
  • withRetry (22-66)
  • withLogging (84-103)
gemini-nano-banana/server/views.ts (2)
gemini-nano-banana/server/main.ts (1)
  • Env (35-62)
gemini-nano-banana/shared/deco.gen.ts (2)
  • Env (154-181)
  • StateSchema (152-152)
template-minimal/server/tools/index.ts (1)
shared/tools/user.ts (1)
  • userTools (65-65)
gemini-nano-banana/view/src/routes/home.tsx (4)
gemini-nano-banana/view/src/lib/rpc.ts (1)
  • client (6-6)
gemini-nano-banana/view/src/components/user-button.tsx (1)
  • UserButton (215-318)
gemini-nano-banana/view/src/lib/hooks.ts (1)
  • useOptionalUser (46-63)
gemini-nano-banana/view/src/components/logged-provider.tsx (1)
  • LoggedProvider (34-57)
gemini-nano-banana/view/src/components/logged-provider.tsx (2)
gemini-nano-banana/view/src/lib/hooks.ts (1)
  • useUser (18-39)
gemini-nano-banana/view/src/components/error-boundary.tsx (1)
  • ErrorBoundary (9-26)
gemini-nano-banana/server/tools/index.ts (4)
object-storage/server/tools/index.ts (2)
  • tools (12-12)
  • userTools (16-16)
template-minimal/server/tools/index.ts (2)
  • tools (11-11)
  • userTools (14-14)
template-with-view/server/tools/index.ts (2)
  • tools (11-11)
  • userTools (14-14)
shared/tools/user.ts (1)
  • userTools (65-65)
gemini-nano-banana/view/src/lib/hooks.ts (2)
gemini-nano-banana/view/src/lib/rpc-logged.ts (1)
  • client (31-62)
gemini-nano-banana/view/src/components/logged-provider.tsx (1)
  • FailedToFetchUserError (7-15)
gemini-nano-banana/view/src/lib/rpc.ts (3)
gemini-nano-banana/server/main.ts (1)
  • Env (35-62)
gemini-nano-banana/shared/deco.gen.ts (1)
  • Env (154-181)
gemini-nano-banana/view/src/lib/rpc-logged.ts (1)
  • client (31-62)
object-storage/server/tools/index.ts (2)
shared/tools/user.ts (1)
  • userTools (65-65)
object-storage/server/tools/storage.ts (1)
  • storageTools (316-323)
gemini-nano-banana/vite.config.ts (1)
shared/deco-vite-plugin.ts (1)
  • deco (89-146)
gemini-nano-banana/view/src/lib/rpc-logged.ts (1)
gemini-nano-banana/view/src/lib/rpc.ts (1)
  • client (6-6)
gemini-nano-banana/server/tools/utils/gemini.ts (2)
gemini-nano-banana/server/main.ts (1)
  • Env (35-62)
gemini-nano-banana/server/constants.ts (1)
  • OPENROUTER_BASE_URL (3-3)
gemini-nano-banana/shared/deco.gen.ts (1)
gemini-nano-banana/server/main.ts (1)
  • Env (35-62)
template-with-view/server/tools/index.ts (1)
shared/tools/user.ts (1)
  • userTools (65-65)
🪛 Biome (2.1.2)
gemini-nano-banana/shared/deco.gen.ts

[error] 62-62: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)


[error] 109-109: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)

🪛 LanguageTool
shared/README.md

[locale-violation] ~3-~3: “servers” é um estrangeirismo. É preferível dizer “servidores”.
Context: ...para criar MCPs (Model Context Protocol servers) na plataforma Deco. ## Módulos Dispon...

(PT_BARBARISMS_REPLACE_SERVERS)


[uncategorized] ~32-~32: Se é uma abreviatura, falta um ponto. Se for uma expressão, coloque entre aspas.
Context: ..., DALL-E, Midjourney, Stable Diffusion, etc). #### Uso Básico ```typescript impor...

(ABREVIATIONS_PUNCTUATION)


[locale-violation] ~80-~80: “ratio” é um estrangeirismo. É preferível dizer “razão” ou “rácio”.
Context: ...aspectRatio (enum, opcional) - Aspect ratio: "1:1", "2:3", "3:2", "3:4", "4:3", "4:...

(PT_BARBARISMS_REPLACE_RATIO)


[uncategorized] ~85-~85: Se é uma abreviatura, falta um ponto. Se for uma expressão, coloque entre aspas.
Context: ...o término (sucesso, filtro de conteúdo, etc) #### Middlewares Disponíveis ##### `...

(ABREVIATIONS_PUNCTUATION)


[locale-violation] ~99-~99: “performance” é um estrangeirismo. É preferível dizer “desempenho”, “atuação”, “apresentação”, “espetáculo” ou “interpretação”.
Context: ...fn, providerName)` Adiciona logging de performance e erros. ```typescript const loggedGen...

(PT_BARBARISMS_REPLACE_PERFORMANCE)


[style] ~106-~106: Três frases seguidas começam com a mesma palavra. Considere reformular a frase ou use um dicionário para encontrar um sinônimo.
Context: ...`` ##### withTimeout(fn, timeoutMs) Adiciona timeout para prevenir execuções muito l...

(PORTUGUESE_WORD_REPEAT_BEGINNING_RULE)


[style] ~114-~114: Três frases seguidas começam com a mesma palavra. Considere reformular a frase ou use um dicionário para encontrar um sinônimo.
Context: ...withContractManagement(fn, clauseId) Adiciona autorização e settlement de contratos p...

(PORTUGUESE_WORD_REPEAT_BEGINNING_RULE)


[misspelling] ~128-~128: Quando escrita sem acento, esta palavra é um verbo. Se pretende referir-se a um substantivo ou adjetivo, deve utilizar a forma acentuada.
Context: ..., options)` Salva uma imagem base64 no file system. ```typescript const { url, pat...

(DIACRITICS)


[grammar] ~243-~243: Possível erro de concordância de número.
Context: ...e input/output 3. Manutenibilidade: Bug fixes e melhorias beneficiam todos os MCPs au...

(GENERAL_NUMBER_AGREEMENT_ERRORS)


[uncategorized] ~244-~244: Se é uma abreviatura, falta um ponto. Se for uma expressão, coloque entre aspas.
Context: ...ar novos providers (DALL-E, Midjourney, etc) 5. Type Safety: TypeScript garante...

(ABREVIATIONS_PUNCTUATION)


[uncategorized] ~293-~293: Se é uma abreviatura, falta um ponto. Se for uma expressão, coloque entre aspas.
Context: ...shared/image-generators/2. Adicione o export em/shared/package.json`: ```json { ...

(ABREVIATIONS_PUNCTUATION)


[locale-violation] ~293-~293: “package” é um estrangeirismo. É preferível dizer “embalagem” ou “pacote”.
Context: ...ge-generators/2. Adicione o export em/shared/package.json`: ```json { "exports": { "....

(PT_BARBARISMS_REPLACE_PACKAGE)

🔇 Additional comments (34)
gemini-nano-banana/.gitignore (1)

1-1: LGTM!

Standard practice for Cloudflare Workers projects to ignore development environment variable files.

gemini-nano-banana/.npmrc (1)

1-1: LGTM!

Standard configuration for JSR package registry integration.

gemini-nano-banana/view/components.json (1)

1-21: LGTM!

Standard ShadCN UI configuration with appropriate path aliases and Tailwind integration.

shared/image-generators/index.ts (1)

1-10: LGTM!

Clean barrel export pattern providing a centralized entry point for image generator utilities.

gemini-nano-banana/server/constants.ts (1)

1-3: LGTM!

Centralized API endpoint configuration following best practices.

gemini-nano-banana/view/src/styles.css (1)

1-138: LGTM!

Well-structured Tailwind v4 stylesheet with:

  • Correct modern syntax (@import, @plugin, @custom-variant, @theme inline)
  • OKLCH color space for perceptual uniformity
  • Comprehensive light/dark theme tokens
  • Proper responsive radius calculations

The implementation aligns with Tailwind v4 best practices.

object-storage/server/tools/index.ts (1)

9-9: Good refactoring to centralize shared code.

The change from local import to the shared package @decocms/mcps-shared promotes code reuse and follows DRY principles. The dependency is properly declared in object-storage/package.json (version 1.0.0).

shared/README.md (1)

1-307: Excellent documentation! Minor Portuguese language polish optional.

The README provides comprehensive documentation with clear examples and usage patterns. The static analysis tool flagged some Portuguese language style issues (e.g., "servers" vs "servidores"), but these are mostly pedantic. Technical terms like "servers", "ratio", and "performance" are widely understood in the developer community and don't need to be translated.

The documentation structure is excellent:

  • Clear module organization
  • Practical code examples
  • Complete end-to-end example (lines 166-237)
  • Benefits and usage guidance
gemini-nano-banana/view/src/hooks/useToolCalls.ts (1)

14-20: LGTM: Proper error handling for localStorage parsing.

The try-catch block correctly handles potential JSON parsing errors from corrupted localStorage data by falling back to an empty array.

gemini-nano-banana/view/src/lib/rpc-logged.ts (2)

31-62: LGTM: Well-implemented transparent logging proxy.

The proxy correctly:

  • Passes through non-function properties unchanged
  • Preserves this context with apply(this, args)
  • Logs both successful calls and errors
  • Re-throws errors after logging to maintain original behavior
  • Dispatches events for reactive updates

18-19: Good practice: Limiting localStorage entries.

The 100-call limit prevents localStorage from growing unbounded while retaining enough history for debugging purposes.

shared/image-generators/base.ts (1)

108-122: LGTM: Well-designed factory with proper type constraints.

The factory function correctly:

  • Enforces ImageGeneratorEnv constraint on the generic type
  • Provides sensible defaults for the description
  • Wires up schemas for validation
  • Delegates execution to the provider-specific implementation
package.json (1)

20-20: LGTM: Workspace correctly added.

The new gemini-nano-banana workspace is properly added to the workspaces array in alphabetical order.

shared/tools/index.ts (1)

1-5: LGTM: Clean barrel export pattern.

This file provides a central export point for shared tools, making it easy for consumers to import from a single location. The pattern allows for future expansion as more shared tools are added.

shared/package.json (1)

8-16: LGTM: Exports and dependencies are correctly configured.

The new exports align with the shared tooling surface, and the npm alias for the JSR package is properly specified. The zod dependency with caret versioning (^3.24.3) follows the established pattern across all workspaces in the monorepo, so no changes are needed.

gemini-nano-banana/index.html (1)

1-41: LGTM! Standard PWA-ready HTML boilerplate.

The HTML entry point is well-structured with proper PWA metadata, multiple favicon variations, and correct manifest linking. The module script path /view/src/main.tsx aligns with Vite's dev server conventions.

template-with-view/server/tools/index.ts (1)

8-14: LGTM! Consistent refactoring to centralized shared tools.

The migration from local user tools to the shared package maintains the same public API while consolidating domain logic. This aligns with the broader PR pattern of extracting reusable tooling.

gemini-nano-banana/view/src/lib/rpc.ts (1)

1-6: LGTM! Clean type-safe RPC client setup.

The client configuration correctly extracts the MCP surface type from the server-side environment and creates a properly typed singleton instance for frontend use.

gemini-nano-banana/wrangler.toml (1)

1-17: LGTM! Standard Cloudflare Workers configuration.

The Wrangler config correctly sets up SPA asset serving with worker-first routing, appropriate compatibility flags, and Deco workspace integration.

gemini-nano-banana/public/manifest.json (1)

1-37: LGTM! Valid PWA manifest.

The manifest includes all required properties with appropriate icon sizes and purposes for modern PWA installation. Theme and background colors are properly defined.

template-minimal/server/tools/index.ts (1)

8-14: LGTM! Consistent refactoring to shared tools.

This follows the same centralization pattern applied across all templates, ensuring consistency in tooling architecture.

gemini-nano-banana/server/tools/index.ts (1)

1-14: LGTM! Consistent tools organization pattern.

The tools index follows the established pattern from other templates, importing from the centralized shared package and maintaining clear domain separation.

gemini-nano-banana/vite.config.ts (1)

1-35: LGTM! Well-configured Vite setup for Cloudflare Workers.

The configuration correctly integrates React, Cloudflare Workers, Tailwind CSS, and Deco plugins. The path alias and environment shims are appropriate for the Workers runtime environment.

gemini-nano-banana/view/src/lib/utils.ts (1)

1-6: LGTM!

This is a standard and well-implemented utility for composing Tailwind classes. The combination of clsx for conditional classes and twMerge for Tailwind conflict resolution is the recommended pattern.

gemini-nano-banana/package.json (1)

1-55: LGTM! Modern stack with appropriate dependencies.

The manifest is well-structured with React 19, Tailwind 4, and modern tooling. The Node >=22.0.0 requirement aligns with the use of cutting-edge features.

gemini-nano-banana/view/src/components/ui/sonner.tsx (1)

1-23: LGTM! Well-integrated theme-aware toast component.

The Sonner wrapper correctly integrates with the theme system and provides appropriate styling via CSS variables.

shared/tools/user.ts (2)

11-26: LGTM! Well-designed generic interface.

The UserToolsEnv interface provides a clear contract for any environment using these tools, improving reusability across different MCP implementations.


37-62: LGTM! Type-safe generic implementation.

The generic createGetUserTool function correctly constrains the environment parameter while maintaining type safety throughout the implementation.

gemini-nano-banana/view/src/components/ui/button.tsx (1)

1-59: LGTM! Well-structured Button component with comprehensive variants.

The component correctly:

  • Uses CVA for variant management with appropriate Tailwind 4 syntax (slash notation for opacity)
  • Implements the asChild pattern via Radix Slot
  • Provides sensible defaults and extensive styling options
  • Follows React 19 conventions (no forwardRef needed)
gemini-nano-banana/tsconfig.json (1)

1-48: LGTM! Solid TypeScript configuration with strict linting.

The configuration appropriately enables strict mode, modern ES features, and comprehensive linting rules. The path aliases align well with the project structure.

gemini-nano-banana/view/src/components/ui/popover.tsx (2)

1-10: LGTM! Clean component wrapper.

The imports and Popover component correctly wrap the Radix primitive with proper TypeScript typing and the conventional data-slot attribute for styling hooks.


40-46: LGTM! Complete and consistent exports.

The PopoverAnchor wrapper and exports statement maintain consistency with the rest of the file.

gemini-nano-banana/shared/deco.gen.ts (2)

1-53: Generated code with repetitive headers.

The file structure follows typical json-schema-to-typescript patterns. The repeated header comments (lines 3, 16, 31, 42) and numbered type aliases suggest the generator could be optimized, but this doesn't affect functionality.


134-183: LGTM! Well-structured MCP type definitions.

The Mcp generic type and Env interface correctly define the MCP contract:

  • The Mcp<T> type properly wraps function signatures with asTool and execute capabilities.
  • The Env interface exports four operations that align with the input/output schemas defined earlier in the file.
  • The structure integrates correctly with gemini-nano-banana/server/main.ts as shown in the relevant code snippets.

Comment on lines 108 to 134
const data = await response.json() as OpenRouterResponse;
const choices = data.choices[0];

const image = choices.message.images?.[0]?.image_url?.url;
const nativeFinishReason = choices.native_finish_reason;

if (!image) {
throw new Error("No image generated in the response");
}

return {
candidates: [
{
content: {
parts: [
{
inline_data: {
data: image,
mime_type: "image/png",
},
},
],
},
finishReason: nativeFinishReason,
},
],
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard against empty OpenRouter choices

If OpenRouter responds with an empty choices array (which can happen on provider-side errors), data.choices[0] is undefined, and the next line throws “Cannot read properties of undefined (reading 'message')”, obscuring the real cause. Please add a defensive check before dereferencing so we surface a clear error instead of a runtime TypeError.

-  const data = await response.json() as OpenRouterResponse;
-  const choices = data.choices[0];
-
-  const image = choices.message.images?.[0]?.image_url?.url;
-  const nativeFinishReason = choices.native_finish_reason;
+  const data = await response.json() as OpenRouterResponse;
+  const firstChoice = data.choices?.[0];
+  if (!firstChoice) {
+    throw new Error("OpenRouter response did not include any choices");
+  }
+
+  const image = firstChoice.message.images?.[0]?.image_url?.url;
+  const nativeFinishReason = firstChoice.native_finish_reason;

…e definitions

- Added geminiTools to the tools export for improved modularity.
- Introduced GENERATE_IMAGEInput and GENERATE_IMAGEOutput interfaces for image generation API.
- Updated aspect ratio state management in the ImageEditor component to utilize new type definitions.
- Refactored rpc.ts to correctly import Env from the shared directory.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
gemini-nano-banana/server/tools/gemini.ts (1)

28-52: Guard against missing Gemini candidates.

response.candidates[0] can be undefined, and even if candidate exists, accessing nested properties without full optional chaining will throw TypeError if any intermediate property is missing. This affects Lines 29, 35, and 51.

Apply this diff to add proper optional chaining:

-    const candidate = response.candidates[0];
-    const inlineData = candidate?.content.parts[0].inline_data;
+    const candidate = response.candidates?.[0];
+    const inlineData = candidate?.content?.parts?.[0]?.inline_data;
 
     // Check if image was generated
     if (!inlineData?.data) {
       return {
         error: true,
-        finishReason: candidate.finishReason || undefined,
+        finishReason: candidate?.finishReason || undefined,
       };
     }
 
     // Extract image data
     const { mimeType, imageData } = extractImageData(inlineData);
 
     // Save to file system
     const { url } = await saveImageToFileSystem(env, {
       imageData,
       mimeType,
       metadata: { prompt: input.prompt },
     });
 
     return {
       image: url,
-      finishReason: candidate.finishReason,
+      finishReason: candidate?.finishReason,
     };
gemini-nano-banana/server/tools/utils/gemini.ts (1)

108-116: Guard against empty OpenRouter choices.

If OpenRouter responds with an empty choices array or undefined, data.choices[0] will be undefined, causing Line 111-112 to throw TypeError: Cannot read properties of undefined. Add a defensive check to surface a clear error.

Apply this diff:

   const data = (await response.json()) as OpenRouterResponse;
-  const choices = data.choices[0];
+  const firstChoice = data.choices?.[0];
+  if (!firstChoice) {
+    throw new Error("OpenRouter response did not include any choices");
+  }
 
-  const image = choices.message.images?.[0]?.image_url?.url;
-  const nativeFinishReason = choices.native_finish_reason;
+  const image = firstChoice.message.images?.[0]?.image_url?.url;
+  const nativeFinishReason = firstChoice.native_finish_reason;
 
   if (!image) {
     throw new Error("No image generated in the response");
🧹 Nitpick comments (4)
gemini-nano-banana/view/src/lib/hooks.ts (2)

39-61: Consider refactoring to reduce code duplication.

The useOptionalUser and useUser hooks share nearly identical logic. The only difference is the 401 handling behavior. Consider extracting a shared helper function to reduce duplication and improve maintainability.

Example refactor:

const createUserQuery = (throwOn401: boolean) => ({
  queryKey: ["user", throwOn401 ? "required" : "optional"],
  queryFn: () =>
    client.GET_USER(
      {},
      {
        handleResponse: async (res: Response) => {
          if (res.status === 401) {
            if (throwOn401) {
              throw new FailedToFetchUserError(
                "Failed to fetch user",
                globalThis.location.href,
              );
            }
            return null;
          }
          return res.json();
        },
      },
    ),
  retry: false,
});

export const useUser = () => {
  return useSuspenseQuery(createUserQuery(true));
};

export const useOptionalUser = () => {
  return useSuspenseQuery(createUserQuery(false));
};

63-67: Remove unnecessary comment about removed code.

This comment documents removed template code rather than explaining the current implementation. Such historical notes belong in commit messages, not in the source code.

Apply this diff to remove the comment:

   });
 };
-
-/**
- * Example hooks from the template
- */
-
-// (Removed all TODO-related hooks)
shared/image-generators/base.ts (1)

64-73: Consider tightening the return type of ensureAuthenticated.

The ensureAuthenticated method returns any, which bypasses type safety. If the actual return type is known, consider using a more specific type or unknown to maintain type safety. If the type varies, consider using a generic parameter.

gemini-nano-banana/server/tools/utils/gemini.ts (1)

118-134: Consider determining the actual MIME type instead of hardcoding.

Line 126 hardcodes mime_type: "image/png", but the actual image from OpenRouter might be in a different format (JPEG, WebP, etc.). If OpenRouter provides MIME type information or if it can be inferred from the URL, use that instead of hardcoding to avoid potential format mismatches.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 422dd98 and 35818c4.

📒 Files selected for processing (9)
  • gemini-nano-banana/server/tools/gemini.ts (1 hunks)
  • gemini-nano-banana/server/tools/utils/gemini.ts (1 hunks)
  • gemini-nano-banana/view/src/lib/hooks.ts (1 hunks)
  • shared/image-generators/base.ts (1 hunks)
  • shared/image-generators/index.ts (1 hunks)
  • shared/image-generators/middleware.ts (1 hunks)
  • shared/image-generators/storage.ts (1 hunks)
  • shared/tools/index.ts (1 hunks)
  • shared/tools/user.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • shared/tools/index.ts
  • shared/tools/user.ts
  • shared/image-generators/storage.ts
🧰 Additional context used
🧬 Code graph analysis (3)
gemini-nano-banana/server/tools/gemini.ts (3)
shared/image-generators/base.ts (3)
  • GenerateImageInput (61-61)
  • GenerateImageOutput (62-62)
  • createImageGeneratorTool (111-125)
shared/image-generators/storage.ts (2)
  • extractImageData (150-161)
  • saveImageToFileSystem (78-142)
shared/image-generators/middleware.ts (3)
  • withContractManagement (177-209)
  • withRetry (22-66)
  • withLogging (84-103)
gemini-nano-banana/view/src/lib/hooks.ts (2)
gemini-nano-banana/view/src/lib/rpc-logged.ts (1)
  • client (31-62)
gemini-nano-banana/view/src/components/logged-provider.tsx (1)
  • FailedToFetchUserError (7-15)
gemini-nano-banana/server/tools/utils/gemini.ts (2)
gemini-nano-banana/server/main.ts (1)
  • Env (35-62)
gemini-nano-banana/server/constants.ts (1)
  • OPENROUTER_BASE_URL (3-3)
🔇 Additional comments (4)
gemini-nano-banana/view/src/lib/hooks.ts (2)

1-3: LGTM!

The imports are appropriate and correctly used throughout the file.


5-10: LGTM!

The User interface is well-defined with appropriate field types.

shared/image-generators/index.ts (1)

1-9: LGTM!

Clean barrel export pattern that provides a centralized entry point for the image generators module.

shared/image-generators/middleware.ts (1)

22-66: LGTM!

The retry logic with exponential backoff is well-implemented, correctly skips non-retryable errors (validation errors and 4xx client errors), and provides clear logging.

Comment on lines 177 to 209
export function withContractManagement<
TEnv extends ContractEnv,
TInput,
TOutput,
>(
fn: (input: TInput, env: TEnv) => Promise<TOutput>,
clauseId: string,
): (input: TInput, env: TEnv) => Promise<TOutput> {
return async (input: TInput, env: TEnv) => {
// Skip contract management if not configured
if (!env.NANOBANANA_CONTRACT) {
console.log("[Contract] Contract management not configured, skipping...");
return fn(input, env);
}

// Authorize
const { transactionId } = await env.NANOBANANA_CONTRACT.CONTRACT_AUTHORIZE({
clauses: [{ clauseId, amount: 1 }],
});

// Execute
const result = await fn(input, env);

// Settle
await env.NANOBANANA_CONTRACT.CONTRACT_SETTLE({
transactionId,
clauses: [{ clauseId, amount: 1 }],
vendorId: env.DECO_CHAT_WORKSPACE,
});

return result;
};
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling for contract settlement.

If fn(input, env) throws an exception after authorization (Line 198), the settlement step (Lines 200-205) never executes. This can leave transactions in an authorized but unsettled state, potentially locking resources or causing billing inconsistencies.

Consider wrapping the execution and settlement in a try/catch block to either:

  1. Settle with a failure status, or
  2. Call a rollback/cancellation method on the contract

Example fix:

   // Authorize
   const { transactionId } = await env.NANOBANANA_CONTRACT.CONTRACT_AUTHORIZE({
     clauses: [{ clauseId, amount: 1 }],
   });
 
-  // Execute
-  const result = await fn(input, env);
-
-  // Settle
-  await env.NANOBANANA_CONTRACT.CONTRACT_SETTLE({
-    transactionId,
-    clauses: [{ clauseId, amount: 1 }],
-    vendorId: env.DECO_CHAT_WORKSPACE,
-  });
-
-  return result;
+  try {
+    // Execute
+    const result = await fn(input, env);
+
+    // Settle on success
+    await env.NANOBANANA_CONTRACT.CONTRACT_SETTLE({
+      transactionId,
+      clauses: [{ clauseId, amount: 1 }],
+      vendorId: env.DECO_CHAT_WORKSPACE,
+    });
+
+    return result;
+  } catch (error) {
+    // TODO: Add CONTRACT_ROLLBACK or similar mechanism
+    // to release reserved resources
+    throw error;
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function withContractManagement<
TEnv extends ContractEnv,
TInput,
TOutput,
>(
fn: (input: TInput, env: TEnv) => Promise<TOutput>,
clauseId: string,
): (input: TInput, env: TEnv) => Promise<TOutput> {
return async (input: TInput, env: TEnv) => {
// Skip contract management if not configured
if (!env.NANOBANANA_CONTRACT) {
console.log("[Contract] Contract management not configured, skipping...");
return fn(input, env);
}
// Authorize
const { transactionId } = await env.NANOBANANA_CONTRACT.CONTRACT_AUTHORIZE({
clauses: [{ clauseId, amount: 1 }],
});
// Execute
const result = await fn(input, env);
// Settle
await env.NANOBANANA_CONTRACT.CONTRACT_SETTLE({
transactionId,
clauses: [{ clauseId, amount: 1 }],
vendorId: env.DECO_CHAT_WORKSPACE,
});
return result;
};
}
export function withContractManagement<
TEnv extends ContractEnv,
TInput,
TOutput,
>(
fn: (input: TInput, env: TEnv) => Promise<TOutput>,
clauseId: string,
): (input: TInput, env: TEnv) => Promise<TOutput> {
return async (input: TInput, env: TEnv) => {
// Skip contract management if not configured
if (!env.NANOBANANA_CONTRACT) {
console.log("[Contract] Contract management not configured, skipping...");
return fn(input, env);
}
// Authorize
const { transactionId } = await env.NANOBANANA_CONTRACT.CONTRACT_AUTHORIZE({
clauses: [{ clauseId, amount: 1 }],
});
try {
// Execute
const result = await fn(input, env);
// Settle on success
await env.NANOBANANA_CONTRACT.CONTRACT_SETTLE({
transactionId,
clauses: [{ clauseId, amount: 1 }],
vendorId: env.DECO_CHAT_WORKSPACE,
});
return result;
} catch (error) {
// TODO: Add CONTRACT_ROLLBACK or similar mechanism
// to release reserved resources
throw error;
}
};
}

- Consolidated aspect ratio state initialization for better readability and consistency in the codebase.
- Updated README.md to provide comprehensive project details, setup instructions, and usage examples for the Gemini Nano Banana MCP.
- Improved error handling in gemini.ts to manage cases where the Gemini API does not return a response or valid image data.
- Refactored inline data access to ensure robustness against undefined properties.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (3)
gemini-nano-banana/view/src/routes/home.tsx (2)

73-110: Cross-origin issue still present.

The past review comment correctly identified that img.crossOrigin = "anonymous" must be set before img.src = currentImage (line 108) to prevent canvas tainting when currentImage is a remote URL.


649-664: Download exports only the overlay canvas.

The past review comment correctly identified that the download button exports canvasRef.current.toDataURL(), which contains only the drawing overlay, not the base image. Users receive a transparent/blank file. The fix is to call combineImageWithCanvas() before creating the download link.

gemini-nano-banana/shared/deco.gen.ts (1)

62-62: Empty interfaces flagged by static analysis.

The past review comment correctly identified that DECO_CHAT_VIEWS_LISTInput (line 62) and GET_USERInput (line 109) are empty interfaces that should be type aliases. Since this is auto-generated code, the fix should be applied to the generator configuration or schema.

Based on static analysis hints.

Also applies to: 109-109

🧹 Nitpick comments (4)
gemini-nano-banana/view/src/lib/rpc-logged.ts (3)

6-11: Consider extracting type definitions and using named constants.

The inline type definition and magic number reduce code clarity and maintainability.

Apply this diff:

+/** Maximum number of tool calls to retain in localStorage */
+const MAX_TOOL_CALLS = 100;
+
+/** Structure of a persisted tool call entry */
+interface ToolCallEntry {
+  timestamp: number;
+  tool: string;
+  input: unknown;
+  output: unknown;
+}
+
 /**
  * Helper to persist tool calls directly in localStorage
  */
-const persistToolCall = (entry: {
-  timestamp: number;
-  tool: string;
-  input: any;
-  output: any;
-}) => {
+const persistToolCall = (entry: ToolCallEntry) => {
   const key = "toolCalls";
-  const existing: (typeof entry)[] = JSON.parse(
+  const existing: ToolCallEntry[] = JSON.parse(
     localStorage.getItem(key) ?? "[]",
   );
   const updated = [...existing, entry];
 
   // Keep only last 100 calls to avoid localStorage bloat
-  const trimmed = updated.slice(-100);
+  const trimmed = updated.slice(-MAX_TOOL_CALLS);

Also applies to: 19-19


48-59: Consider consistent output structure for success and error cases.

Error outputs are wrapped in { error: ... } while successful outputs are stored raw. Consumers need to detect this structure difference, which could lead to confusion.

If you want uniform structure, apply this diff:

       } catch (error) {
         // Log errors too
         persistToolCall({
           timestamp: Date.now(),
           tool: String(prop),
           input,
-          output: {
-            error: error instanceof Error ? error.message : String(error),
-          },
+          error: error instanceof Error ? error.message : String(error),
         });
         throw error;
       }

Then update the ToolCallEntry interface:

 interface ToolCallEntry {
   timestamp: number;
   tool: string;
   input: unknown;
   output: unknown;
+  error?: string;
 }

This way, consumers can distinguish success from failure via the error field rather than checking output structure.


13-21: Address concurrency safety in localStorage persistence through debouncing or batching.

The read-modify-write sequence on localStorage is not atomic and poses a race condition risk if concurrent tool calls complete simultaneously. While current usage through React Query caching appears limited, the architectural pattern would benefit from defensive improvements.

Suggested mitigations:

  1. Debounce or batch logging calls within a short window to reduce individual writes
  2. Use a write queue with deferred, coalesced persistence to serialize updates
  3. Add in-memory buffer to accumulate entries before persisting, preventing repeated reads
gemini-nano-banana/view/src/routes/home.tsx (1)

579-593: Rotate buttons lack handlers.

The rotate buttons (lines 579-593) are rendered but have no onClick handlers, so they don't perform any action when clicked.

Do you want me to generate the rotation logic (applying ctx.rotate() transforms to the canvas or creating a rotated Image element)?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 35818c4 and 64f843b.

📒 Files selected for processing (5)
  • gemini-nano-banana/server/tools/index.ts (1 hunks)
  • gemini-nano-banana/shared/deco.gen.ts (1 hunks)
  • gemini-nano-banana/view/src/lib/rpc-logged.ts (1 hunks)
  • gemini-nano-banana/view/src/lib/rpc.ts (1 hunks)
  • gemini-nano-banana/view/src/routes/home.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • gemini-nano-banana/server/tools/index.ts
  • gemini-nano-banana/view/src/lib/rpc.ts
🧰 Additional context used
🧬 Code graph analysis (3)
gemini-nano-banana/view/src/routes/home.tsx (5)
gemini-nano-banana/shared/deco.gen.ts (1)
  • GENERATE_IMAGEInput (158-162)
gemini-nano-banana/view/src/lib/rpc.ts (1)
  • client (6-6)
gemini-nano-banana/view/src/components/user-button.tsx (1)
  • UserButton (215-318)
gemini-nano-banana/view/src/lib/hooks.ts (1)
  • useOptionalUser (44-61)
gemini-nano-banana/view/src/components/logged-provider.tsx (1)
  • LoggedProvider (34-57)
gemini-nano-banana/view/src/lib/rpc-logged.ts (1)
gemini-nano-banana/view/src/lib/rpc.ts (1)
  • client (6-6)
gemini-nano-banana/shared/deco.gen.ts (1)
gemini-nano-banana/server/main.ts (1)
  • Env (35-62)
🪛 Biome (2.1.2)
gemini-nano-banana/shared/deco.gen.ts

[error] 62-62: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)


[error] 109-109: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
gemini-nano-banana/server/tools/gemini.ts (1)

77-77: Verify that geminiTools is actually used or remove as dead code.

The previous review indicated that geminiTools is not imported or used anywhere in the codebase. Additionally, the export pattern appears incorrect—you're exporting an array containing the factory function generateImage rather than instantiated tool instances.

Please run this script to confirm whether geminiTools is actually imported:

#!/bin/bash
# Search for any imports or usage of geminiTools
rg -n 'geminiTools' --type=ts

# Also check the tools/index.ts to see what it exports
cat gemini-nano-banana/server/tools/index.ts 2>/dev/null || echo "index.ts not found"

If geminiTools is indeed unused, remove the export. If it is used, please verify the export pattern is correct for how tools are consumed in tools/index.ts.

gemini-nano-banana/server/tools/utils/gemini.ts (1)

108-112: Guard against empty or missing OpenRouter choices array.

If OpenRouter responds with an empty or undefined choices array (which can occur on provider-side errors), data.choices[0] at line 109 will be undefined, causing a TypeError at line 111 when attempting to access choices.message. Add a defensive check before dereferencing.

Apply this diff to add the safety check:

   const data = (await response.json()) as OpenRouterResponse;
-  const choices = data.choices[0];
+  const firstChoice = data.choices?.[0];
+  if (!firstChoice) {
+    throw new Error("OpenRouter response did not include any choices");
+  }
 
-  const image = choices.message.images?.[0]?.image_url?.url;
-  const nativeFinishReason = choices.native_finish_reason;
+  const image = firstChoice.message.images?.[0]?.image_url?.url;
+  const nativeFinishReason = firstChoice.native_finish_reason;
🧹 Nitpick comments (2)
gemini-nano-banana/README.md (1)

11-250: Consider Portuguese equivalents for English terms (optional localization improvement).

The README is comprehensive and clear. For consistency with the Portuguese-language documentation, consider translating these English terms:

  • "Aspect Ratios" (lines 12, 24, 94) → "Razões de Aspecto" or "Rácios de Imagem"
  • "server" (line 166) → "servidor"
  • "Router" (line 250) → "Roteador"

These are stylistic improvements to enhance linguistic consistency and are not blockers for merge.

gemini-nano-banana/server/tools/gemini.ts (1)

28-59: Add optional chaining for consistency at line 58.

While the optional chaining at lines 36 and 42 correctly guards against missing candidates, line 58 accesses candidate.finishReason without the ? operator. Although the flow guarantees candidate exists at this point, using optional chaining throughout maintains consistency and defensive coding practices.

Apply this diff for consistency:

     return {
       image: url,
-      finishReason: candidate.finishReason,
+      finishReason: candidate?.finishReason,
     };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 64f843b and 5489d97.

📒 Files selected for processing (3)
  • gemini-nano-banana/README.md (1 hunks)
  • gemini-nano-banana/server/tools/gemini.ts (1 hunks)
  • gemini-nano-banana/server/tools/utils/gemini.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
gemini-nano-banana/server/tools/gemini.ts (3)
shared/image-generators/base.ts (3)
  • GenerateImageInput (61-61)
  • GenerateImageOutput (62-62)
  • createImageGeneratorTool (111-125)
shared/image-generators/storage.ts (2)
  • extractImageData (150-161)
  • saveImageToFileSystem (78-142)
shared/image-generators/middleware.ts (3)
  • withContractManagement (177-209)
  • withRetry (22-66)
  • withLogging (84-103)
gemini-nano-banana/server/tools/utils/gemini.ts (2)
gemini-nano-banana/server/main.ts (1)
  • Env (35-62)
gemini-nano-banana/server/constants.ts (1)
  • OPENROUTER_BASE_URL (3-3)
🪛 LanguageTool
gemini-nano-banana/README.md

[uncategorized] ~11-~11: Empregue a forma composta, plural + singular, nesta expressão académica/científica.
Context: ...texto usando o modelo Gemini - Utilizem imagens base para modificações e variações - Persona...

(SUBSTANTIVO_PLURAL_E_CHAVE_BASE)


[locale-violation] ~12-~12: “ratios” é um estrangeirismo. É preferível dizer “razões” ou “rácios”.
Context: ...rsonalizem proporções de imagem (aspect ratios) - Armazenem e acessem imagens geradas ...

(PT_BARBARISMS_REPLACE_RATIOS)


[style] ~13-~13: Para conferir mais clareza ao seu texto, busque usar uma linguagem mais concisa.
Context: ...) - Armazenem e acessem imagens geradas através de um sistema de arquivos - Gerenciem auto...

(ATRAVES_DE_POR_VIA)


[uncategorized] ~23-~23: Empregue a forma composta, plural + singular, nesta expressão académica/científica.
Context: ...alvar imagens geradas - 🖼️ Suporte a Imagens Base: Modificação de imagens existentes - ...

(SUBSTANTIVO_PLURAL_E_CHAVE_BASE)


[locale-violation] ~24-~24: “Ratios” é um estrangeirismo. É preferível dizer “razões” ou “rácios”.
Context: ...ção de imagens existentes - 📐 Aspect Ratios Personalizáveis: Controle sobre propo...

(PT_BARBARISMS_REPLACE_RATIOS)


[inconsistency] ~63-~63: O URL contém o caratére inválido segundo RFC 1738. Os caratéres especiais podem ser codificados com % seguido de dois números hexadecimais. Context: ...ev ``` O servidor estará disponível emhttp://localhost:8787` (porta padrão do Cloudflare Workers). ...

(URL_VALIDATION)


[locale-violation] ~94-~94: “Ratio” é um estrangeirismo. É preferível dizer “razão” ou “rácio”.
Context: ...n: "STOP" } ### Gerando com Aspect Ratio Específico typescript const result ...

(PT_BARBARISMS_REPLACE_RATIO)


[uncategorized] ~155-~155: Pontuação duplicada
Context: ... autorização e pagamento para uso da API: - CONTRACT_AUTHORIZE: Autoriza uma transação antes da geração...

(DOUBLE_PUNCTUATION_XML)


[uncategorized] ~160-~160: Pontuação duplicada
Context: ...TEMSistema de armazenamento de imagens: -FS_READ: Lê arquivos do sistema de arquivos - F...

(DOUBLE_PUNCTUATION_XML)


[locale-violation] ~166-~166: “server” é um estrangeirismo. É preferível dizer “servidor”.
Context: ...ção. Configure os scopes necessários em server/main.ts: ```typescript oauth: { sco...

(PT_BARBARISMS_REPLACE_SERVER)


[locale-violation] ~250-~250: “Router” é um estrangeirismo. É preferível dizer “encaminhador” ou “roteador”.
Context: ...ailwindCSS 4 - Roteamento: TanStack Router - State Management: TanStack Query ...

(PT_BARBARISMS_REPLACE_ROUTER)

🪛 markdownlint-cli2 (0.18.1)
gemini-nano-banana/README.md

129-129: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (3)
gemini-nano-banana/README.md (3)

1-27: Previous placeholder concern resolved.

The README now contains comprehensive documentation addressing the prior review feedback. The project description, purpose, and features are well-organized and informative.


28-76: Setup instructions are clear and actionable.

Prerequisites and installation steps are well-documented with practical examples. Commands are properly formatted and easy to follow.


245-257: Technology stack is complete and well-documented.

The final sections provide a clear overview of the technology choices and licensing information.

- Deleted the index.html file and various unused assets from the public directory to streamline the project.
- Updated the project description in package.json to reflect its current purpose as an MCP server for image generation using Gemini 2.5 Flash Image Preview.
- Removed unnecessary dependencies and scripts from package.json to enhance project clarity and maintainability.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
gemini-nano-banana/README.md (1)

24-24: Consider Portuguese equivalents for consistency.

The README uses English terms "Aspect Ratios" in a Portuguese document. For linguistic consistency, consider replacing with "Rácios de Aspecto" or "Proporções de Imagem" (the latter is already used elsewhere in the document).

Also applies to: 99-99

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5489d97 and 130d6dd.

📒 Files selected for processing (3)
  • gemini-nano-banana/README.md (1 hunks)
  • gemini-nano-banana/package.json (1 hunks)
  • gemini-nano-banana/vite.config.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • gemini-nano-banana/package.json
🧰 Additional context used
🧬 Code graph analysis (1)
gemini-nano-banana/vite.config.ts (1)
shared/deco-vite-plugin.ts (1)
  • deco (89-146)
🪛 LanguageTool
gemini-nano-banana/README.md

[uncategorized] ~11-~11: Empregue a forma composta, plural + singular, nesta expressão académica/científica.
Context: ...texto usando o modelo Gemini - Utilizem imagens base para modificações e variações - Persona...

(SUBSTANTIVO_PLURAL_E_CHAVE_BASE)


[locale-violation] ~12-~12: “ratios” é um estrangeirismo. É preferível dizer “razões” ou “rácios”.
Context: ...rsonalizem proporções de imagem (aspect ratios) - Armazenem e acessem imagens geradas ...

(PT_BARBARISMS_REPLACE_RATIOS)


[style] ~13-~13: Para conferir mais clareza ao seu texto, busque usar uma linguagem mais concisa.
Context: ...) - Armazenem e acessem imagens geradas através de um sistema de arquivos - Gerenciem auto...

(ATRAVES_DE_POR_VIA)


[uncategorized] ~23-~23: Empregue a forma composta, plural + singular, nesta expressão académica/científica.
Context: ...alvar imagens geradas - 🖼️ Suporte a Imagens Base: Modificação de imagens existentes - ...

(SUBSTANTIVO_PLURAL_E_CHAVE_BASE)


[locale-violation] ~24-~24: “Ratios” é um estrangeirismo. É preferível dizer “razões” ou “rácios”.
Context: ...ção de imagens existentes - 📐 Aspect Ratios Personalizáveis: Controle sobre propo...

(PT_BARBARISMS_REPLACE_RATIOS)


[inconsistency] ~63-~63: O URL contém o caratére inválido segundo RFC 1738. Os caratéres especiais podem ser codificados com % seguido de dois números hexadecimais. Context: ...ev ``` O servidor estará disponível emhttp://localhost:8787` (porta padrão do Cloudflare Workers). ...

(URL_VALIDATION)


[locale-violation] ~94-~94: “Ratio” é um estrangeirismo. É preferível dizer “razão” ou “rácio”.
Context: ...n: "STOP" } ### Gerando com Aspect Ratio Específico typescript const result ...

(PT_BARBARISMS_REPLACE_RATIO)


[uncategorized] ~148-~148: Pontuação duplicada
Context: ... autorização e pagamento para uso da API: - CONTRACT_AUTHORIZE: Autoriza uma transação antes da geração...

(DOUBLE_PUNCTUATION_XML)


[uncategorized] ~153-~153: Pontuação duplicada
Context: ...TEMSistema de armazenamento de imagens: -FS_READ: Lê arquivos do sistema de arquivos - F...

(DOUBLE_PUNCTUATION_XML)


[locale-violation] ~159-~159: “server” é um estrangeirismo. É preferível dizer “servidor”.
Context: ...ção. Configure os scopes necessários em server/main.ts: ```typescript oauth: { sco...

(PT_BARBARISMS_REPLACE_SERVER)

🪛 markdownlint-cli2 (0.18.1)
gemini-nano-banana/README.md

129-129: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: deploy (object-storage)
🔇 Additional comments (5)
gemini-nano-banana/README.md (1)

1-26: Comprehensive README addressing previous placeholder feedback.

The documentation is well-structured with clear sections covering project description, features, setup/installation, usage examples, and configuration details. This effectively addresses the earlier feedback on placeholder content.

gemini-nano-banana/vite.config.ts (4)

1-3: LGTM! Clean import structure.

The imports are well-structured and all are utilized in the configuration.


5-5: LGTM! Good practice extracting the constant.

Extracting the environment name as a constant improves maintainability.


8-17: LGTM! Plugins configured correctly.

Both the Cloudflare and Deco plugins are properly configured. The Cloudflare plugin correctly references the wrangler.toml configuration, and the Deco plugin invocation is valid per the function signature.


19-25: LGTM! Standard Cloudflare Workers configuration.

The environment definitions are correctly configured for Cloudflare Workers, including the NODE_ENV fallback and the global-to-globalThis mapping for compatibility.

- Refactored `withContractManagement` to automatically include retry and logging functionalities, simplifying middleware composition.
- Updated README.md to reflect changes in middleware usage and provide clearer examples for implementing image generation with contract management.
- Adjusted image generation tool implementations to utilize the new middleware structure, improving code clarity and maintainability.
…atting

- Deleted .gitignore and .npmrc files as they are no longer needed.
- Reformatted the `withContractManagement` function in middleware.ts for improved readability.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
gemini-nano-banana/server/tools/gemini.ts (1)

75-75: Verify the geminiTools export is actually used.

A previous review flagged this export as unused dead code and marked it as addressed in commit 5489d97, but the export is still present. If it remains unused, it should be removed.

Run this script to confirm whether geminiTools is imported anywhere:

#!/bin/bash
# Search for imports of geminiTools across the codebase
rg -n 'geminiTools' --type ts
shared/image-generators/middleware.ts (1)

218-234: Add error handling to prevent unsettled transactions.

If fn(input, env) throws after authorization (Line 224), the settlement step (Line 227) never executes, leaving the transaction in an authorized but unsettled state. This can cause billing inconsistencies or resource locks.

Apply this diff to wrap execution and settlement in error handling:

   // Authorize
   const { transactionId } = await contract.CONTRACT_AUTHORIZE({
     clauses: [{ clauseId, amount: 1 }],
   });
 
-  // Execute
-  const result = await fn(input, env);
-
-  // Settle
-  await contract.CONTRACT_SETTLE({
-    transactionId,
-    clauses: [{ clauseId, amount: 1 }],
-    vendorId: env.DECO_CHAT_WORKSPACE,
-  });
-
-  return result;
+  try {
+    // Execute
+    const result = await fn(input, env);
+
+    // Settle on success
+    await contract.CONTRACT_SETTLE({
+      transactionId,
+      clauses: [{ clauseId, amount: 1 }],
+      vendorId: env.DECO_CHAT_WORKSPACE,
+    });
+
+    return result;
+  } catch (error) {
+    // Consider adding CONTRACT_ROLLBACK to release reserved resources
+    // For now, at minimum log the unsettled transaction
+    console.error(`[Contract] Transaction ${transactionId} failed to settle after error:`, error);
+    throw error;
+  }
🧹 Nitpick comments (1)
shared/README.md (1)

1-316: Comprehensive documentation; minor Portuguese style and punctuation refinements suggested.

The README provides clear, well-structured documentation for the shared package API and is a valuable resource. The examples are detailed and idiomatic. To enhance consistency and readability, consider these refinements:

  1. Abbreviations: "etc" should be "etc." (lines 32, 87, 250, 302)
  2. Repetitive sentence beginnings: Lines 108–116 consecutively start with "Adiciona". Vary the phrasing (e.g., "O withTimeout previne..." or "Implementa timeout para...").
  3. English/Portuguese mix: Line 3 uses "servers" instead of the Portuguese preference "servidores". Lines 82 and 101 use English terms ("ratio", "performance") where Portuguese alternatives ("razão/rácio", "desempenho") may be preferred for consistency.
  4. Terminology clarity: Line 138 flags "Salva" (should be verified in context, but the verb form appears correct; confirm if intent is imperative or narrative).

These are minor style preferences and do not affect the technical accuracy or usefulness of the documentation.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 130d6dd and 9996417.

📒 Files selected for processing (5)
  • gemini-nano-banana/README.md (1 hunks)
  • gemini-nano-banana/server/tools/gemini.ts (1 hunks)
  • shared/README.md (1 hunks)
  • shared/image-generators/base.ts (1 hunks)
  • shared/image-generators/middleware.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • shared/image-generators/base.ts
🧰 Additional context used
🧬 Code graph analysis (1)
gemini-nano-banana/server/tools/gemini.ts (5)
gemini-nano-banana/server/tools/utils/gemini.ts (2)
  • generateImage (138-167)
  • createGeminiClient (169-172)
gemini-nano-banana/server/main.ts (1)
  • Env (35-62)
shared/image-generators/base.ts (3)
  • GenerateImageInput (61-61)
  • GenerateImageOutput (62-62)
  • createImageGeneratorTool (109-123)
shared/image-generators/storage.ts (2)
  • extractImageData (150-161)
  • saveImageToFileSystem (78-142)
shared/image-generators/middleware.ts (1)
  • withContractManagement (192-237)
🪛 GitHub Actions: Checks
shared/image-generators/middleware.ts

[error] 1-1: Format issues found in 1 files. Run 'oxfmt' without --check to fix. Command failed: bun run fmt --check (oxfmt --check).

🪛 LanguageTool
shared/README.md

[locale-violation] ~3-~3: “servers” é um estrangeirismo. É preferível dizer “servidores”.
Context: ...para criar MCPs (Model Context Protocol servers) na plataforma Deco. ## Módulos Dispon...

(PT_BARBARISMS_REPLACE_SERVERS)


[uncategorized] ~32-~32: Se é uma abreviatura, falta um ponto. Se for uma expressão, coloque entre aspas.
Context: ..., DALL-E, Midjourney, Stable Diffusion, etc). #### Uso Básico ```typescript impor...

(ABREVIATIONS_PUNCTUATION)


[locale-violation] ~82-~82: “ratio” é um estrangeirismo. É preferível dizer “razão” ou “rácio”.
Context: ...aspectRatio (enum, opcional) - Aspect ratio: "1:1", "2:3", "3:2", "3:4", "4:3", "4:...

(PT_BARBARISMS_REPLACE_RATIO)


[uncategorized] ~87-~87: Se é uma abreviatura, falta um ponto. Se for uma expressão, coloque entre aspas.
Context: ...o término (sucesso, filtro de conteúdo, etc) #### Middlewares Disponíveis ##### `...

(ABREVIATIONS_PUNCTUATION)


[locale-violation] ~101-~101: “performance” é um estrangeirismo. É preferível dizer “desempenho”, “atuação”, “apresentação”, “espetáculo” ou “interpretação”.
Context: ...fn, providerName)` Adiciona logging de performance e erros. ```typescript const loggedGen...

(PT_BARBARISMS_REPLACE_PERFORMANCE)


[style] ~108-~108: Três frases seguidas começam com a mesma palavra. Considere reformular a frase ou use um dicionário para encontrar um sinônimo.
Context: ...`` ##### withTimeout(fn, timeoutMs) Adiciona timeout para prevenir execuções muito l...

(PORTUGUESE_WORD_REPEAT_BEGINNING_RULE)


[style] ~116-~116: Três frases seguidas começam com a mesma palavra. Considere reformular a frase ou use um dicionário para encontrar um sinônimo.
Context: ... withContractManagement(fn, options) Adiciona autorização e settlement de contratos p...

(PORTUGUESE_WORD_REPEAT_BEGINNING_RULE)


[misspelling] ~138-~138: Quando escrita sem acento, esta palavra é um verbo. Se pretende referir-se a um substantivo ou adjetivo, deve utilizar a forma acentuada.
Context: ..., options)` Salva uma imagem base64 no file system. ```typescript const { url, pat...

(DIACRITICS)


[grammar] ~249-~249: Possível erro de concordância de número.
Context: ...e input/output 3. Manutenibilidade: Bug fixes e melhorias beneficiam todos os MCPs au...

(GENERAL_NUMBER_AGREEMENT_ERRORS)


[uncategorized] ~250-~250: Se é uma abreviatura, falta um ponto. Se for uma expressão, coloque entre aspas.
Context: ...ar novos providers (DALL-E, Midjourney, etc) 5. Type Safety: TypeScript garante...

(ABREVIATIONS_PUNCTUATION)


[uncategorized] ~302-~302: Se é uma abreviatura, falta um ponto. Se for uma expressão, coloque entre aspas.
Context: ...shared/image-generators/2. Adicione o export em/shared/package.json`: ```json { ...

(ABREVIATIONS_PUNCTUATION)


[locale-violation] ~302-~302: “package” é um estrangeirismo. É preferível dizer “embalagem” ou “pacote”.
Context: ...ge-generators/2. Adicione o export em/shared/package.json`: ```json { "exports": { "....

(PT_BARBARISMS_REPLACE_PACKAGE)

gemini-nano-banana/README.md

[uncategorized] ~11-~11: Empregue a forma composta, plural + singular, nesta expressão académica/científica.
Context: ...texto usando o modelo Gemini - Utilizem imagens base para modificações e variações - Persona...

(SUBSTANTIVO_PLURAL_E_CHAVE_BASE)


[locale-violation] ~12-~12: “ratios” é um estrangeirismo. É preferível dizer “razões” ou “rácios”.
Context: ...rsonalizem proporções de imagem (aspect ratios) - Armazenem e acessem imagens geradas ...

(PT_BARBARISMS_REPLACE_RATIOS)


[style] ~13-~13: Para conferir mais clareza ao seu texto, busque usar uma linguagem mais concisa.
Context: ...) - Armazenem e acessem imagens geradas através de um sistema de arquivos - Gerenciem auto...

(ATRAVES_DE_POR_VIA)


[uncategorized] ~23-~23: Empregue a forma composta, plural + singular, nesta expressão académica/científica.
Context: ...alvar imagens geradas - 🖼️ Suporte a Imagens Base: Modificação de imagens existentes - ...

(SUBSTANTIVO_PLURAL_E_CHAVE_BASE)


[locale-violation] ~24-~24: “Ratios” é um estrangeirismo. É preferível dizer “razões” ou “rácios”.
Context: ...ção de imagens existentes - 📐 Aspect Ratios Personalizáveis: Controle sobre propo...

(PT_BARBARISMS_REPLACE_RATIOS)


[inconsistency] ~63-~63: O URL contém o caratére inválido segundo RFC 1738. Os caratéres especiais podem ser codificados com % seguido de dois números hexadecimais. Context: ...ev ``` O servidor estará disponível emhttp://localhost:8787` (porta padrão do Cloudflare Workers). ...

(URL_VALIDATION)


[locale-violation] ~94-~94: “Ratio” é um estrangeirismo. É preferível dizer “razão” ou “rácio”.
Context: ...n: "STOP" } ### Gerando com Aspect Ratio Específico typescript const result ...

(PT_BARBARISMS_REPLACE_RATIO)


[uncategorized] ~148-~148: Pontuação duplicada
Context: ... autorização e pagamento para uso da API: - CONTRACT_AUTHORIZE: Autoriza uma transação antes da geração...

(DOUBLE_PUNCTUATION_XML)


[uncategorized] ~153-~153: Pontuação duplicada
Context: ...TEMSistema de armazenamento de imagens: -FS_READ: Lê arquivos do sistema de arquivos - F...

(DOUBLE_PUNCTUATION_XML)


[locale-violation] ~159-~159: “server” é um estrangeirismo. É preferível dizer “servidor”.
Context: ...ção. Configure os scopes necessários em server/main.ts: ```typescript oauth: { sco...

(PT_BARBARISMS_REPLACE_SERVER)

🪛 markdownlint-cli2 (0.18.1)
gemini-nano-banana/README.md

129-129: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

- Introduced a new shared storage module with support for various storage providers, including S3 and file system.
- Added `saveImage` function to streamline image saving across different storage backends.
- Implemented `ObjectStorage` interface and adapters for S3 and file system, allowing flexible storage integration.
- Updated image generation tools to utilize the new storage capabilities, improving modularity and maintainability.
- Added comprehensive README documentation for the storage module and its usage.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

♻️ Duplicate comments (1)
shared/image-generators/middleware.ts (1)

119-140: Critical: Contract settlement error handling still missing.

This issue was previously flagged but remains unaddressed. If fn(input, env) (Line 131) throws after authorization (Line 127), the settlement step (Lines 133-137) never executes, leaving transactions in an authorized but unsettled state. This can lock resources and cause billing inconsistencies.

Apply the previously suggested fix:

     const { transactionId } = await contract.CONTRACT_AUTHORIZE({
       clauses: [{ clauseId, amount: 1 }],
     });

-    const result = await fn(input, env);
-
-    await contract.CONTRACT_SETTLE({
-      transactionId,
-      clauses: [{ clauseId, amount: 1 }],
-      vendorId: env.DECO_CHAT_WORKSPACE,
-    });
-
-    return result;
+    try {
+      const result = await fn(input, env);
+
+      await contract.CONTRACT_SETTLE({
+        transactionId,
+        clauses: [{ clauseId, amount: 1 }],
+        vendorId: env.DECO_CHAT_WORKSPACE,
+      });
+
+      return result;
+    } catch (error) {
+      // TODO: Add CONTRACT_ROLLBACK or similar mechanism
+      // to release reserved resources
+      throw error;
+    }
🧹 Nitpick comments (4)
shared/image-generators/middleware.ts (1)

19-27: Fragile error detection pattern for HTTP status codes.

Checking error.message.includes("400") is unreliable because:

  1. Error messages may not contain status codes as strings
  2. The string "400" could appear in unrelated error messages
  3. Different HTTP libraries structure errors differently

Consider a more robust approach:

         if (error instanceof z.ZodError) {
           throw error;
         }

-        if (
-          error instanceof Error &&
-          (error.message.includes("400") ||
-            error.message.includes("401") ||
-            error.message.includes("403") ||
-            error.message.includes("404"))
-        ) {
-          throw error;
-        }
+        // Check for HTTP client errors more robustly
+        if (error && typeof error === "object") {
+          const status = (error as any).status || (error as any).statusCode;
+          if (status && status >= 400 && status < 500) {
+            throw error;
+          }
+        }
shared/storage/adapters/supabase.ts (1)

71-118: Consider adding input validation for the uploadFile method.

The uploadFile method doesn't validate inputs like path format or data presence. Adding validation would make failures more explicit and easier to debug.

Consider adding validation:

   async uploadFile(
     path: string,
     data: Buffer | Uint8Array | Blob,
     options?: {
       contentType?: string;
       cacheControl?: string;
       upsert?: boolean;
     },
   ): Promise<{ url: string; path: string }> {
+    if (!path || path.trim() === "") {
+      throw new Error("Path cannot be empty");
+    }
+    if (!data) {
+      throw new Error("Data cannot be empty");
+    }
+
     const formData = new FormData();
shared/storage/adapters/s3.ts (2)

31-40: Constructor overloading weakens type safety.

The constructor uses runtime type checking with if (bucketName) to distinguish between two different signatures. This pattern makes it difficult for TypeScript to validate usage at compile time and can lead to subtle bugs if the second parameter is accidentally passed as undefined or an empty string.

Consider providing two separate factory methods instead:

- constructor(configOrClient: S3Config | any, bucketName?: string) {
-   if (bucketName) {
-     this.s3Client = configOrClient;
-     this.bucketName = bucketName;
-   } else {
-     const config = configOrClient as S3Config;
-     this.s3Client = createS3Client(config);
-     this.bucketName = config.bucketName;
-   }
- }
+ constructor(config: S3Config) {
+   this.s3Client = createS3Client(config);
+   this.bucketName = config.bucketName;
+ }
+
+ static fromClient(client: any, bucketName: string): S3StorageAdapter {
+   const adapter = Object.create(S3StorageAdapter.prototype);
+   adapter.s3Client = client;
+   adapter.bucketName = bucketName;
+   return adapter;
+ }

93-102: Multiple non-null assertions assume AWS SDK always returns required fields.

Lines 95-98 use non-null assertion operators (!) which will throw runtime errors if the AWS SDK response omits any of these fields. While the SDK typically provides these fields, defensive coding would handle potential omissions more gracefully.

Consider adding validation or using optional chaining:

     return {
       objects: (response.Contents || []).map((obj: any) => ({
-        key: obj.Key!,
-        size: obj.Size!,
-        lastModified: obj.LastModified!,
-        etag: obj.ETag!,
+        key: obj.Key ?? "",
+        size: obj.Size ?? 0,
+        lastModified: obj.LastModified ?? new Date(),
+        etag: obj.ETag ?? "",
       })),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98d077b and 95cce62.

📒 Files selected for processing (14)
  • shared/image-generators/README.md (1 hunks)
  • shared/image-generators/base.ts (1 hunks)
  • shared/image-generators/index.ts (1 hunks)
  • shared/image-generators/middleware.ts (1 hunks)
  • shared/image-generators/storage.ts (1 hunks)
  • shared/package.json (1 hunks)
  • shared/storage/README.md (1 hunks)
  • shared/storage/adapters/filesystem.ts (1 hunks)
  • shared/storage/adapters/s3.ts (1 hunks)
  • shared/storage/adapters/supabase.ts (1 hunks)
  • shared/storage/factory.ts (1 hunks)
  • shared/storage/index.ts (1 hunks)
  • shared/storage/interface.ts (1 hunks)
  • shared/storage/types.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • shared/image-generators/base.ts
🧰 Additional context used
🧬 Code graph analysis (6)
shared/storage/adapters/filesystem.ts (2)
shared/storage/interface.ts (1)
  • ObjectStorage (1-11)
shared/storage/types.ts (1)
  • FileSystemBinding (15-26)
shared/storage/adapters/supabase.ts (1)
shared/storage/interface.ts (1)
  • ObjectStorage (1-11)
shared/storage/interface.ts (1)
shared/image-generators/storage.ts (2)
  • ObjectStorage (3-3)
  • ExtendedObjectStorage (4-4)
shared/storage/adapters/s3.ts (2)
shared/storage/types.ts (1)
  • S3Config (1-9)
shared/storage/interface.ts (1)
  • ExtendedObjectStorage (13-43)
shared/storage/factory.ts (4)
shared/storage/adapters/s3.ts (1)
  • S3StorageAdapter (27-155)
shared/storage/types.ts (2)
  • S3Config (1-9)
  • FileSystemBinding (15-26)
shared/storage/adapters/filesystem.ts (1)
  • FileSystemStorageAdapter (3-25)
shared/storage/interface.ts (1)
  • ObjectStorage (1-11)
shared/image-generators/storage.ts (2)
shared/storage/interface.ts (1)
  • ObjectStorage (1-11)
shared/storage/adapters/filesystem.ts (1)
  • FileSystemStorageAdapter (3-25)
🪛 GitHub Actions: Checks
shared/storage/index.ts

[error] 1-1: Formatting issues found by oxfmt. Run 'oxfmt' to fix code style issues in this file.

shared/storage/adapters/filesystem.ts

[error] 1-1: Formatting issues found by oxfmt. Run 'oxfmt' to fix code style issues in this file.

shared/storage/types.ts

[error] 1-1: Formatting issues found by oxfmt. Run 'oxfmt' to fix code style issues in this file.

shared/storage/adapters/supabase.ts

[error] 1-1: Formatting issues found by oxfmt. Run 'oxfmt' to fix code style issues in this file.

shared/storage/interface.ts

[error] 1-1: Formatting issues found by oxfmt. Run 'oxfmt' to fix code style issues in this file.

shared/storage/adapters/s3.ts

[error] 1-1: Formatting issues found by oxfmt. Run 'oxfmt' to fix code style issues in this file.

shared/storage/factory.ts

[error] 1-1: Formatting issues found by oxfmt. Run 'oxfmt' to fix code style issues in this file.

shared/image-generators/storage.ts

[error] 1-1: Formatting issues found by oxfmt. Run 'oxfmt' to fix code style issues in this file.

🪛 LanguageTool
shared/image-generators/README.md

[uncategorized] ~44-~44: Encontrada possível ausência de vírgula.
Context: ...ra qualquer provedor compatível com S3 (AWS S3, R2, MinIO, etc.): ```typescript im...

(AI_PT_HYDRA_LEO_MISSING_COMMA)

shared/storage/README.md

[typographical] ~8-~8: Símbolo sem par: “]” aparentemente está ausente
Context: ...Não quer ler tudo? Comece aqui:* - 📖 **[Quick Start - 3 passos para salvar image...

(UNPAIRED_BRACKETS)


[typographical] ~9-~9: Símbolo sem par: “]” aparentemente está ausente
Context: ...salvar imagens](./QUICKSTART.md)** - 📖 **[Todos os provedores disponíveis (Supabas...

(UNPAIRED_BRACKETS)


[typographical] ~10-~10: Símbolo sem par: “]” aparentemente está ausente
Context: ...ase, R2, S3...)](./PROVIDERS.md)** - 📖 **[Guia completo do Supabase](./SUPABASE_GU...

(UNPAIRED_BRACKETS)


[uncategorized] ~16-~16: Encontrada possível ausência de vírgula.
Context: ...e usar qualquer provedor (Supabase, AWS S3, Cloudflare R2, MinIO, etc.) e troca...

(AI_PT_HYDRA_LEO_MISSING_COMMA)


[uncategorized] ~16-~16: Encontrada possível ausência de vírgula.
Context: ...qualquer provedor* (Supabase, AWS S3, Cloudflare R2, MinIO, etc.) e trocar entre eles mu...

(AI_PT_HYDRA_LEO_MISSING_COMMA)


[uncategorized] ~38-~38: Encontrada possível ausência de vírgula.
Context: ...### Adapters 1. S3StorageAdapter - AWS S3, R2, MinIO, etc. 2. **FileSystemStor...

(AI_PT_HYDRA_LEO_MISSING_COMMA)


[misspelling] ~46-~46: Esta é uma palavra só.
Context: ...ile system - createStorageFromEnv() - Auto-detecta e cria storage apropriado ## 🚀 Uso Bá...

(AUTO)


[misspelling] ~88-~88: Esta é uma palavra só.
Context: ...ReadUrl("/file.png", 3600); ### 4. Auto-detecção typescript import { createStorageFr...

(AUTO)


[misspelling] ~134-~134: Possível erro ortográfico.
Context: ...ey, expiresIn); ``` ### Exemplo 2: MCP gemini-nano-banana (image generator) Antes: `...

(PT_MULTITOKEN_SPELLING_HYPHEN)


[misspelling] ~303-~303: Possível erro ortográfico.
Context: ...tituir lógica custom por adapters - ✅ gemini-nano-banana - Já usa via image-generators ...

(PT_MULTITOKEN_SPELLING_HYPHEN)


[typographical] ~317-~317: Símbolo sem par: “]” aparentemente está ausente
Context: ...Qualquer provedor S3-compatible 📖 **[Guia completo do Supabase](./SUPABASE_GU...

(UNPAIRED_BRACKETS)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: deploy (object-storage)
🔇 Additional comments (15)
shared/storage/types.ts (1)

1-26: LGTM! Well-defined storage type definitions.

The type definitions are clear, complete, and properly structured:

  • S3Config covers all necessary S3-compatible storage configuration
  • FileSystemBinding provides a clean contract for filesystem operations
  • FileSystemUrlResponse is appropriately minimal

These types align well with the storage adapter implementations and provide good type safety.

shared/image-generators/middleware.ts (3)

44-63: LGTM! Clean logging middleware.

The logging implementation is straightforward and provides useful operational visibility with start/completion times and error tracking.


64-78: LGTM! Standard timeout implementation.

The timeout pattern using Promise.race is correct and widely used. The implementation appropriately rejects with a clear timeout error message.


80-94: LGTM! Well-structured contract interfaces.

The contract-related type definitions provide a clear contract for authorization and settlement workflows with appropriate input/output types.

shared/storage/index.ts (1)

1-6: LGTM! Clean barrel export pattern.

The centralized exports provide a convenient single entry point for the storage module, following standard TypeScript/Node.js patterns.

shared/image-generators/index.ts (1)

1-3: LGTM! Standard barrel export.

Clean re-export pattern providing a unified entry point for image generator utilities.

shared/storage/README.md (1)

1-360: LGTM! Comprehensive and well-structured documentation.

The README provides excellent coverage:

  • Clear quick start guides with links to detailed docs
  • Comprehensive interface and adapter documentation
  • Multiple usage patterns (MCP state, direct S3, FILE_SYSTEM, auto-detection)
  • Before/after migration examples
  • Advanced operations and testing guidance

The LanguageTool warnings are false positives related to markdown formatting and Portuguese language rules that don't apply here.

shared/storage/adapters/filesystem.ts (1)

3-25: LGTM! Clean adapter implementation.

The FileSystemStorageAdapter correctly implements the ObjectStorage interface by delegating to the FileSystemBinding. The implementation is straightforward and properly structured with:

  • Clean constructor injection of dependencies
  • Proper async/await patterns
  • Appropriate destructuring of response objects
shared/package.json (2)

7-13: LGTM! Proper export mappings.

The exports map correctly exposes the new modules:

  • Tools (user tools and index)
  • Image generators
  • Storage utilities
  • Vite plugin

This provides a clean public API for consumers of the shared package.


14-18: LGTM! Appropriate devDependencies.

The dependency versions are current and properly specified:

  • vite at 7.2.0 (exact version for build consistency)
  • @deco/workers-runtime at 0.23.2 via JSR
  • zod at ^3.24.3 (allowing patch updates)
shared/storage/interface.ts (2)

1-11: LGTM! Well-designed core interface.

The ObjectStorage interface provides a clean, minimal contract for storage operations with:

  • Symmetric read/write URL generation
  • Proper expiration handling
  • Optional metadata and content type for writes

13-43: LGTM! Thoughtful extended interface design.

The ExtendedObjectStorage interface appropriately extends the core interface with advanced operations as optional methods. This allows:

  • Simple implementations to only provide read/write URLs
  • Full-featured implementations to support listing, metadata, and deletion
  • Flexible adapter patterns

The return types are comprehensive and include proper pagination support (continuationToken, isTruncated) and error handling (errors array in deleteObjects).

shared/image-generators/storage.ts (2)

66-73: Good use of Promise.all for concurrent operations.

Fetching read and write URLs in parallel reduces latency. This is an efficient approach.


75-86: This review comment is incorrect.

The concern about Buffer not being available is unfounded for this codebase. All worker deployments in the repository (object-storage, template-minimal, template-with-view) have compatibility_flags = [ "nodejs_compat" ] in their wrangler.toml. This Cloudflare Workers configuration explicitly enables Node.js API compatibility, including Buffer polyfills. Additionally, saveImage() is only used in server-side contexts (gemini-nano-banana/server/tools), not in browsers or edge environments without nodejs_compat. The code is correct as-is.

Likely an incorrect or invalid review comment.

shared/storage/adapters/supabase.ts (1)

41-70: The review comment is incorrect and should be disregarded.

Supabase's uploadToSignedUrl accepts contentType as a client-side upload option (not a signing parameter), and does not accept custom metadata. The code correctly sends only expiresIn to the /object/upload/sign endpoint. The contentType and metadata parameters in the method signature are not applicable to presigned upload signing—they either serve as unused parameters or documentation. Sending them to the signing endpoint would be incorrect according to Supabase's API design.

Likely an incorrect or invalid review comment.


## Exemplos Completos

Veja [EXAMPLES.md](./EXAMPLES.md) para exemplos detalhados de uso com diferentes provedores.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify the referenced file exists.

The documentation references EXAMPLES.md but this file may not exist in the repository.


🏁 Script executed:

#!/bin/bash
# Check if EXAMPLES.md exists in the shared/image-generators directory
fd -t f "EXAMPLES.md" shared/image-generators/

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for EXAMPLES.md anywhere in the repository
echo "=== Searching for EXAMPLES.md in entire repository ==="
fd -t f "EXAMPLES.md"

echo ""
echo "=== Contents of shared/image-generators directory ==="
fd -t f . shared/image-generators/ | head -20

Length of output: 281


Remove the broken reference to EXAMPLES.md.

The file EXAMPLES.md does not exist in the shared/image-generators/ directory. Either create the file, remove this reference, or update it to point to an existing documentation file.

🤖 Prompt for AI Agents
In shared/image-generators/README.md at line 290, the README links to a
non-existent EXAMPLES.md; fix this by either creating
shared/image-generators/EXAMPLES.md with the referenced examples, removing the
sentence/link, or updating the link to point to an existing documentation file
(e.g., change to ./USAGE.md or ../EXAMPLES.md if those exist); ensure the
updated link path is valid in the repository and run a quick link check to
confirm it resolves.

…neration and storage modules

- Reformatted code in base.ts, storage.ts, and various storage adapter files for consistency and clarity.
- Removed unnecessary line breaks and standardized object property formatting.
- Enhanced type assertions in Supabase storage adapter for better type safety.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (8)
shared/storage/adapters/supabase.ts (1)

1-123: Address the formatting issue flagged by the pipeline.

The pipeline indicates formatting issues found by oxfmt. Run oxfmt to fix code style issues.

shared/storage/factory.ts (2)

49-57: Remove unnecessary type assertion.

The as any cast bypasses TypeScript's type checking. After the conditional checks verify the required properties exist, explicitly construct the state object to preserve type safety.

Apply this diff:

   const state = env.DECO_CHAT_REQUEST_CONTEXT?.state;
   if (
     state?.region &&
     state?.accessKeyId &&
     state?.secretAccessKey &&
     state?.bucketName
   ) {
-    return createStorageFromState(state as any);
+    return createStorageFromState({
+      region: state.region,
+      accessKeyId: state.accessKeyId,
+      secretAccessKey: state.secretAccessKey,
+      endpoint: state.endpoint,
+      bucketName: state.bucketName,
+      defaultPresignedUrlExpiration: state.defaultPresignedUrlExpiration,
+    });
   }

1-62: Address the formatting issue flagged by the pipeline.

The pipeline indicates formatting issues found by oxfmt. Run oxfmt to fix code style issues.

shared/storage/adapters/s3.ts (5)

3-26: Replace require() with dynamic import() — ESM-only mode breaks on require().

The project uses "type": "module" making it ESM-only. The require() call on line 5 will fail at runtime with "require is not defined in ES module scope."

Make createS3Client async and replace require() with dynamic import():

-export function createS3Client(config: S3Config): any {
+export async function createS3Client(config: S3Config): Promise<any> {
   try {
-    const { S3Client } = require("@aws-sdk/client-s3");
+    const { S3Client } = await import("@aws-sdk/client-s3");

     const clientConfig: any = {
       region: config.region,
       credentials: {
         accessKeyId: config.accessKeyId,
         secretAccessKey: config.secretAccessKey,
       },
     };

     if (config.endpoint) {
       clientConfig.endpoint = config.endpoint;
       clientConfig.forcePathStyle = config.forcePathStyle ?? true;
     }

     return new S3Client(clientConfig);
   } catch {
     throw new Error(
       "S3StorageAdapter requires @aws-sdk/client-s3. Install it with: npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner",
     );
   }
 }

Note: The constructor calling createS3Client (line 37) will need to be updated to handle the async call.


1-155: Address the formatting issue flagged by the pipeline.

The pipeline indicates formatting issues found by oxfmt. Run oxfmt to fix code style issues.


42-52: Replace require() with dynamic import() in method.

The require() calls on lines 43-44 will fail in ESM-only mode.

Replace with:

 async getReadUrl(path: string, expiresIn: number): Promise<string> {
-  const { GetObjectCommand } = require("@aws-sdk/client-s3");
-  const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
+  const { GetObjectCommand } = await import("@aws-sdk/client-s3");
+  const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");

   const command = new GetObjectCommand({
     Bucket: this.bucketName,
     Key: path,
   });

   return getSignedUrl(this.s3Client, command, { expiresIn });
 }

54-75: Replace require() with dynamic import() in method.

The require() calls on lines 62-63 will fail in ESM-only mode.

Replace with:

 async getWriteUrl(
   path: string,
   options: {
     contentType?: string;
     metadata?: Record<string, string>;
     expiresIn: number;
   },
 ): Promise<string> {
-  const { PutObjectCommand } = require("@aws-sdk/client-s3");
-  const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
+  const { PutObjectCommand } = await import("@aws-sdk/client-s3");
+  const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");

   const command = new PutObjectCommand({
     Bucket: this.bucketName,
     Key: path,
     ContentType: options.contentType,
     Metadata: options.metadata,
   });

   return getSignedUrl(this.s3Client, command, {
     expiresIn: options.expiresIn,
   });
 }

77-154: Replace all require() calls with dynamic import() in these methods.

The require() calls on lines 82, 106, 125, and 136 will fail in ESM-only mode.

Replace each occurrence:

 async listObjects(...) {
-  const { ListObjectsV2Command } = require("@aws-sdk/client-s3");
+  const { ListObjectsV2Command } = await import("@aws-sdk/client-s3");
   // ...
 }

 async getMetadata(key: string) {
-  const { HeadObjectCommand } = require("@aws-sdk/client-s3");
+  const { HeadObjectCommand } = await import("@aws-sdk/client-s3");
   // ...
 }

 async deleteObject(key: string): Promise<void> {
-  const { DeleteObjectCommand } = require("@aws-sdk/client-s3");
+  const { DeleteObjectCommand } = await import("@aws-sdk/client-s3");
   // ...
 }

 async deleteObjects(keys: string[]) {
-  const { DeleteObjectsCommand } = require("@aws-sdk/client-s3");
+  const { DeleteObjectsCommand } = await import("@aws-sdk/client-s3");
   // ...
 }
🧹 Nitpick comments (3)
shared/image-generators/base.ts (3)

22-28: Consider simplifying the baseImageUrl field definition.

The field is defined as both .nullable() and .optional(), which allows three states: absent, explicitly null, or a string value. Unless there's a specific requirement to distinguish between "absent" and "explicitly null", consider using just .optional() for simplicity:

  baseImageUrl: z
    .string()
-   .nullable()
    .optional()
    .describe(
      "URL of an existing image to use as base (image-to-image generation)",
    ),

34-38: Consider strengthening the output schema contract.

All fields are optional, which permits returning an empty object and makes the success/error contract unclear. Consider using a discriminated union to make success and error cases explicit:

export const GenerateImageOutputSchema = z.discriminatedUnion("status", [
  z.object({
    status: z.literal("success"),
    image: z.string().describe("URL of the generated image"),
  }),
  z.object({
    status: z.literal("error"),
    finishReason: z.string().describe("Error reason"),
  }),
]);

This ensures consumers can rely on the presence of specific fields based on the outcome.


44-45: Consider typing the ensureAuthenticated return value.

The method currently returns any, which reduces type safety. If the authentication context returns a specific type (e.g., user object or JWT claims), consider typing it explicitly for better IntelliSense and compile-time checks.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 95cce62 and 05315b5.

📒 Files selected for processing (9)
  • shared/image-generators/base.ts (1 hunks)
  • shared/image-generators/storage.ts (1 hunks)
  • shared/storage/adapters/filesystem.ts (1 hunks)
  • shared/storage/adapters/s3.ts (1 hunks)
  • shared/storage/adapters/supabase.ts (1 hunks)
  • shared/storage/factory.ts (1 hunks)
  • shared/storage/index.ts (1 hunks)
  • shared/storage/interface.ts (1 hunks)
  • shared/storage/types.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • shared/storage/index.ts
  • shared/storage/types.ts
  • shared/storage/interface.ts
  • shared/image-generators/storage.ts
🧰 Additional context used
🧬 Code graph analysis (5)
shared/storage/adapters/filesystem.ts (2)
shared/storage/interface.ts (1)
  • ObjectStorage (1-11)
shared/storage/types.ts (1)
  • FileSystemBinding (15-26)
shared/image-generators/base.ts (2)
object-storage/shared/deco.gen.ts (1)
  • Env (453-512)
template-with-view/shared/deco.gen.ts (2)
  • DECO_CHAT_VIEWS_LISTInput (62-62)
  • Env (154-181)
shared/storage/adapters/supabase.ts (3)
shared/storage/interface.ts (1)
  • ObjectStorage (1-11)
object-storage/view/src/hooks/useStorage.ts (3)
  • params (118-143)
  • useUploadFile (113-149)
  • usePutPresignedUrl (57-74)
object-storage/server/tools/storage.ts (4)
  • obj (306-306)
  • ctx (114-133)
  • ctx (66-90)
  • ctx (208-228)
shared/storage/adapters/s3.ts (2)
shared/storage/types.ts (1)
  • S3Config (1-9)
shared/storage/interface.ts (1)
  • ExtendedObjectStorage (13-43)
shared/storage/factory.ts (4)
shared/storage/adapters/s3.ts (1)
  • S3StorageAdapter (27-155)
shared/storage/types.ts (2)
  • S3Config (1-9)
  • FileSystemBinding (15-26)
shared/storage/adapters/filesystem.ts (1)
  • FileSystemStorageAdapter (3-25)
shared/storage/interface.ts (1)
  • ObjectStorage (1-11)
🔇 Additional comments (6)
shared/image-generators/base.ts (1)

58-72: Well-structured factory function.

The implementation correctly wires provider-specific execution into the standardized tool contract. Note that the hardcoded tool ID "GENERATE_IMAGE" means only one image generator tool can be active per system. If your design requires multiple concurrent image generators (e.g., different providers), consider making the ID configurable via options.

shared/storage/adapters/filesystem.ts (1)

3-24: LGTM!

Clean delegation pattern correctly implementing the ObjectStorage interface by forwarding to the underlying FileSystemBinding.

shared/storage/factory.ts (3)

5-21: LGTM!

Clear and explicit mapping of state properties to S3StorageAdapter configuration.


23-25: LGTM!

Simple factory function for S3 storage creation.


27-31: LGTM!

Simple factory function for filesystem storage creation.

shared/storage/adapters/supabase.ts (1)

41-70: No action required — the implementation is correct.

The method correctly excludes contentType and metadata from the signing request. Supabase's createSignedUploadUrl obtains a signed URL, while contentType and metadata are set in the actual upload request that follows. These options are accepted in the method signature to conform to the ObjectStorage interface, but they are appropriately not used during the signing phase—callers set them when performing the actual upload after obtaining the signed URL.

Likely an incorrect or invalid review comment.

Comment on lines 31 to 40
constructor(configOrClient: S3Config | any, bucketName?: string) {
if (bucketName) {
this.s3Client = configOrClient;
this.bucketName = bucketName;
} else {
const config = configOrClient as S3Config;
this.s3Client = createS3Client(config);
this.bucketName = config.bucketName;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Constructor cannot call async createS3Client — consider lazy initialization or factory pattern.

If createS3Client becomes async (as required for ESM compatibility), the constructor cannot call it directly since constructors cannot be async.

Consider one of these approaches:

Option 1: Lazy initialization (recommended)

private s3Client: any | null = null;
private config?: S3Config;

constructor(configOrClient: S3Config | any, bucketName?: string) {
  if (bucketName) {
    this.s3Client = configOrClient;
    this.bucketName = bucketName;
  } else {
    this.config = configOrClient as S3Config;
    this.bucketName = this.config.bucketName;
  }
}

private async ensureClient() {
  if (!this.s3Client && this.config) {
    this.s3Client = await createS3Client(this.config);
  }
}

async getReadUrl(path: string, expiresIn: number): Promise<string> {
  await this.ensureClient();
  // ... rest of method
}

Option 2: Static async factory method

static async create(config: S3Config): Promise<S3StorageAdapter> {
  const client = await createS3Client(config);
  return new S3StorageAdapter(client, config.bucketName);
}
🤖 Prompt for AI Agents
shared/storage/adapters/s3.ts lines 31-40: the constructor currently calls
createS3Client synchronously which will break if createS3Client becomes async;
change to lazy initialization by making s3Client nullable and storing the
S3Config on construction when a config is passed, set bucketName from the
config, and remove the direct call to createS3Client; add a private async
ensureClient method that awaits createS3Client using the stored config if
s3Client is null, and call ensureClient at the top of all public async methods
that use s3Client (e.g., getReadUrl, upload, delete) so the client is created on
first use; alternatively, if you prefer explicit creation, implement a static
async factory method that awaits createS3Client and returns a fully initialized
adapter instance.

- Removed the unused views file to simplify the project structure.
- Updated image generation logic to utilize the new storage module, replacing the deprecated saveImageToFileSystem function with saveImage.
- Enhanced the main entry point documentation for clarity.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
gemini-nano-banana/server/main.ts (1)

1-100: Well-structured main entry point.

The overall structure follows Cloudflare Workers best practices with clear separation of concerns: type definitions, runtime configuration, and exports. The extensive comments provide good context for developers.

Consider adding JSDoc comments to the ContractClause interface fields to clarify what clauseId represents and the unit/currency for amount (e.g., cents? dollars? contract-specific units?).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 05315b5 and 0a8fc0f.

📒 Files selected for processing (7)
  • gemini-nano-banana/server/main.ts (1 hunks)
  • gemini-nano-banana/server/tools/gemini.ts (1 hunks)
  • shared/image-generators/storage.ts (1 hunks)
  • template-minimal/README.md (1 hunks)
  • template-minimal/server/main.ts (1 hunks)
  • template-minimal/server/views.ts (0 hunks)
  • template-minimal/wrangler.toml (1 hunks)
💤 Files with no reviewable changes (1)
  • template-minimal/server/views.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • gemini-nano-banana/server/tools/gemini.ts
🧰 Additional context used
🧬 Code graph analysis (2)
shared/image-generators/storage.ts (1)
shared/storage/interface.ts (1)
  • ObjectStorage (1-11)
gemini-nano-banana/server/main.ts (2)
template-minimal/server/main.ts (1)
  • Env (18-23)
gemini-nano-banana/shared/deco.gen.ts (2)
  • Env (201-234)
  • StateSchema (199-199)
🔇 Additional comments (8)
template-minimal/README.md (1)

1-1: Clarify the intent: is this README intentionally minimal or incomplete?

The file contains only a heading with no body content. Given the PR consolidates templates and redirects to shared implementations, this minimal structure may be intentional. However, it's worth confirming whether this is a deliberate scaffold or if additional content (e.g., description, setup instructions, usage examples) is expected.

template-minimal/wrangler.toml (1)

2-2: Workspace name properly aligned with minimal template identity.

The rename from "mcp-template-with-view" to "mcp-template-minimal" is verified as safe. The old name correctly remains in the separate template-with-view template. No dangling references or breaking changes.

template-minimal/server/main.ts (2)

1-5: Documentation update aligns with views removal.

The comment accurately reflects that the MCP server endpoint is at /mcp. This aligns with the removal of the views configuration.


25-61: Clean and minimal runtime configuration.

The simplified configuration without views keeps the template minimal and focused. The fallback to ASSETS for unmatched requests is appropriate.

shared/image-generators/storage.ts (3)

1-13: LGTM! Clean re-export pattern.

The re-exports consolidate storage primitives effectively, and the local import of ObjectStorage is appropriate for internal use within this file.


15-47: LGTM! Well-defined interfaces.

The interface definitions are clear and comprehensive. The deprecated notice on FileSystemUrlResponse provides proper migration guidance.


100-111: LGTM! Simple and effective utility.

The extractImageData function provides sensible defaults and clear normalization of inline image data.

gemini-nano-banana/server/main.ts (1)

62-98: LGTM! Clean runtime configuration.

The runtime setup is well-structured with clear comments explaining oauth scopes and state schema. The empty scopes array and StateSchema are consistent with the generated types and appear intentional for the current implementation stage.

Comment on lines +62 to +64
const extension = mimeType.split("/")[1] || "png";
const name = fileName || new Date().toISOString().replace(/[:.]/g, "-");
const path = `${directory}/${name}.${extension}`;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Consider adding randomness to prevent filename collisions.

The timestamp-based filename could result in collisions if multiple images are saved within the same millisecond. Consider appending a random suffix or UUID fragment to ensure uniqueness.

Apply this diff to add randomness:

-  const name = fileName || new Date().toISOString().replace(/[:.]/g, "-");
+  const name = fileName || `${new Date().toISOString().replace(/[:.]/g, "-")}-${Math.random().toString(36).substring(2, 9)}`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const extension = mimeType.split("/")[1] || "png";
const name = fileName || new Date().toISOString().replace(/[:.]/g, "-");
const path = `${directory}/${name}.${extension}`;
const extension = mimeType.split("/")[1] || "png";
const name = fileName || `${new Date().toISOString().replace(/[:.]/g, "-")}-${Math.random().toString(36).substring(2, 9)}`;
const path = `${directory}/${name}.${extension}`;
🤖 Prompt for AI Agents
In shared/image-generators/storage.ts around lines 62 to 64, the current
filename uses an ISO timestamp which can collide when multiple files are created
in the same millisecond; update the name generation to append a short random
suffix or UUID fragment (for example using crypto.randomUUID() or a hex slice
from crypto.randomBytes) so the final path becomes
`${directory}/${name}-${random}.${extension}`; ensure you keep the existing
sanitization of the name and fall back to "png" for unknown mime types, and use
a short deterministic slice (e.g., first 8 chars) to keep filenames readable.

Comment on lines +75 to +86
const base64Data = imageData.includes(",")
? imageData.split(",")[1]
: imageData;
const imageBuffer = Buffer.from(base64Data, "base64");

const uploadResponse = await fetch(writeUrl, {
method: "PUT",
headers: {
"Content-Type": mimeType,
},
body: imageBuffer,
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Consider validating base64 data before upload.

The code extracts and converts base64 data without validation. If imageData contains invalid base64, Buffer.from() may produce corrupted data or throw an error, leading to unclear failure modes.

Consider adding basic validation:

  const base64Data = imageData.includes(",")
    ? imageData.split(",")[1]
    : imageData;
+  
+  // Validate base64 format
+  if (!/^[A-Za-z0-9+/]*={0,2}$/.test(base64Data)) {
+    throw new Error("Invalid base64 image data");
+  }
+  
  const imageBuffer = Buffer.from(base64Data, "base64");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const base64Data = imageData.includes(",")
? imageData.split(",")[1]
: imageData;
const imageBuffer = Buffer.from(base64Data, "base64");
const uploadResponse = await fetch(writeUrl, {
method: "PUT",
headers: {
"Content-Type": mimeType,
},
body: imageBuffer,
});
const base64Data = imageData.includes(",")
? imageData.split(",")[1]
: imageData;
// Validate base64 format
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(base64Data)) {
throw new Error("Invalid base64 image data");
}
const imageBuffer = Buffer.from(base64Data, "base64");
const uploadResponse = await fetch(writeUrl, {
method: "PUT",
headers: {
"Content-Type": mimeType,
},
body: imageBuffer,
});

…ge generation capabilities

- Renamed project from "gemini-nano-banana" to "nanobanana" for clarity.
- Updated wrangler configuration to reflect new project name and scope.
- Introduced a new .gitignore file to exclude development variables.
- Refactored image generation tools to improve modularity and integrate with the new storage system.
- Removed deprecated interfaces and streamlined the environment type definitions for better maintainability.
- Enhanced the storage module to support file system operations and improved image saving logic.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
shared/image-generators/storage.ts (2)

32-34: Consider adding randomness to prevent filename collisions.

The timestamp-based filename could result in collisions if multiple images are saved within the same millisecond in high-throughput scenarios.

Apply this diff to add a random suffix:

-  const name = fileName || new Date().toISOString().replace(/[:.]/g, "-");
+  const name = fileName || `${new Date().toISOString().replace(/[:.]/g, "-")}-${crypto.randomUUID().slice(0, 8)}`;

46-49: Consider validating base64 data before processing.

The code extracts and converts base64 data without validation. Invalid base64 could produce corrupted data or unclear error messages.

Consider adding basic validation:

  const base64Data = imageData.includes(",")
    ? imageData.split(",")[1]
    : imageData;
+  
+  // Validate base64 format
+  if (!/^[A-Za-z0-9+/]*={0,2}$/.test(base64Data)) {
+    throw new Error("Invalid base64 image data");
+  }
+  
  const imageBuffer = Buffer.from(base64Data, "base64");
gemini-nano-banana/shared/deco.gen.ts (1)

23-23: Fix empty interfaces at the generator level.

Static analysis correctly identifies that several interfaces (lines 23, 108, 215, 326, 394) are empty and should be type aliases instead. Since this is auto-generated code, update the source JSON schema or generator configuration to emit:

export type FS_DELETEOutput = Record<string, never>;
export type FS_READ_METADATAOutput = Record<string, never>;
export type DECO_CHAT_VIEWS_LISTInput = Record<string, never>;
export type GET_USERInput = Record<string, never>;
export type CONTRACT_GETInput = Record<string, never>;

Based on static analysis hints.

shared/image-generators/base.ts (1)

73-78: Consider typing ensureAuthenticated more specifically.

The ensureAuthenticated method returns any, which loses type safety. If the authentication context has a known shape, consider defining a more specific return type.

For example, if it returns user information:

export interface ImageGeneratorEnv {
  DECO_REQUEST_CONTEXT: {
    ensureAuthenticated: () => { userId: string; [key: string]: unknown };
  };
  DECO_CHAT_WORKSPACE: string;
}
shared/storage/adapters/filesystem.ts (1)

87-95: Consider tracking individual deletion failures in deleteObjects.

The current implementation uses Promise.all and always returns an empty errors array. If any individual deletion fails, the entire operation will throw. Consider using Promise.allSettled to track individual failures.

Apply this diff for granular error tracking:

  async deleteObjects(options: { keys: string[] }): Promise<{
    deleted: string[];
    errors: Array<{ key: string; message: string }>;
  }> {
-    await Promise.all(
-      options.keys.map((key) => this.fileSystem.FS_DELETE({ path: key })),
-    );
-    return { deleted: options.keys, errors: [] };
+    const results = await Promise.allSettled(
+      options.keys.map((key) => 
+        this.fileSystem.FS_DELETE({ path: key }).then(() => key)
+      ),
+    );
+    
+    const deleted: string[] = [];
+    const errors: Array<{ key: string; message: string }> = [];
+    
+    results.forEach((result, index) => {
+      if (result.status === 'fulfilled') {
+        deleted.push(result.value);
+      } else {
+        errors.push({
+          key: options.keys[index],
+          message: result.reason?.message || 'Deletion failed',
+        });
+      }
+    });
+    
+    return { deleted, errors };
  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a8fc0f and 2583f9a.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • gemini-nano-banana/.gitignore (1 hunks)
  • gemini-nano-banana/server/main.ts (1 hunks)
  • gemini-nano-banana/server/tools/gemini.ts (1 hunks)
  • gemini-nano-banana/shared/deco.gen.ts (1 hunks)
  • gemini-nano-banana/wrangler.toml (1 hunks)
  • shared/image-generators/base.ts (1 hunks)
  • shared/image-generators/storage.ts (1 hunks)
  • shared/storage/adapters/filesystem.ts (1 hunks)
  • shared/storage/factory.ts (1 hunks)
  • shared/storage/index.ts (1 hunks)
  • shared/storage/interface.ts (1 hunks)
  • shared/storage/types.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • gemini-nano-banana/.gitignore
🚧 Files skipped from review as they are similar to previous changes (5)
  • shared/storage/index.ts
  • shared/storage/factory.ts
  • gemini-nano-banana/server/main.ts
  • gemini-nano-banana/server/tools/gemini.ts
  • gemini-nano-banana/wrangler.toml
🧰 Additional context used
🧬 Code graph analysis (4)
shared/image-generators/base.ts (2)
shared/storage/interface.ts (1)
  • ObjectStorage (1-43)
shared/image-generators/storage.ts (1)
  • saveImage (18-69)
shared/image-generators/storage.ts (1)
shared/storage/interface.ts (1)
  • ObjectStorage (1-43)
shared/storage/adapters/filesystem.ts (2)
shared/storage/interface.ts (1)
  • ObjectStorage (1-43)
shared/storage/types.ts (1)
  • FileSystemBinding (15-31)
gemini-nano-banana/shared/deco.gen.ts (1)
gemini-nano-banana/server/main.ts (1)
  • Env (18-23)
🪛 Biome (2.1.2)
gemini-nano-banana/shared/deco.gen.ts

[error] 23-23: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)


[error] 108-108: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)


[error] 215-215: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)


[error] 326-326: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)


[error] 394-394: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)

🔇 Additional comments (5)
shared/image-generators/storage.ts (1)

1-69: LGTM! Clean storage abstraction.

The implementation provides a well-structured image persistence layer using the ObjectStorage abstraction. The parallel presigned URL generation, base64 handling, and upload flow are correctly implemented.

shared/storage/types.ts (1)

1-31: LGTM! Well-defined storage type contracts.

The interfaces provide clear type contracts for S3 configuration, filesystem operations, and URL responses. The FileSystemBinding interface properly defines all necessary filesystem operations with appropriate input/output types.

shared/storage/interface.ts (1)

1-43: LGTM! Comprehensive object storage interface.

The ObjectStorage interface provides a well-designed abstraction with all necessary operations for presigned URLs, listing, metadata retrieval, and deletion. The optional methods allow for flexible implementations while the core methods (createPresignedReadUrl, createPresignedPutUrl) are required.

shared/image-generators/base.ts (1)

1-127: LGTM! Well-structured image generator tooling.

The module provides a clean abstraction for image generation with proper validation (Zod schemas), error handling, storage integration, and tool creation. The flow from input validation through provider execution to storage is well-organized.

shared/storage/adapters/filesystem.ts (1)

1-102: LGTM! Solid adapter implementation.

The FileSystemStorageAdapter correctly implements the ObjectStorage interface, providing proper delegation to FileSystemBinding operations with sensible defaults and type conversions where needed.

- Updated image generation tools to utilize a new metadata structure for provider and description.
- Introduced a contract management system within the image generation process, allowing for better handling of contract clauses and authorization.
- Refactored middleware functions to support a more streamlined approach with improved retry and logging capabilities.
- Removed deprecated file system storage factory and adjusted related storage types for consistency.
- Enhanced overall modularity and maintainability of the image generation and storage systems.
…oject

- Renamed project from "gemini-nano-banana" to "nanobanana" for clarity and consistency.
- Updated package.json and bun.lock to reflect the new project name and dependencies.
- Removed obsolete files and configurations related to the previous project structure, including .gitignore, README.md, and TypeScript configuration files.
- Introduced new files and configurations for the nanobanana project, including a fresh README.md and updated server structure for image generation.
- Enhanced image generation tools and integrated new constants for API endpoints.
- Streamlined the overall project structure for improved maintainability and clarity.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
gemini-nano-banana/server/tools/gemini.ts (1)

46-47: Return the expected callback shape explicitly.

inlineData happens to contain { data, mimeType }, but handing the raw API object back ties us to Google’s response shape and bypasses the GenerateImageCallbackOutput contract. Please project the fields we actually use so future tweaks to the Gemini payload don’t break the save pipeline.

-    return inlineData;
+    return {
+      data: inlineData.data,
+      mimeType: inlineData.mimeType,
+    };
shared/image-generators/middleware.ts (1)

38-40: Preserve the original error on final failure.

Wrapping the last failure in a brand-new Error drops stack/type information and makes debugging harder. Prefer rethrowing lastError (or using it as the cause) so callers keep the actual failure context.

-      throw new Error(
-        `Failed after ${maxRetries} attempts: ${lastError?.message}`,
-      );
+      if (lastError) {
+        throw lastError;
+      }
+      throw new Error(`Failed after ${maxRetries} attempts`);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2583f9a and f490b62.

📒 Files selected for processing (5)
  • gemini-nano-banana/server/tools/gemini.ts (1 hunks)
  • shared/image-generators/base.ts (1 hunks)
  • shared/image-generators/middleware.ts (1 hunks)
  • shared/storage/index.ts (1 hunks)
  • shared/storage/types.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • shared/storage/index.ts
🧰 Additional context used
🧬 Code graph analysis (2)
gemini-nano-banana/server/tools/gemini.ts (5)
shared/image-generators/base.ts (1)
  • createImageGeneratorTools (110-183)
gemini-nano-banana/server/main.ts (1)
  • Env (18-23)
gemini-nano-banana/shared/deco.gen.ts (1)
  • Env (526-607)
shared/storage/adapters/filesystem.ts (1)
  • adaptFileSystemBindingToObjectStorage (98-102)
gemini-nano-banana/server/tools/utils/gemini.ts (1)
  • createGeminiClient (169-172)
shared/image-generators/base.ts (3)
shared/storage/interface.ts (1)
  • ObjectStorage (1-43)
shared/image-generators/middleware.ts (6)
  • Contract (97-106)
  • ContractClause (92-95)
  • applyMiddlewares (82-90)
  • withLogging (45-65)
  • withRetry (3-43)
  • withTimeout (67-80)
shared/image-generators/storage.ts (1)
  • saveImage (18-69)
🔇 Additional comments (1)
shared/storage/types.ts (1)

1-17: Binding surface looks consistent.

The declared methods and inputs line up with what the storage adapter expects, so no issues spotted here.

Comment on lines +125 to +159
const { transactionId } = await contract.binding.CONTRACT_AUTHORIZE({
clauses: [
{
clauseId: contract.clause.clauseId,
amount: contract.clause.amount,
},
],
});

const result = await options.execute({ env, input: context });

if ("error" in result) {
return {
error: true,
finishReason: result.finishReason,
};
}

const storage = options.getStorage(env);
const saveImageResult = await saveImage(storage, {
imageData: result.data,
mimeType: result.mimeType || "image/png",
metadata: { prompt: context.prompt },
});

await contract.binding.CONTRACT_SETTLE({
transactionId,
clauses: [
{
clauseId: contract.clause.clauseId,
amount: contract.clause.amount,
},
],
vendorId: env.DECO_CHAT_WORKSPACE,
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Release contract authorizations on all exit paths.

We authorize the clause, but if options.execute throws or returns { error: true }, we return before calling CONTRACT_SETTLE. That leaves the transaction in an authorized-but-unsettled state, effectively locking funds until an eventual timeout. Please add a settle/cancel path (e.g., wrap in try/catch/finally) so the authorization is always released, even when generation fails.

🤖 Prompt for AI Agents
In shared/image-generators/base.ts around lines 125–159, the code authorizes a
contract but returns early on execution errors (or if execute throws), never
calling CONTRACT_SETTLE; wrap the authorize/execute/save flow in
try/catch/finally so CONTRACT_SETTLE is always invoked with the original
transactionId, clauses, and vendorId in the finally block (use the same params
as the successful path), log any settle errors without overwriting the original
error, and rethrow or return the original error result as before so
authorization is always released on all exit paths.

viktormarinho and others added 3 commits November 10, 2025 12:32
…directories

- Added exclusion rules for `@deco/workers-runtime` and related directories in tsconfig.json files across multiple projects to prevent unnecessary type checking and improve build performance.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (2)
nanobanana/vite.config.ts (1)

27-28: Remove misleading comment or correct it.

The comment "Clear cache more aggressively" is misleading because setting cacheDir to "node_modules/.vite" is Vite's default behavior and doesn't change caching aggressiveness.

Consider either removing these lines entirely if they're redundant:

-  // Clear cache more aggressively
-  cacheDir: "node_modules/.vite",

Or updating the comment to be accurate:

-  // Clear cache more aggressively
+  // Explicitly configure Vite's default cache directory for clarity
   cacheDir: "node_modules/.vite",
nanobanana/README.md (1)

129-129: Code fence language specifier missing.

This issue was already flagged in a previous review. The file-structure code block needs a language identifier to resolve the markdownlint validation error.

🧹 Nitpick comments (3)
package.json (1)

22-22: LGTM!

The nanobanana workspace is correctly added to enable monorepo package management.

Consider alphabetically ordering the workspaces array for consistency (nanobanana should come after object-storage).

nanobanana/server/tools/gemini.ts (1)

24-24: Redundant logical OR with undefined.

The expression input.baseImageUrl || undefined is unnecessary since baseImageUrl is already optional and will be undefined when not provided.

Apply this diff:

-      input.baseImageUrl || undefined,
+      input.baseImageUrl,
nanobanana/server/tools/utils/gemini.ts (1)

86-86: Type the request body parameter.

The body parameter uses any, which bypasses type checking. Consider creating a proper type or interface for the request body to improve type safety.

+interface OpenRouterRequestBody {
+  model: string;
+  messages: Array<{
+    role: string;
+    content: Array<{
+      type: string;
+      text?: string;
+      image_url?: { url: string };
+    }>;
+  }>;
+  modalities: string[];
+  image_config?: {
+    aspect_ratio: string;
+  };
+}
+
 async function makeOpenrouterRequest(
   env: Env,
   endpoint: string,
-  body: any,
+  body: OpenRouterRequestBody,
 ): Promise<GeminiResponse> {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f490b62 and c647943.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • nanobanana/.gitignore (1 hunks)
  • nanobanana/README.md (1 hunks)
  • nanobanana/package.json (1 hunks)
  • nanobanana/server/constants.ts (1 hunks)
  • nanobanana/server/main.ts (1 hunks)
  • nanobanana/server/tools/gemini.ts (1 hunks)
  • nanobanana/server/tools/index.ts (1 hunks)
  • nanobanana/server/tools/utils/gemini.ts (1 hunks)
  • nanobanana/shared/deco.gen.ts (1 hunks)
  • nanobanana/tsconfig.json (1 hunks)
  • nanobanana/vite.config.ts (1 hunks)
  • nanobanana/wrangler.toml (1 hunks)
  • object-storage/tsconfig.json (1 hunks)
  • package.json (1 hunks)
  • template-minimal/tsconfig.json (1 hunks)
  • template-with-view/tsconfig.json (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • nanobanana/.gitignore
🧰 Additional context used
🧬 Code graph analysis (6)
nanobanana/server/tools/index.ts (1)
nanobanana/server/tools/gemini.ts (1)
  • geminiTools (6-48)
nanobanana/server/main.ts (1)
nanobanana/shared/deco.gen.ts (2)
  • Env (526-607)
  • StateSchema (513-524)
nanobanana/server/tools/utils/gemini.ts (3)
nanobanana/server/main.ts (1)
  • Env (18-23)
nanobanana/shared/deco.gen.ts (1)
  • Env (526-607)
nanobanana/server/constants.ts (1)
  • OPENROUTER_BASE_URL (3-3)
nanobanana/server/tools/gemini.ts (5)
shared/image-generators/base.ts (1)
  • createImageGeneratorTools (110-183)
nanobanana/server/main.ts (1)
  • Env (18-23)
nanobanana/shared/deco.gen.ts (1)
  • Env (526-607)
shared/storage/adapters/filesystem.ts (1)
  • adaptFileSystemBindingToObjectStorage (98-102)
nanobanana/server/tools/utils/gemini.ts (1)
  • createGeminiClient (169-172)
nanobanana/vite.config.ts (1)
shared/deco-vite-plugin.ts (1)
  • deco (89-146)
nanobanana/shared/deco.gen.ts (1)
nanobanana/server/main.ts (1)
  • Env (18-23)
🪛 Biome (2.1.2)
nanobanana/shared/deco.gen.ts

[error] 23-23: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)


[error] 108-108: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)


[error] 215-215: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)


[error] 326-326: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)


[error] 394-394: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)

🪛 LanguageTool
nanobanana/README.md

[uncategorized] ~11-~11: Empregue a forma composta, plural + singular, nesta expressão académica/científica.
Context: ...texto usando o modelo Gemini - Utilizem imagens base para modificações e variações - Persona...

(SUBSTANTIVO_PLURAL_E_CHAVE_BASE)


[locale-violation] ~12-~12: “ratios” é um estrangeirismo. É preferível dizer “razões” ou “rácios”.
Context: ...rsonalizem proporções de imagem (aspect ratios) - Armazenem e acessem imagens geradas ...

(PT_BARBARISMS_REPLACE_RATIOS)


[style] ~13-~13: Para conferir mais clareza ao seu texto, busque usar uma linguagem mais concisa.
Context: ...) - Armazenem e acessem imagens geradas através de um sistema de arquivos - Gerenciem auto...

(ATRAVES_DE_POR_VIA)


[uncategorized] ~23-~23: Empregue a forma composta, plural + singular, nesta expressão académica/científica.
Context: ...alvar imagens geradas - 🖼️ Suporte a Imagens Base: Modificação de imagens existentes - ...

(SUBSTANTIVO_PLURAL_E_CHAVE_BASE)


[locale-violation] ~24-~24: “Ratios” é um estrangeirismo. É preferível dizer “razões” ou “rácios”.
Context: ...ção de imagens existentes - 📐 Aspect Ratios Personalizáveis: Controle sobre propo...

(PT_BARBARISMS_REPLACE_RATIOS)


[inconsistency] ~63-~63: O URL contém o caratére inválido segundo RFC 1738. Os caratéres especiais podem ser codificados com % seguido de dois números hexadecimais. Context: ...ev ``` O servidor estará disponível emhttp://localhost:8787` (porta padrão do Cloudflare Workers). ...

(URL_VALIDATION)


[locale-violation] ~94-~94: “Ratio” é um estrangeirismo. É preferível dizer “razão” ou “rácio”.
Context: ...n: "STOP" } ### Gerando com Aspect Ratio Específico typescript const result ...

(PT_BARBARISMS_REPLACE_RATIO)


[uncategorized] ~148-~148: Pontuação duplicada
Context: ... autorização e pagamento para uso da API: - CONTRACT_AUTHORIZE: Autoriza uma transação antes da geração...

(DOUBLE_PUNCTUATION_XML)


[uncategorized] ~153-~153: Pontuação duplicada
Context: ...TEMSistema de armazenamento de imagens: -FS_READ: Lê arquivos do sistema de arquivos - F...

(DOUBLE_PUNCTUATION_XML)


[locale-violation] ~159-~159: “server” é um estrangeirismo. É preferível dizer “servidor”.
Context: ...ção. Configure os scopes necessários em server/main.ts: ```typescript oauth: { sco...

(PT_BARBARISMS_REPLACE_SERVER)

🪛 markdownlint-cli2 (0.18.1)
nanobanana/README.md

129-129: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (9)
template-minimal/tsconfig.json (1)

46-51: Exclude pattern matches the monorepo pattern—consistent across workspaces.

Identical exclude entries for Deco workers runtime, maintaining consistency with object-storage and template-with-view tsconfigs. This coordinated approach to scoping TypeScript project boundaries is appropriate.

template-with-view/tsconfig.json (1)

48-53: Exclude pattern maintains monorepo‑wide consistency.

The identical exclude entries across all three workspace tsconfigs (object-storage, template-minimal, template-with-view) confirm a coordinated effort to exclude Deco workers runtime from the TypeScript project scope. This approach is sound and reduces type-checking overhead.

object-storage/tsconfig.json (1)

48-53: Exclude pattern for Deco workers runtime is correct and consistently applied across monorepo.

The @deco/workers-runtime dependency (JSR package mapped to npm) is properly excluded from TypeScript type-checking across all workspace packages. The pattern correctly covers both the npm alias and Bun cache variants, with dual-level paths (node_modules/ and ../node_modules/) appropriate for monorepo resolution. Verification confirms this is consistent with template-minimal, template-with-view, and nanobanana workspaces.

package.json (1)

18-18: LGTM!

The clean script appropriately removes common build artifacts and dependency directories across the monorepo workspaces.

nanobanana/package.json (1)

1-34: LGTM!

The package manifest is well-structured with appropriate dependencies, scripts, and engine requirements for a Deco MCP project targeting Cloudflare Workers.

nanobanana/server/constants.ts (1)

1-3: LGTM!

The API endpoint constants are cleanly defined and will facilitate configuration management for Gemini and OpenRouter integrations.

nanobanana/server/tools/index.ts (1)

1-16: LGTM!

The tool aggregation pattern is clean and well-documented, with good separation of concerns across domains.

nanobanana/wrangler.toml (1)

1-39: LGTM!

The Wrangler configuration is well-structured with appropriate bindings for contract-based pricing and file-system integration, aligning with the Deco MCP architecture.

nanobanana/server/main.ts (1)

1-63: LGTM!

The MCP server entry point is well-structured with clear type definitions, comprehensive documentation, and appropriate runtime configuration for OAuth, state management, and tool integration.

};
}

return inlineData;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify property name consistency for return type.

The code returns inlineData which has properties {data: string, mime_type: string} (as defined in the Zod schema at nanobanana/server/tools/utils/gemini.ts lines 15-20). However, based on the shared image generator interface in shared/image-generators/base.ts, the expected return type is {data: string, mimeType?: string} (note the camelCase mimeType).

Run this script to verify the expected return type from the base image generator:

If there is a mismatch, consider transforming the return value:

-    return inlineData;
+    return {
+      data: inlineData.data,
+      mimeType: inlineData.mime_type,
+    };

🏁 Script executed:

#!/bin/bash
# Check the expected return type for the execute function in the base image generator

# Search for the interface or type definition that defines what execute should return
ast-grep --pattern $'execute: async ({ env, input }: { env: $_, input: $_ }) => {
  $$$
}'

# Also check for explicit type definitions related to image generation results
rg -nP --type=ts -C5 'interface.*Result|type.*Result.*=.*\{.*data.*mimeType'

Length of output: 593


I need to gather more targeted information. The previous script output doesn't show the relevant interfaces. Let me verify the specific files mentioned in the review comment.


---

🏁 Script executed:

```shell
#!/bin/bash
# Check the base image generator interface
echo "=== Base Image Generator Interface ==="
cat shared/image-generators/base.ts 2>/dev/null || echo "File not found"

echo -e "\n=== Zod Schema in utils/gemini.ts ==="
cat nanobanana/server/tools/utils/gemini.ts 2>/dev/null | head -30 || echo "File not found"

echo -e "\n=== Return statement in gemini.ts line 46 context ==="
cat nanobanana/server/tools/gemini.ts 2>/dev/null | sed -n '40,50p' || echo "File not found"

Length of output: 5913


I need to see more context around line 46 to confirm how inlineData is assigned:


---

🏁 Script executed:

```shell
#!/bin/bash
# Get full context around line 46 in gemini.ts
echo "=== Full context around line 46 in nanobanana/server/tools/gemini.ts ==="
cat nanobanana/server/tools/gemini.ts 2>/dev/null | sed -n '1,70p'

Length of output: 1532


Transform property names from snake_case to camelCase before returning.

The code returns inlineData with properties {data: string, mime_type: string} (from the Gemini API response parsed via Zod schema), but the execute function must return GenerateImageCallbackOutputSuccess with properties {data: string, mimeType?: string} as defined in shared/image-generators/base.ts. Transform the return value at line 46:

-    return inlineData;
+    return {
+      data: inlineData.data,
+      mimeType: inlineData.mime_type,
+    };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return inlineData;
return {
data: inlineData.data,
mimeType: inlineData.mime_type,
};
🤖 Prompt for AI Agents
In nanobanana/server/tools/gemini.ts around line 46, the function currently
returns inlineData whose properties use snake_case ({data, mime_type}); change
the return to match GenerateImageCallbackOutputSuccess by returning an object
with data and mimeType (map mime_type -> mimeType and omit undefined values),
i.e., transform inlineData.mime_type to mimeType and return { data:
inlineData.data, mimeType: inlineData.mime_type ?? undefined } (or similar) so
the property names match the expected camelCase shape.

Comment on lines +108 to +109
const data = (await response.json()) as OpenRouterResponse;
const choices = data.choices[0];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add bounds check before array access.

The code directly accesses data.choices[0] without verifying the array is non-empty, which could cause a runtime error if the OpenRouter API returns an empty choices array.

Apply this diff:

   const data = (await response.json()) as OpenRouterResponse;
+  
+  if (!data.choices || data.choices.length === 0) {
+    throw new Error("OpenRouter API returned no choices");
+  }
+  
   const choices = data.choices[0];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const data = (await response.json()) as OpenRouterResponse;
const choices = data.choices[0];
const data = (await response.json()) as OpenRouterResponse;
if (!data.choices || data.choices.length === 0) {
throw new Error("OpenRouter API returned no choices");
}
const choices = data.choices[0];
🤖 Prompt for AI Agents
In nanobanana/server/tools/utils/gemini.ts around lines 108 to 109, the code
accesses data.choices[0] without verifying data.choices exists and has at least
one element; add a bounds check after parsing JSON to ensure data.choices is an
array and has length > 0, handle the empty case by throwing or returning a clear
error (or fallback) before accessing index 0, and update downstream logic to use
the validated choices value.

Comment on lines +118 to +134
return {
candidates: [
{
content: {
parts: [
{
inline_data: {
data: image,
mime_type: "image/png",
},
},
],
},
finishReason: nativeFinishReason,
},
],
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Semantic mismatch: URL stored in inline_data.data field.

The code stores an image URL (from line 111: choices.message.images?.[0]?.image_url?.url) directly in the inline_data.data field (line 125). According to standard Gemini API conventions, inline_data.data should contain base64-encoded binary data, not a URL reference.

This mismatch could cause issues downstream if consumers expect base64 data but receive a URL instead. The consuming code in nanobanana/server/tools/gemini.ts may need to handle this URL differently than base64 data.

Consider one of these approaches:

  1. Fetch and encode the image (if inline data is required):
const imageResponse = await fetch(image);
const imageBuffer = await imageResponse.arrayBuffer();
const base64Data = btoa(String.fromCharCode(...new Uint8Array(imageBuffer)));

return {
  candidates: [{
    content: {
      parts: [{
        inline_data: {
          data: base64Data,
          mime_type: "image/png",
        },
      }],
    },
    finishReason: nativeFinishReason,
  }],
};
  1. Return URL in a different structure (if the schema supports it):
return {
  candidates: [{
    content: {
      parts: [{
        imageUrl: image,  // Use a different field for URLs
      }],
    },
    finishReason: nativeFinishReason,
  }],
};

Verify which approach aligns with your architecture and the expected data format in the storage layer.

🤖 Prompt for AI Agents
In nanobanana/server/tools/gemini.ts around lines 118 to 134, the code places an
image URL into inline_data.data which must contain base64-encoded binary per
Gemini conventions; fix by either (A) fetching the URL, reading the bytes and
base64-encoding them and then set inline_data.data to that base64 string and
preserve mime_type, or (B) change the returned structure to expose the URL in a
separate field (e.g., image_url or url) instead of inline_data.data so consumers
expecting a URL can handle it; choose the approach consistent with downstream
storage/consumers and update any related typing and tests.

* and run json-schema-to-typescript to regenerate this file.
*/

export interface FS_DELETEOutput {}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix empty interfaces in the generation process.

Multiple empty interfaces (FS_DELETEOutput, FS_READ_METADATAOutput, DECO_CHAT_VIEWS_LISTInput, GET_USERInput, CONTRACT_GETInput) should be type aliases instead of interfaces per TypeScript best practices.

Since this is a generated file, the fix should be applied to the json-schema-to-typescript generation configuration or templates, not manually in this file.

Based on static analysis.

Also applies to: 108-108, 215-215, 326-326, 394-394

🧰 Tools
🪛 Biome (2.1.2)

[error] 23-23: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)

🤖 Prompt for AI Agents
In nanobanana/shared/deco.gen.ts around lines 23, 108, 215, 326 and 394, several
empty interface declarations (e.g. FS_DELETEOutput, FS_READ_METADATAOutput,
DECO_CHAT_VIEWS_LISTInput, GET_USERInput, CONTRACT_GETInput) are generated as
interfaces but should be emitted as type aliases; update the
json-schema-to-typescript generation configuration or the generation template so
that when a schema resolves to an empty object it emits a type alias (for
example "export type NAME = {}") instead of "export interface NAME {}", and
regenerate the file — do not change the generated file manually.

Comment on lines 1 to 54
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"verbatimModuleSyntax": false,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
"allowJs": true,

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,

/* Path Aliases */
"baseUrl": ".",
"paths": {
"@/*": ["./view/src/*"],
"shared/*": ["./shared/*"],
"server/*": ["./server/*"],
"worker/*": ["./worker/*"],
"npm:@cloudflare/workers-types@*": ["node_modules/@cloudflare/workers-types"],
"npm:zod@*": ["node_modules/zod"],
"npm:@modelcontextprotocol/sdk@*/types.js": ["node_modules/@modelcontextprotocol/sdk/dist/esm/types.d.ts"],
"npm:@mastra/core@*": ["node_modules/@mastra/core"]
},

/* Types */
"types": ["vite/client", "@cloudflare/workers-types"]
},
"include": [
"view",
"server",
"shared",
"vite.config.ts"
],
"exclude": [
"node_modules/@deco/workers-runtime",
"node_modules/.bun/@jsr+deco__workers-runtime*",
"../node_modules/@deco/workers-runtime",
"../node_modules/.bun/@jsr+deco__workers-runtime*"
]
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Well-structured TypeScript configuration for Cloudflare Workers + Vite setup.

The configuration is solid and appropriately tuned for a monorepo workspace:

  • Target and module settings align with modern ES2022 + ESNext requirements.
  • Bundler mode with isolatedModules and noEmit is appropriate for Vite/Cloudflare Workers.
  • Strict linting (noUnusedLocals, noUnusedParameters, noUncheckedSideEffectImports) enforces good code quality discipline.
  • Path aliases are well-organized for cross-workspace imports (shared/*, server/*, worker/*, etc.).
  • Explicit types for Vite and Cloudflare Workers are correctly included.
  • Deliberate exclusion of @deco/workers-runtime prevents type conflicts.

To ensure the path aliases resolve correctly across shared modules and the bundler respects these mappings, run a quick type-check:

If you haven't already tested the path aliases end-to-end (e.g., importing from shared/tools or server/*), do so before merge to confirm Vite and TypeScript both honor these mappings correctly.


🏁 Script executed:

#!/bin/bash
# Verify TypeScript compilation succeeds with the new tsconfig
cd nanobanana && npx tsc --noEmit

Length of output: 626


🏁 Script executed:

#!/bin/bash
# Check if TypeScript is listed in nanobanana and root package.json
echo "=== Checking nanobanana/package.json for TypeScript ==="
if [ -f "nanobanana/package.json" ]; then
  jq '.devDependencies, .dependencies' nanobanana/package.json | grep -i typescript || echo "TypeScript not found in nanobanana/package.json"
else
  echo "nanobanana/package.json not found"
fi

echo ""
echo "=== Checking root package.json for TypeScript ==="
if [ -f "package.json" ]; then
  jq '.devDependencies, .dependencies' package.json | grep -i typescript || echo "TypeScript not found in root package.json"
else
  echo "root package.json not found"
fi

echo ""
echo "=== Listing files in nanobanana directory ==="
ls -la nanobanana/ | head -20

echo ""
echo "=== Checking if vite.config.ts exists in nanobanana ==="
find nanobanana -name "vite.config.ts" -o -name "package.json"

Length of output: 916


🏁 Script executed:

#!/bin/bash
# Validate tsconfig.json JSON syntax and verify referenced paths
echo "=== Validating tsconfig.json JSON syntax ==="
if jq empty nanobanana/tsconfig.json 2>&1; then
  echo "✓ tsconfig.json is valid JSON"
else
  echo "✗ tsconfig.json has JSON syntax errors"
  jq empty nanobanana/tsconfig.json 2>&1
fi

echo ""
echo "=== Checking included directories exist ==="
for dir in view server shared; do
  if [ -d "$dir" ]; then
    echo "$dir/ exists"
  else
    echo "$dir/ NOT found"
  fi
done

echo ""
echo "=== Checking vite.config.ts is included and exists ==="
if [ -f "nanobanana/vite.config.ts" ]; then
  echo "✓ nanobanana/vite.config.ts exists"
else
  echo "✗ nanobanana/vite.config.ts NOT found"
fi

echo ""
echo "=== Checking path alias targets exist ==="
echo "Checking @/* -> ./view/src/*"
if [ -d "view/src" ]; then
  echo "✓ view/src/ exists"
else
  echo "✗ view/src/ NOT found (or may be generated)"
fi

echo "Checking shared/* -> ./shared/*"
if [ -d "shared" ]; then
  echo "✓ shared/ exists"
else
  echo "✗ shared/ NOT found"
fi

echo "Checking server/* -> ./server/*"
if [ -d "server" ]; then
  echo "✓ server/ exists"
else
  echo "✗ server/ NOT found"
fi

echo "Checking worker/* -> ./worker/*"
if [ -d "worker" ]; then
  echo "✓ worker/ exists"
else
  echo "✗ worker/ NOT found (may be expected)"
fi

echo ""
echo "=== Directory structure around nanobanana ==="
ls -la | grep -E "^d"

Length of output: 1337


🏁 Script executed:

#!/bin/bash
# Get the actual content of tsconfig.json to identify the syntax error
echo "=== Actual content of nanobanana/tsconfig.json ==="
cat nanobanana/tsconfig.json

echo ""
echo "=== Check for comments or special characters ==="
cat -A nanobanana/tsconfig.json | head -15

echo ""
echo "=== Check directory structure within nanobanana ==="
find nanobanana -type d | head -20

Length of output: 2163


🏁 Script executed:

#!/bin/bash
# Check template-with-view structure to understand expected layout
echo "=== Directory structure of template-with-view ==="
find template-with-view -type d | head -15

echo ""
echo "=== Check if template-with-view/view exists ==="
if [ -d "template-with-view/view" ]; then
  echo "✓ template-with-view/view/ exists"
  find template-with-view/view -type d | head -10
else
  echo "✗ template-with-view/view/ does not exist"
fi

echo ""
echo "=== Verify JSON cannot have comments - test with simple JSON validator ==="
# Try to parse with node's native JSON parser (if node available)
node -e "const fs = require('fs'); JSON.parse(fs.readFileSync('nanobanana/tsconfig.json', 'utf8'));" 2>&1 || echo "Failed to parse as JSON (expected - contains comments)"

Length of output: 1433


Remove JSON comments and fix missing directory references in tsconfig.json.

The configuration has two critical issues that prevent it from working:

  1. JSON syntax error: Comments (/* ... */) are invalid in JSON files and will fail during parsing by TypeScript, bundlers, and any JSON parser. Remove all comments or convert to .jsonc format if your toolchain supports it.

  2. Missing directories: The include array and path aliases reference directories that don't exist in the nanobanana workspace:

    • "view" in include (line 44) — nanobanana/view/ does not exist
    • "@/*" → "./view/src/*" (line 30) — nanobanana/view/ does not exist
    • "worker/*" → "./worker/*" (line 33) — nanobanana/worker/ does not exist

Only server/ and shared/ exist in nanobanana. Either correct the configuration to match the actual workspace structure, or clarify if this tsconfig is intended for a different workspace layout.

…ths and add npm shims

- Removed specific npm module paths from tsconfig.json files across multiple projects to simplify configuration.
- Added new npm-shims.d.ts files to support Deno-style npm imports for various packages, enhancing compatibility and maintainability.
@viktormarinho viktormarinho force-pushed the feat/add-mcps-already-supported branch from 484f764 to 428de50 Compare November 10, 2025 15:53
…multiple projects

- Updated package.json and wrangler.toml files to replace dependencies from @deco/workers-runtime to @decocms/runtime.
- Adjusted import statements in server and view files to reflect the new runtime.
- Ensured compatibility with the new runtime by updating related configurations and schemas.
@viktormarinho viktormarinho merged commit ab52fb9 into main Nov 10, 2025
7 of 8 checks passed
This was referenced Nov 11, 2025
@viniciusventura29 viniciusventura29 deleted the feat/add-mcps-already-supported branch November 17, 2025 19:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants