-
Notifications
You must be signed in to change notification settings - Fork 0
chore: add gemini-nano-banana workspace with initial setup including … #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…Vite, React, and Tailwind CSS integration
|
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 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. ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (30)
WalkthroughThis 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
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. Comment |
🚀 Preview Deployments Ready!Your changes have been deployed to preview environments: 📦
|
…mcps-already-supported
… 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.
There was a problem hiding this 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.pageappears 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 theEnvtype inmain.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
ensureAuthenticatedmethod returnsany, 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
__filenameand__dirnamedefinitions 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
PopoverTriggerandPopoverContentare properly implemented:
- Tailwind v4 syntax verified:
outline-hiddenandorigin-(--radix-popover-content-transform-origin)with parentheses are correct.- Portal rendering and prop forwarding work as expected.
- Default values for
alignandsideOffsetare sensible.Optionally, consider adding
displayNameproperties 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
⛔ Files ignored due to path filters (11)
bun.lockis excluded by!**/*.lockgemini-nano-banana/public/apple-touch-icon.pngis excluded by!**/*.pnggemini-nano-banana/public/d.pngis excluded by!**/*.pnggemini-nano-banana/public/favicon-16x16.pngis excluded by!**/*.pnggemini-nano-banana/public/favicon-192x192.pngis excluded by!**/*.pnggemini-nano-banana/public/favicon-32x32.pngis excluded by!**/*.pnggemini-nano-banana/public/favicon-48x48.pngis excluded by!**/*.pnggemini-nano-banana/public/favicon-512x512.pngis excluded by!**/*.pnggemini-nano-banana/public/favicon.icois excluded by!**/*.icogemini-nano-banana/public/favicon.pngis excluded by!**/*.pnggemini-nano-banana/public/logo.pngis 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-sharedpromotes code reuse and follows DRY principles. The dependency is properly declared inobject-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
thiscontext withapply(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
ImageGeneratorEnvconstraint 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-bananaworkspace 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.tsxaligns 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
clsxfor conditional classes andtwMergefor 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
UserToolsEnvinterface 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
createGetUserToolfunction 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
asChildpattern 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
Popovercomponent correctly wrap the Radix primitive with proper TypeScript typing and the conventionaldata-slotattribute for styling hooks.
40-46: LGTM! Complete and consistent exports.The
PopoverAnchorwrapper 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
Mcpgeneric type andEnvinterface correctly define the MCP contract:
- The
Mcp<T>type properly wraps function signatures withasToolandexecutecapabilities.- The
Envinterface 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.tsas shown in the relevant code snippets.
| 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, | ||
| }, | ||
| ], | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this 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 ifcandidateexists, accessing nested properties without full optional chaining will throwTypeErrorif 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
choicesarray orundefined,data.choices[0]will beundefined, causing Line 111-112 to throwTypeError: 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
useOptionalUseranduseUserhooks 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 ofensureAuthenticated.The
ensureAuthenticatedmethod returnsany, which bypasses type safety. If the actual return type is known, consider using a more specific type orunknownto 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
📒 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.
| 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; | ||
| }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
- Settle with a failure status, or
- 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.
| 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.
There was a problem hiding this 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 beforeimg.src = currentImage(line 108) to prevent canvas tainting whencurrentImageis 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 callcombineImageWithCanvas()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) andGET_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
ToolCallEntryinterface:interface ToolCallEntry { timestamp: number; tool: string; input: unknown; output: unknown; + error?: string; }This way, consumers can distinguish success from failure via the
errorfield 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:
- Debounce or batch logging calls within a short window to reduce individual writes
- Use a write queue with deferred, coalesced persistence to serialize updates
- 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
onClickhandlers, 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
📒 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)
There was a problem hiding this 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
geminiToolsis not imported or used anywhere in the codebase. Additionally, the export pattern appears incorrect—you're exporting an array containing the factory functiongenerateImagerather than instantiated tool instances.Please run this script to confirm whether
geminiToolsis 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
geminiToolsis indeed unused, remove the export. If it is used, please verify the export pattern is correct for how tools are consumed intools/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
choicesarray (which can occur on provider-side errors),data.choices[0]at line 109 will beundefined, causing a TypeError at line 111 when attempting to accesschoices.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.finishReasonwithout the?operator. Although the flow guaranteescandidateexists 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
📒 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.
There was a problem hiding this 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
📒 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.
There was a problem hiding this 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
geminiToolsis imported anywhere:#!/bin/bash # Search for imports of geminiTools across the codebase rg -n 'geminiTools' --type tsshared/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:
- Abbreviations: "etc" should be "etc." (lines 32, 87, 250, 302)
- Repetitive sentence beginnings: Lines 108–116 consecutively start with "Adiciona". Vary the phrasing (e.g., "O
withTimeoutprevine..." or "Implementa timeout para...").- 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.
- 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
📒 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.
There was a problem hiding this 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:
- Error messages may not contain status codes as strings
- The string "400" could appear in unrelated error messages
- 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
uploadFilemethod 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 asundefinedor 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
📒 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:
S3Configcovers all necessary S3-compatible storage configurationFileSystemBindingprovides a clean contract for filesystem operationsFileSystemUrlResponseis appropriately minimalThese 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.raceis 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
FileSystemStorageAdaptercorrectly implements theObjectStorageinterface by delegating to theFileSystemBinding. 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:
viteat 7.2.0 (exact version for build consistency)@deco/workers-runtimeat 0.23.2 via JSRzodat ^3.24.3 (allowing patch updates)shared/storage/interface.ts (2)
1-11: LGTM! Well-designed core interface.The
ObjectStorageinterface 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
ExtendedObjectStorageinterface 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 (errorsarray indeleteObjects).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
Buffernot being available is unfounded for this codebase. All worker deployments in the repository (object-storage, template-minimal, template-with-view) havecompatibility_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
uploadToSignedUrlacceptscontentTypeas a client-side upload option (not a signing parameter), and does not accept custom metadata. The code correctly sends onlyexpiresInto the/object/upload/signendpoint. ThecontentTypeandmetadataparameters 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 -20Length 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.
There was a problem hiding this 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
oxfmtto fix code style issues.shared/storage/factory.ts (2)
49-57: Remove unnecessary type assertion.The
as anycast 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
oxfmtto fix code style issues.shared/storage/adapters/s3.ts (5)
3-26: Replacerequire()with dynamicimport()— ESM-only mode breaks onrequire().The project uses
"type": "module"making it ESM-only. Therequire()call on line 5 will fail at runtime with "require is not defined in ES module scope."Make
createS3Clientasync and replacerequire()with dynamicimport():-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
oxfmtto fix code style issues.
42-52: Replacerequire()with dynamicimport()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: Replacerequire()with dynamicimport()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 allrequire()calls with dynamicimport()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 thebaseImageUrlfield 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 theensureAuthenticatedreturn 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
📒 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
ObjectStorageinterface by forwarding to the underlyingFileSystemBinding.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
contentTypeandmetadatafrom the signing request. Supabase'screateSignedUploadUrlobtains a signed URL, whilecontentTypeandmetadataare set in the actual upload request that follows. These options are accepted in the method signature to conform to theObjectStorageinterface, 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.
shared/storage/adapters/s3.ts
Outdated
| 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
…mcps-already-supported
There was a problem hiding this 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
ContractClauseinterface fields to clarify whatclauseIdrepresents and the unit/currency foramount(e.g., cents? dollars? contract-specific units?).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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-viewtemplate. 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
ObjectStorageis appropriate for internal use within this file.
15-47: LGTM! Well-defined interfaces.The interface definitions are clear and comprehensive. The deprecated notice on
FileSystemUrlResponseprovides proper migration guidance.
100-111: LGTM! Simple and effective utility.The
extractImageDatafunction 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
scopesarray andStateSchemaare consistent with the generated types and appear intentional for the current implementation stage.
| const extension = mimeType.split("/")[1] || "png"; | ||
| const name = fileName || new Date().toISOString().replace(/[:.]/g, "-"); | ||
| const path = `${directory}/${name}.${extension}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| 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, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
There was a problem hiding this 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
ensureAuthenticatedmethod returnsany, 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.alland always returns an empty errors array. If any individual deletion fails, the entire operation will throw. Consider usingPromise.allSettledto 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
⛔ Files ignored due to path filters (1)
bun.lockis 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.
There was a problem hiding this 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.
inlineDatahappens to contain{ data, mimeType }, but handing the raw API object back ties us to Google’s response shape and bypasses theGenerateImageCallbackOutputcontract. 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
Errordrops stack/type information and makes debugging harder. Prefer rethrowinglastError(or using it as thecause) 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
📒 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.
| 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, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
…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.
There was a problem hiding this 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
cacheDirto"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 || undefinedis unnecessary sincebaseImageUrlis already optional and will beundefinedwhen 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
bodyparameter usesany, 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
⛔ Files ignored due to path filters (1)
bun.lockis 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-storageandtemplate-with-viewtsconfigs. 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-runtimedependency (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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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.
| 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.
| const data = (await response.json()) as OpenRouterResponse; | ||
| const choices = data.choices[0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| return { | ||
| candidates: [ | ||
| { | ||
| content: { | ||
| parts: [ | ||
| { | ||
| inline_data: { | ||
| data: image, | ||
| mime_type: "image/png", | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| finishReason: nativeFinishReason, | ||
| }, | ||
| ], | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
- 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,
}],
};- 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 {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| { | ||
| "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*" | ||
| ] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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
isolatedModulesandnoEmitis 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-runtimeprevents 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 --noEmitLength 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 -20Length 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:
-
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.jsoncformat if your toolchain supports it. -
Missing directories: The
includearray 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.
484f764 to
428de50
Compare
…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.
wip
Summary by CodeRabbit
New Features
Refactoring
Documentation
Chores