-
Notifications
You must be signed in to change notification settings - Fork 9
feat: add Google Drive integration via Composio #1454
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
base: main
Are you sure you want to change the base?
Conversation
- Add generic Composio toolkit infrastructure (toolkits.ts, authenticateToolkit.ts, getConnectedAccount.ts) - Add Google Drive login flow with OAuth redirect - Refactor Google Sheets to use shared Composio utilities (DRY) - Add composioAgent for toolkit-agnostic tool fetching - Add reusable ComposioLoginLoading and ComposioLoginResult components - Use window.location.origin for dev/prod redirect compatibility
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis PR refactors Composio toolkit authentication by introducing a generalized toolkit framework. It consolidates Google Sheets and Google Drive login flows through shared UI components, a generic login hook architecture, and centralized toolkit configuration, replacing toolkit-specific implementations with reusable abstractions. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UIComponent as Google Drive Login UI
participant Hook as useGoogleDriveLogin
participant API as fetchConnectedAccountsRefresh
participant RecoupAPI as Recoup API
participant ComposioClient as Composio Client
User->>UIComponent: Click "Connect Google Drive"
UIComponent->>Hook: Call handleLogin()
Hook->>API: Fetch redirect URL with accountId
API->>RecoupAPI: POST refresh request
RecoupAPI-->>API: Return redirect URL
API-->>Hook: Return response
Hook->>ComposioClient: Navigate to redirect_url
ComposioClient->>User: Redirect to Google auth
Note over User,ComposioClient: User completes OAuth flow
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ❌ 1❌ Failed checks (1 warning)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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 |
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.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 408ff1e438
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // Get tools for all Composio toolkits | ||
| const [googleSheetsTools, googleDriveTools] = await Promise.all([ | ||
| getToolkitTools("GOOGLE_SHEETS", body), | ||
| getToolkitTools("GOOGLE_DRIVE", body), | ||
| ]); |
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 Drive toolkit when env var is missing
The new unconditional getToolkitTools("GOOGLE_DRIVE", …) call means every chat request now tries to resolve Drive tools. When a user has no existing Drive connection, getToolkitTools → getConnectedAccount → authenticateToolkit reads COMPOSIO_GOOGLE_DRIVE_AUTH_CONFIG_ID and throws if it isn’t set. In any environment where Drive isn’t configured (or for new users), this will fail the entire tool setup even if Drive isn’t requested. Consider skipping Drive when the env var is absent or deferring the call until the user requests Drive.
Useful? React with 👍 / 👎.
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
🤖 Fix all issues with AI agents
In @components/VercelChat/tools/composio/ComposioLoginResult.tsx:
- Line 6: The ComposioLoginResult component hardcodes "Google" and uses
string-concatenated classNames; make the redirect service dynamic (add a prop
like serviceName or redirectService or read it from useUserProvider()) and
replace the literal "Google" text with that prop/value throughout the component
so non-Google integrations work; also replace the className usage
className={`w-full ${buttonColor} text-white`} with the cn() utility (e.g.,
cn('w-full', buttonColor, 'text-white')) to merge classes properly while keeping
the existing import of useUserProvider (whose filename currently has the typo
UserProvder) unchanged.
In @hooks/useComposioLogin.ts:
- Line 21: The hook useComposioLogin currently force-casts userData?.account_id
to a string via accountId = userData?.account_id as string; — change this to
null-safe handling: validate that userData and userData.account_id exist (e.g.,
derive accountId as string | null or undefined instead of forcing a cast),
return that accountId from the hook, and update any API call paths inside the
hook (and consuming components) to early-return or disable operations when
accountId is missing; ensure the returned shape includes accountId so callers
can disable buttons when no account is available.
In @lib/agents/composioAgent/getToolkitTools.ts:
- Around line 30-34: The callbackUrl in the CreateConnectedAccountOptions object
is using a hardcoded production domain; update the options object so callbackUrl
is built from an environment-specific origin instead: if this code runs
client-side use window.location.origin, otherwise read a configured environment
variable (e.g., process.env.APP_ORIGIN) and fall back to a sensible default,
then append the encoded latestUserMessageText as before (refer to options,
CreateConnectedAccountOptions, callbackUrl, latestUserMessageText).
In @lib/chat/setupToolsForRequest.ts:
- Around line 34-37: Replace the Promise.all call that fetches toolkit tools so
a single toolkit failure does not bail the whole request: call
Promise.allSettled with the two getToolkitTools("GOOGLE_SHEETS", body) and
getToolkitTools("GOOGLE_DRIVE", body) promises, inspect each result, set
googleSheetsTools and googleDriveTools to the fulfilled values (or an empty
array/null) when rejected, and log the rejection errors via your logging
utility; ensure subsequent code that uses googleSheetsTools/googleDriveTools can
handle missing/empty values so the request proceeds with available toolkits.
In @lib/composio/getConnectedAccount.ts:
- Around line 28-34: getConnectedAccount currently calls authenticateToolkit and
then immediately re-invokes composio.connectedAccounts.list, which assumes OAuth
completes synchronously; instead, stop retrying immediately after
authenticateToolkit. Modify getConnectedAccount so that after calling
authenticateToolkit it returns a clear pending/auth-initiated result (or throws
a specific AuthPending error) that the caller can handle (e.g., trigger browser
flow and poll later), or alternatively document that the caller must re-run/get
accounts after the OAuth flow completes; do not call
composio.connectedAccounts.list again directly after authenticateToolkit. Ensure
references to authenticateToolkit, composio.connectedAccounts.list, and the
userAccounts handling are updated accordingly.
🧹 Nitpick comments (13)
components/VercelChat/tools/composio/ComposioLoginResult.tsx (2)
60-63: Hardcoded "Google" breaks component reusability.This shared component hardcodes "Google" in the authorization notice, which won't be accurate for non-Google toolkits added in the future. Consider parameterizing the provider name or deriving it from
toolkitName.♻️ Suggested fix
- <p className="text-xs text-muted-foreground text-center"> - You'll be redirected to Google to authorize access to your{" "} - {toolkitName} for this account. - </p> + <p className="text-xs text-muted-foreground text-center"> + You'll be redirected to authorize access to your {toolkitName} for + this account. + </p>
44-58: Consider using CVA for button color variants.Per coding guidelines, use
class-variance-authority(CVA) for component variants. PassingbuttonColoras a raw class string works but bypasses the variant pattern used elsewhere in the codebase. This is a minor consistency nit for future extensibility.hooks/useComposioLogin.ts (2)
27-27: Consider safer optional chaining for window access.While the typeof check works, using optional chaining provides more defensive coding against edge cases.
♻️ Optional refinement
- const baseUrl = typeof window !== "undefined" ? window.location.origin : "https://chat.recoupable.com"; + const baseUrl = window?.location?.origin ?? "https://chat.recoupable.com";This pattern is more concise and handles nullish values more gracefully.
45-48: Add type guard for error handling.The error in the catch block is typed as
unknown(implicitly), butconfig.nameis used without verifying thatconfigis valid. WhilegetToolkitConfiglikely always returns a valid config, defensive error handling is recommended.🛡️ Improved error handling
} catch (error) { + console.error(`Failed to initiate ${config.name} login:`, error); toast.error( `Failed to initiate ${config.name} login. Please try again.` ); } finally {Adding a console.error helps with debugging in production while maintaining the user-friendly toast message. This follows the guideline that console.log should be removed, but console.error for actual errors is appropriate.
lib/composio/authenticateToolkit.ts (1)
13-28: Add error handling with context for authentication failures.The function lacks explicit error handling around authentication operations. When
getToolkitAuthConfigIdorcomposio.connectedAccounts.initiatefails, the raw error propagates to callers without additional context. Consider wrapping the operations in a try-catch block to provide more descriptive error messages that include the toolkit key and user ID.♻️ Proposed error handling enhancement
export async function authenticateToolkit( toolkitKey: ComposioToolkitKey, userId: string, options?: CreateConnectedAccountOptions ) { - const authConfigId = getToolkitAuthConfigId(toolkitKey); - const composio = await getComposioClient(); - - const connectionRequest = await composio.connectedAccounts.initiate( - userId, - authConfigId, - options - ); - - return connectionRequest; + try { + const authConfigId = getToolkitAuthConfigId(toolkitKey); + const composio = await getComposioClient(); + + const connectionRequest = await composio.connectedAccounts.initiate( + userId, + authConfigId, + options + ); + + return connectionRequest; + } catch (error) { + throw new Error( + `Failed to authenticate ${toolkitKey} for user ${userId}: ${error instanceof Error ? error.message : 'Unknown error'}` + ); + } }lib/agents/composioAgent/getToolkitTools.ts (1)
21-48: Consider separating authentication and tool retrieval concerns.The function handles both checking authentication state and retrieving tools, which violates the Single Responsibility Principle. While the current implementation is functional, consider whether the authentication check logic could be extracted to improve testability and reusability.
components/VercelChat/tools/composio/ComposioLoginLoading.tsx (2)
10-15: Add accessibility attributes for screen reader support.The loading component lacks ARIA attributes to communicate its purpose and dynamic state to assistive technologies. Add
role="status"andaria-live="polite"to ensure screen readers announce the loading state.♿ Proposed accessibility enhancement
return ( - <div className="flex items-center gap-1 py-1 px-2 bg-muted/50 rounded-sm border border-border w-fit text-xs text-muted-foreground"> + <div + role="status" + aria-live="polite" + aria-label={`Initializing ${toolkitName} login`} + className="flex items-center gap-1 py-1 px-2 bg-muted/50 rounded-sm border border-border w-fit text-xs text-muted-foreground" + > <Loader className="h-3 w-3 animate-spin text-foreground" /> <span>Initializing {toolkitName} login...</span> </div> );
18-18: Remove redundant default export.The component has both a named export (line 7) and a default export (line 18). The named export is sufficient and more explicit. Per coding guidelines: "Use Single Responsibility Principle: One function per file with clear naming."
♻️ Proposed cleanup
- -export default ComposioLoginLoading;lib/composio/fetchConnectedAccountsRefresh.ts (2)
43-45: Enhance error messages with response details.The error message only includes the HTTP status code but lacks context about which toolkit failed and the response body (which may contain useful error details from the API). Include the toolkit key and attempt to parse the error response.
🔧 Proposed error handling enhancement
if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); + const errorBody = await response.text().catch(() => 'Unable to parse error response'); + throw new Error( + `Failed to refresh ${toolkitKey} connected account (HTTP ${response.status}): ${errorBody}` + ); }
32-41: Add timeout handling for fetch requests.The fetch request lacks timeout handling, which could cause the request to hang indefinitely if the API is unresponsive. Consider adding an AbortController with a timeout to improve reliability.
⏱️ Proposed timeout handling
+ const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout + + try { const response = await fetch(`${API_BASE_URL}${config.refreshEndpoint}`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ accountId, redirectUrl, }), + signal: controller.signal, }); + clearTimeout(timeoutId); + } catch (error) { + clearTimeout(timeoutId); + if (error instanceof Error && error.name === 'AbortError') { + throw new Error(`Request to refresh ${toolkitKey} timed out`); + } + throw error; + }lib/composio/getConnectedAccount.ts (2)
42-46: Improve safety of connection status check.The
isConnectionActivefunction uses optional chaining onitems[0]but doesn't explicitly check if the array has items. While technically safe due to optional chaining, it returnsfalsefor both "no accounts" and "account exists but inactive", which may be ambiguous for callers.🛡️ Proposed safer implementation
export function isConnectionActive( userAccounts: Awaited<ReturnType<typeof getConnectedAccount>> ): boolean { - return userAccounts.items[0]?.data?.status === "ACTIVE"; + return ( + userAccounts.items.length > 0 && + userAccounts.items[0]?.data?.status === "ACTIVE" + ); }Alternatively, consider returning a more descriptive type:
export function getConnectionStatus( userAccounts: Awaited<ReturnType<typeof getConnectedAccount>> ): "active" | "inactive" | "none" { if (userAccounts.items.length === 0) return "none"; return userAccounts.items[0]?.data?.status === "ACTIVE" ? "active" : "inactive"; }
15-37: Consider separating account retrieval from authentication initiation.The
getConnectedAccountfunction handles both retrieving accounts and initiating authentication when none exist. This violates the Single Responsibility Principle and makes the function's behavior less predictable. Consider either:
- Renaming to
getOrInitiateConnectedAccountto clarify dual behavior- Splitting into separate functions:
getConnectedAccountandinitiateConnectionThe current approach may be acceptable given the OAuth flow, but explicit naming would improve code clarity.
lib/tools/composio/loginTools.ts (1)
24-30: Consider adding error handling in the execute function.The
executefunction callsgetConnectedAccountwithout a try/catch. If the connected account lookup or authentication flow fails, the error will propagate unhandled, which could result in a poor user experience or unclear error messages from the AI.Wrapping this in error handling would provide more graceful degradation and clearer feedback.
♻️ Suggested improvement
execute: async ({ account_id }) => { - await getConnectedAccount(toolkitKey, account_id); - return { - success: true, - message: `${config.name} login initiated successfully. Please click the button above to login with ${config.name}.`, - }; + try { + await getConnectedAccount(toolkitKey, account_id); + return { + success: true, + message: `${config.name} login initiated successfully. Please click the button above to login with ${config.name}.`, + }; + } catch (error) { + return { + success: false, + message: `Failed to initiate ${config.name} login. Please try again.`, + }; + } },
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (24)
components/VercelChat/ToolComponents.tsxcomponents/VercelChat/tools/composio/ComposioLoginLoading.tsxcomponents/VercelChat/tools/composio/ComposioLoginResult.tsxcomponents/VercelChat/tools/googleDrive/GoogleDriveLoginLoading.tsxcomponents/VercelChat/tools/googleDrive/GoogleDriveLoginResult.tsxcomponents/VercelChat/tools/googleSheets/GoogleSheetsLoginLoading.tsxcomponents/VercelChat/tools/googleSheets/GoogleSheetsLoginResult.tsxhooks/useComposioLogin.tshooks/useGoogleDriveLogin.tshooks/useGoogleSheetsLogin.tslib/agents/composioAgent/getToolkitTools.tslib/agents/composioAgent/index.tslib/agents/googleSheetsAgent/getGoogleSheetsTools.tslib/agents/googleSheetsAgent/index.tslib/chat/setupToolsForRequest.tslib/composio/authenticateToolkit.tslib/composio/fetchConnectedAccountsRefresh.tslib/composio/getConnectedAccount.tslib/composio/googleSheets/authenticateGoogleSheetsToolkit.tslib/composio/googleSheets/fetchConnectedAccountsRefresh.tslib/composio/googleSheets/getConnectedAccount.tslib/composio/toolkits.tslib/tools/composio/googleSheetsLoginTool.tslib/tools/composio/loginTools.ts
💤 Files with no reviewable changes (6)
- lib/agents/googleSheetsAgent/index.ts
- lib/composio/googleSheets/authenticateGoogleSheetsToolkit.ts
- lib/composio/googleSheets/fetchConnectedAccountsRefresh.ts
- lib/composio/googleSheets/getConnectedAccount.ts
- lib/tools/composio/googleSheetsLoginTool.ts
- lib/agents/googleSheetsAgent/getGoogleSheetsTools.ts
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{tsx,ts,jsx,js}
📄 CodeRabbit inference engine (.cursor/rules/component-design.mdc)
**/*.{tsx,ts,jsx,js}: Use semantic HTML elements appropriate to the component's role
Ensure keyboard navigation and focus management in UI components
Provide proper ARIA roles/states and test with screen readers
Start with semantic HTML first, then augment with ARIA if needed
Support both controlled and uncontrolled state in components
Wrap a single HTML element per component (no multiple root elements)
Favor composition over inheritance in component design
Expose clear APIs via props/slots for component customization
UseasChildpattern for flexible element types in components
Support polymorphism withasprop when appropriate
Use@radix-ui/react-*primitives for behavior and accessibility
Useclass-variance-authority(CVA) for component variants
Use@radix-ui/react-slotfor implementingasChildpattern
Use@radix-ui/react-use-controllable-statefor state management
Follow shadcn/ui component structure and naming conventions
Usecn()utility combiningclsxandtailwind-mergefor class merging
Export both component and variant functions (e.g.,Button, buttonVariants)
Use@radix-ui/react-iconsfor UI component icons (not Lucide React)
Uselucide-reactfor application icons and illustrations
Useframer-motionfor animations and transitions
Usenext-themesfor theme management
Usetailwindcss-animatefor CSS animations
Usesonnerfor toast notifications
Useembla-carousel-reactfor carousels
Usereact-resizable-panelsfor resizable layouts
ProvidedefaultValueandonValueChangeprops for state management
Usedata-stateattribute for visual component states (open/closed, loading, etc.)
Usedata-slotattribute for component identification
Use kebab-case naming for data attributes (e.g.,data-slot="form-field")
Follow class order in Tailwind: base → variants → conditionals → user overrides
Define component variants outside components to avoid recreation on re-render
Implement focus trapping in modal/dialog components
Store...
Files:
components/VercelChat/tools/googleSheets/GoogleSheetsLoginResult.tsxcomponents/VercelChat/tools/googleDrive/GoogleDriveLoginLoading.tsxhooks/useGoogleDriveLogin.tshooks/useComposioLogin.tscomponents/VercelChat/tools/googleSheets/GoogleSheetsLoginLoading.tsxcomponents/VercelChat/tools/composio/ComposioLoginLoading.tsxlib/tools/composio/loginTools.tslib/composio/fetchConnectedAccountsRefresh.tscomponents/VercelChat/tools/googleDrive/GoogleDriveLoginResult.tsxlib/agents/composioAgent/getToolkitTools.tscomponents/VercelChat/tools/composio/ComposioLoginResult.tsxlib/composio/authenticateToolkit.tslib/chat/setupToolsForRequest.tslib/agents/composioAgent/index.tscomponents/VercelChat/ToolComponents.tsxlib/composio/toolkits.tshooks/useGoogleSheetsLogin.tslib/composio/getConnectedAccount.ts
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/component-design.mdc)
**/*.{tsx,ts}: Extend native HTML attributes usingReact.ComponentProps<"element">pattern
Export component prop types with explicitComponentNamePropsnaming convention
Files:
components/VercelChat/tools/googleSheets/GoogleSheetsLoginResult.tsxcomponents/VercelChat/tools/googleDrive/GoogleDriveLoginLoading.tsxhooks/useGoogleDriveLogin.tshooks/useComposioLogin.tscomponents/VercelChat/tools/googleSheets/GoogleSheetsLoginLoading.tsxcomponents/VercelChat/tools/composio/ComposioLoginLoading.tsxlib/tools/composio/loginTools.tslib/composio/fetchConnectedAccountsRefresh.tscomponents/VercelChat/tools/googleDrive/GoogleDriveLoginResult.tsxlib/agents/composioAgent/getToolkitTools.tscomponents/VercelChat/tools/composio/ComposioLoginResult.tsxlib/composio/authenticateToolkit.tslib/chat/setupToolsForRequest.tslib/agents/composioAgent/index.tscomponents/VercelChat/ToolComponents.tsxlib/composio/toolkits.tshooks/useGoogleSheetsLogin.tslib/composio/getConnectedAccount.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/stagehand.mdc)
**/*.{ts,tsx}: Plan instructions using Stagehandobservemethod before executing actions (e.g.,const results = await page.observe("Click the sign in button");)
Cache the results ofobserveto avoid unexpected DOM changes
Keepactactions atomic and specific (e.g., "Click the sign in button" not "Sign in to the website"). Avoid actions that are more than one step.
Use variable substitution with thevariablesparameter inactfor dynamic form filling (e.g.,action: "Enter Name: %name%", variables: { name: "John Doe" })
Always use Zod schemas for structured data extraction with theextractmethod (e.g.,schema: z.object({ text: z.string() }))
Wrap array extractions in a single object when extracting multiple items usingextract(e.g.,schema: z.object({ buttons: z.array(z.string()) }))
Use explicitinstructionandschemaparameters when callingextractmethod
Import Stagehand types from@browserbasehq/stagehandpackage (e.g.,import { Stagehand, Page, BrowserContext } from "@browserbasehq/stagehand";)
Initialize Stagehand withnew Stagehand()constructor and callawait stagehand.init()before using page automation methods
Implement main automation logic in functions that accept{ page, context, stagehand }parameters
Provide specific instructions to agents instead of vague ones (e.g., "Navigate to products page and filter by 'Electronics'" not "Do some stuff on this page")
Break down complex agent tasks into smaller steps for better execution
Use error handling with try/catch blocks when executing agent tasks
Combine agents for navigation with traditional methods for precise data extraction
**/*.{ts,tsx}: Create single-responsibility components with obvious data flow
Use strict TypeScript types with zero 'any' types
Follow Next.js optimization guides for performance
Files:
components/VercelChat/tools/googleSheets/GoogleSheetsLoginResult.tsxcomponents/VercelChat/tools/googleDrive/GoogleDriveLoginLoading.tsxhooks/useGoogleDriveLogin.tshooks/useComposioLogin.tscomponents/VercelChat/tools/googleSheets/GoogleSheetsLoginLoading.tsxcomponents/VercelChat/tools/composio/ComposioLoginLoading.tsxlib/tools/composio/loginTools.tslib/composio/fetchConnectedAccountsRefresh.tscomponents/VercelChat/tools/googleDrive/GoogleDriveLoginResult.tsxlib/agents/composioAgent/getToolkitTools.tscomponents/VercelChat/tools/composio/ComposioLoginResult.tsxlib/composio/authenticateToolkit.tslib/chat/setupToolsForRequest.tslib/agents/composioAgent/index.tscomponents/VercelChat/ToolComponents.tsxlib/composio/toolkits.tshooks/useGoogleSheetsLogin.tslib/composio/getConnectedAccount.ts
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)
**/*.{ts,tsx,js}: Write minimal code - use only the absolute minimum code needed
Ensure code is self-documenting through precise naming with verbs for functions and nouns for variables
Add short comments only when necessary to clarify non-obvious logic
Implement built-in security practices for authentication and data handling
Consider if code can be split into smaller functions before implementing
Avoid unnecessary abstractions during implementation
Ensure code clarity is suitable for understanding by junior developers
Files:
components/VercelChat/tools/googleSheets/GoogleSheetsLoginResult.tsxcomponents/VercelChat/tools/googleDrive/GoogleDriveLoginLoading.tsxhooks/useGoogleDriveLogin.tshooks/useComposioLogin.tscomponents/VercelChat/tools/googleSheets/GoogleSheetsLoginLoading.tsxcomponents/VercelChat/tools/composio/ComposioLoginLoading.tsxlib/tools/composio/loginTools.tslib/composio/fetchConnectedAccountsRefresh.tscomponents/VercelChat/tools/googleDrive/GoogleDriveLoginResult.tsxlib/agents/composioAgent/getToolkitTools.tscomponents/VercelChat/tools/composio/ComposioLoginResult.tsxlib/composio/authenticateToolkit.tslib/chat/setupToolsForRequest.tslib/agents/composioAgent/index.tscomponents/VercelChat/ToolComponents.tsxlib/composio/toolkits.tshooks/useGoogleSheetsLogin.tslib/composio/getConnectedAccount.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx,js,jsx}: Use Single Responsibility Principle: One function per file with clear naming
Remove all console.log statements before merging to production
Write comments to explain 'why', not 'what'; prefer self-documenting code
Files:
components/VercelChat/tools/googleSheets/GoogleSheetsLoginResult.tsxcomponents/VercelChat/tools/googleDrive/GoogleDriveLoginLoading.tsxhooks/useGoogleDriveLogin.tshooks/useComposioLogin.tscomponents/VercelChat/tools/googleSheets/GoogleSheetsLoginLoading.tsxcomponents/VercelChat/tools/composio/ComposioLoginLoading.tsxlib/tools/composio/loginTools.tslib/composio/fetchConnectedAccountsRefresh.tscomponents/VercelChat/tools/googleDrive/GoogleDriveLoginResult.tsxlib/agents/composioAgent/getToolkitTools.tscomponents/VercelChat/tools/composio/ComposioLoginResult.tsxlib/composio/authenticateToolkit.tslib/chat/setupToolsForRequest.tslib/agents/composioAgent/index.tscomponents/VercelChat/ToolComponents.tsxlib/composio/toolkits.tshooks/useGoogleSheetsLogin.tslib/composio/getConnectedAccount.ts
**/components/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/components/**/*.{ts,tsx}: Use @radix-ui/react-* primitives with class-variance-authority (CVA) for component styling
Use cn() utility for class merging in components
Always include accessibility in components: use semantic HTML, ARIA attributes, and keyboard navigation
Files:
components/VercelChat/tools/googleSheets/GoogleSheetsLoginResult.tsxcomponents/VercelChat/tools/googleDrive/GoogleDriveLoginLoading.tsxcomponents/VercelChat/tools/googleSheets/GoogleSheetsLoginLoading.tsxcomponents/VercelChat/tools/composio/ComposioLoginLoading.tsxcomponents/VercelChat/tools/googleDrive/GoogleDriveLoginResult.tsxcomponents/VercelChat/tools/composio/ComposioLoginResult.tsxcomponents/VercelChat/ToolComponents.tsx
components/**/*.tsx
⚙️ CodeRabbit configuration file
components/**/*.tsx: For React components, ensure:
- Single responsibility per component
- Proper prop interfaces (ISP)
- Use composition over inheritance
- Avoid prop drilling (use context or state management)
- Keep components under 200 lines
- Extract custom hooks for complex logic
- Use TypeScript interfaces for props
- DRY: Extract common UI patterns into reusable components
- KISS: Prefer simple component structure over complex abstractions
Files:
components/VercelChat/tools/googleSheets/GoogleSheetsLoginResult.tsxcomponents/VercelChat/tools/googleDrive/GoogleDriveLoginLoading.tsxcomponents/VercelChat/tools/googleSheets/GoogleSheetsLoginLoading.tsxcomponents/VercelChat/tools/composio/ComposioLoginLoading.tsxcomponents/VercelChat/tools/googleDrive/GoogleDriveLoginResult.tsxcomponents/VercelChat/tools/composio/ComposioLoginResult.tsxcomponents/VercelChat/ToolComponents.tsx
hooks/**/*.ts
⚙️ CodeRabbit configuration file
hooks/**/*.ts: For custom hooks, ensure:
- Single responsibility per hook
- Return consistent interface
- Handle edge cases and errors
- Use proper dependency arrays
- Keep hooks focused and reusable
- Follow naming convention (use prefix)
- DRY: Extract common hook logic into shared utilities
- KISS: Avoid complex hook compositions, prefer simple state management
Files:
hooks/useGoogleDriveLogin.tshooks/useComposioLogin.tshooks/useGoogleSheetsLogin.ts
lib/**/*.ts
⚙️ CodeRabbit configuration file
lib/**/*.ts: For utility functions, ensure:
- Pure functions when possible
- Single responsibility per function
- Proper error handling
- Use TypeScript for type safety
- Avoid side effects
- Keep functions under 50 lines
- DRY: Consolidate similar logic into shared utilities
- KISS: Prefer simple, readable implementations over clever optimizations
Files:
lib/tools/composio/loginTools.tslib/composio/fetchConnectedAccountsRefresh.tslib/agents/composioAgent/getToolkitTools.tslib/composio/authenticateToolkit.tslib/chat/setupToolsForRequest.tslib/agents/composioAgent/index.tslib/composio/toolkits.tslib/composio/getConnectedAccount.ts
🧠 Learnings (3)
📚 Learning: 2025-11-29T16:42:25.945Z
Learnt from: CR
Repo: Recoupable-com/Recoup-Chat PR: 0
File: .cursor/rules/component-design.mdc:0-0
Timestamp: 2025-11-29T16:42:25.945Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : Use `lucide-react` for application icons and illustrations
Applied to files:
components/VercelChat/tools/googleSheets/GoogleSheetsLoginLoading.tsxcomponents/VercelChat/tools/composio/ComposioLoginLoading.tsxcomponents/VercelChat/tools/composio/ComposioLoginResult.tsx
📚 Learning: 2026-01-07T17:34:21.911Z
Learnt from: CR
Repo: Recoupable-com/Recoup-Chat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-07T17:34:21.911Z
Learning: Applies to **/lib/chat/toolChains/**/*.{ts,tsx} : MCP tools like `send_email` are provided by the recoup-api MCP server; reference tool names in tool chains at `lib/chat/toolChains/` instead of defining tools locally
Applied to files:
lib/chat/setupToolsForRequest.tscomponents/VercelChat/ToolComponents.tsx
📚 Learning: 2026-01-07T17:34:21.911Z
Learnt from: CR
Repo: Recoupable-com/Recoup-Chat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-07T17:34:21.911Z
Learning: Applies to **/lib/consts.ts : Store shared constants in `lib/consts.ts` including `RECOUP_FROM_EMAIL` for email sender address and platform-specific configurations
Applied to files:
lib/composio/toolkits.ts
🧬 Code graph analysis (13)
components/VercelChat/tools/googleSheets/GoogleSheetsLoginResult.tsx (2)
hooks/useGoogleSheetsLogin.ts (1)
useGoogleSheetsLogin(6-8)components/VercelChat/tools/composio/ComposioLoginResult.tsx (1)
ComposioLoginResult(18-66)
components/VercelChat/tools/googleDrive/GoogleDriveLoginLoading.tsx (1)
components/VercelChat/tools/composio/ComposioLoginLoading.tsx (1)
ComposioLoginLoading(7-16)
hooks/useGoogleDriveLogin.ts (1)
hooks/useComposioLogin.ts (1)
useComposioLogin(17-59)
components/VercelChat/tools/googleSheets/GoogleSheetsLoginLoading.tsx (1)
components/VercelChat/tools/composio/ComposioLoginLoading.tsx (1)
ComposioLoginLoading(7-16)
lib/tools/composio/loginTools.ts (2)
lib/composio/toolkits.ts (2)
ComposioToolkitKey(26-26)getToolkitConfig(32-34)lib/composio/getConnectedAccount.ts (1)
getConnectedAccount(15-37)
lib/composio/fetchConnectedAccountsRefresh.ts (1)
lib/composio/toolkits.ts (2)
ComposioToolkitKey(26-26)getToolkitConfig(32-34)
components/VercelChat/tools/googleDrive/GoogleDriveLoginResult.tsx (2)
hooks/useGoogleDriveLogin.ts (1)
useGoogleDriveLogin(6-8)components/VercelChat/tools/composio/ComposioLoginResult.tsx (1)
ComposioLoginResult(18-66)
components/VercelChat/tools/composio/ComposioLoginResult.tsx (1)
components/ui/button.tsx (1)
Button(57-57)
lib/composio/authenticateToolkit.ts (2)
lib/composio/toolkits.ts (2)
ComposioToolkitKey(26-26)getToolkitAuthConfigId(39-50)lib/composio/client.ts (1)
getComposioClient(11-16)
lib/chat/setupToolsForRequest.ts (2)
lib/agents/composioAgent/getToolkitTools.ts (1)
getToolkitTools(21-48)lib/chat/filterExcludedTools.ts (1)
filterExcludedTools(9-24)
components/VercelChat/ToolComponents.tsx (2)
components/VercelChat/tools/googleDrive/GoogleDriveLoginLoading.tsx (1)
GoogleDriveLoginLoading(3-5)components/VercelChat/tools/googleDrive/GoogleDriveLoginResult.tsx (1)
GoogleDriveLoginResult(7-20)
hooks/useGoogleSheetsLogin.ts (1)
hooks/useComposioLogin.ts (1)
useComposioLogin(17-59)
lib/composio/getConnectedAccount.ts (3)
lib/composio/toolkits.ts (2)
ComposioToolkitKey(26-26)getToolkitConfig(32-34)lib/composio/client.ts (1)
getComposioClient(11-16)lib/composio/authenticateToolkit.ts (1)
authenticateToolkit(13-28)
⏰ 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). (2)
- GitHub Check: Vercel Agent Review
- GitHub Check: Run evaluations
🔇 Additional comments (16)
lib/composio/toolkits.ts (1)
1-50: Well-structured toolkit configuration with proper error handling.This establishes a clean single source of truth for Composio toolkits. The use of
as constensures type safety, andgetToolkitAuthConfigIdappropriately throws with a descriptive message when the environment variable is missing. Good adherence to the Single Responsibility Principle.lib/agents/composioAgent/index.ts (1)
1-1: Clean barrel export.Simple and follows the standard module entry point pattern.
hooks/useGoogleDriveLogin.ts (1)
1-8: Clean hook composition following established patterns.This follows the same pattern as
useGoogleSheetsLogin, promoting consistency and DRY principles. The delegation touseComposioLoginkeeps toolkit-specific hooks thin and maintainable.components/VercelChat/tools/googleSheets/GoogleSheetsLoginResult.tsx (1)
11-18: Good refactor to shared component.The migration to
ComposioLoginResulteliminates duplication and centralizes the login UI. The props are well-aligned with the toolkit configuration.components/VercelChat/tools/googleDrive/GoogleDriveLoginLoading.tsx (1)
1-7: Consistent pattern with GoogleSheetsLoginLoading.Clean delegation to the shared
ComposioLoginLoadingcomponent. Follows the established pattern for toolkit-specific loading wrappers.components/VercelChat/ToolComponents.tsx (3)
106-107: Imports added correctly.New imports follow the existing import organization pattern.
288-293: Tool call component wiring follows established pattern.The
googleDriveLoginToolcase mirrors thegoogleSheetsLoginToolpattern exactly, maintaining consistency.
559-564: Tool result component wiring follows established pattern.Consistent with the
googleSheetsLoginToolresult handling above.components/VercelChat/tools/googleSheets/GoogleSheetsLoginLoading.tsx (1)
1-7: LGTM! Clean refactoring to shared component.The refactoring successfully eliminates code duplication by delegating to
ComposioLoginLoading. The component maintains a clear single responsibility and follows composition over duplication principles.components/VercelChat/tools/googleDrive/GoogleDriveLoginResult.tsx (1)
1-22: LGTM! Well-composed login result component.The component demonstrates excellent separation of concerns: presentation logic is cleanly separated from business logic (delegated to the hook), and shared UI is properly composed via
ComposioLoginResult. The use ofHardDrivefromlucide-reactaligns with coding guidelines for application icons.hooks/useGoogleSheetsLogin.ts (1)
1-8: LGTM! Excellent refactoring to shared hook.The refactoring successfully eliminates login logic duplication by delegating to the centralized
useComposioLoginhook. This demonstrates strong adherence to DRY principles while maintaining a clear, focused API for Google Sheets login.lib/tools/composio/loginTools.ts (5)
1-4: Clean and minimal imports.All imports are utilized and properly organized. The use of the
@/alias maintains consistency with the project's module resolution.
6-13: Well-structured input schema with clear validation.The zod schema provides meaningful validation with a helpful error message that guides the AI on where to source the
account_id. This aligns well with the self-documenting code principle.
34-38: Good use of TypeScript for compile-time safety.The
Record<ComposioToolkitKey, Tool>type ensures that if a new toolkit is added toCOMPOSIO_TOOLKITS, TypeScript will enforce that this record is updated accordingly. This is a solid approach that catches missing entries at compile time rather than runtime.
40-45: Clean single-responsibility getter.The function is appropriately minimal and leverages TypeScript's type system to ensure only valid toolkit keys are accepted. No additional validation needed.
47-49: Good backward compatibility strategy.Exporting named aliases alongside the generic
getLoginToolForToolkitfunction maintains API compatibility for existing consumers while enabling the new toolkit-agnostic approach. This respects the Open/Closed principle by extending functionality without modifying existing interfaces.
| import React from "react"; | ||
| import { LucideIcon } from "lucide-react"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { useUserProvider } from "@/providers/UserProvder"; |
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
🏁 Script executed:
# Find provider files with similar names
fd -i "userprov" providers/Repository: Recoupable-com/Recoup-Chat
Length of output: 95
🏁 Script executed:
# Also check the full path for the file in question
fd -t f "ComposioLoginResult.tsx"Repository: Recoupable-com/Recoup-Chat
Length of output: 130
🏁 Script executed:
# Search for UserProvider/UserProvder in providers directory
rg "UserProv" providers/ -t ts -t tsxRepository: Recoupable-com/Recoup-Chat
Length of output: 97
🏁 Script executed:
# Let's read the ComposioLoginResult.tsx file to verify other issues
cat -n components/VercelChat/tools/composio/ComposioLoginResult.tsxRepository: Recoupable-com/Recoup-Chat
Length of output: 2411
🏁 Script executed:
# Also verify the content of the provider file
cat -n providers/UserProvder.tsxRepository: Recoupable-com/Recoup-Chat
Length of output: 950
Remove hardcoded "Google" text and use cn() for class merging.
The notice at line 60-62 assumes all toolkits redirect to Google, which breaks reusability for non-Google Composio integrations. Make the redirect service dynamic via props or pass it through context.
Additionally, line 46 uses string concatenation for className instead of the cn() utility per coding guidelines. Replace className={w-full ${buttonColor} text-white} with proper class merging using cn().
Note: The import path UserProvder correctly matches the actual provider filename, though the provider file itself has a typo in its name (UserProvder.tsx should be UserProvider.tsx).
🤖 Prompt for AI Agents
In @components/VercelChat/tools/composio/ComposioLoginResult.tsx at line 6, The
ComposioLoginResult component hardcodes "Google" and uses string-concatenated
classNames; make the redirect service dynamic (add a prop like serviceName or
redirectService or read it from useUserProvider()) and replace the literal
"Google" text with that prop/value throughout the component so non-Google
integrations work; also replace the className usage className={`w-full
${buttonColor} text-white`} with the cn() utility (e.g., cn('w-full',
buttonColor, 'text-white')) to merge classes properly while keeping the existing
import of useUserProvider (whose filename currently has the typo UserProvder)
unchanged.
| const [isLoading, setIsLoading] = useState(false); | ||
| const { userData } = useUserProvider(); | ||
| const { messages } = useVercelChatContext(); | ||
| const accountId = userData?.account_id as string; |
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 null safety for accountId.
The accountId is cast to string without checking if userData?.account_id is defined. This could lead to "undefined" being passed as a string to API calls, causing cryptic errors.
🔒 Proposed fix with early validation
const { userData } = useUserProvider();
const { messages } = useVercelChatContext();
- const accountId = userData?.account_id as string;
+ const accountId = userData?.account_id;
const config = getToolkitConfig(toolkitKey);
const handleLogin = async () => {
+ if (!accountId) {
+ toast.error("Account information is missing. Please refresh the page.");
+ return;
+ }
+
const latestUserMessageText = getLatestUserMessageText(messages);Additionally, consider reflecting this state in the consuming components by including accountId in the return value so buttons can be properly disabled when the account is not available.
🤖 Prompt for AI Agents
In @hooks/useComposioLogin.ts at line 21, The hook useComposioLogin currently
force-casts userData?.account_id to a string via accountId =
userData?.account_id as string; — change this to null-safe handling: validate
that userData and userData.account_id exist (e.g., derive accountId as string |
null or undefined instead of forcing a cast), return that accountId from the
hook, and update any API call paths inside the hook (and consuming components)
to early-return or disable operations when accountId is missing; ensure the
returned shape includes accountId so callers can disable buttons when no account
is available.
| const options: CreateConnectedAccountOptions = { | ||
| callbackUrl: `https://chat.recoupable.com?q=${encodeURIComponent( | ||
| latestUserMessageText | ||
| )}`, | ||
| }; |
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.
Replace hardcoded production URL with environment variable.
The callback URL uses a hardcoded production domain https://chat.recoupable.com, which breaks local development and violates environment-specific configuration practices. Use window.location.origin for client-side code or an environment variable for server-side code.
🔧 Proposed fix using window.location.origin or environment variable
If this code runs in a browser context (client-side):
const options: CreateConnectedAccountOptions = {
- callbackUrl: `https://chat.recoupable.com?q=${encodeURIComponent(
+ callbackUrl: `${window.location.origin}?q=${encodeURIComponent(
latestUserMessageText
)}`,
};If this is server-side code:
+ const baseUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
+
const options: CreateConnectedAccountOptions = {
- callbackUrl: `https://chat.recoupable.com?q=${encodeURIComponent(
+ callbackUrl: `${baseUrl}?q=${encodeURIComponent(
latestUserMessageText
)}`,
};🤖 Prompt for AI Agents
In @lib/agents/composioAgent/getToolkitTools.ts around lines 30 - 34, The
callbackUrl in the CreateConnectedAccountOptions object is using a hardcoded
production domain; update the options object so callbackUrl is built from an
environment-specific origin instead: if this code runs client-side use
window.location.origin, otherwise read a configured environment variable (e.g.,
process.env.APP_ORIGIN) and fall back to a sensible default, then append the
encoded latestUserMessageText as before (refer to options,
CreateConnectedAccountOptions, callbackUrl, latestUserMessageText).
| const [googleSheetsTools, googleDriveTools] = await Promise.all([ | ||
| getToolkitTools("GOOGLE_SHEETS", body), | ||
| getToolkitTools("GOOGLE_DRIVE", body), | ||
| ]); |
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 resilient error handling for toolkit tool fetching.
Using Promise.all means if any single toolkit's tool fetching fails (e.g., network issue, API error), the entire chat request will fail. This reduces resilience compared to allowing the request to proceed with available toolkits.
🛡️ Proposed resilient implementation using Promise.allSettled
- // Get tools for all Composio toolkits
- const [googleSheetsTools, googleDriveTools] = await Promise.all([
- getToolkitTools("GOOGLE_SHEETS", body),
- getToolkitTools("GOOGLE_DRIVE", body),
- ]);
+ // Get tools for all Composio toolkits (continue even if one fails)
+ const toolkitResults = await Promise.allSettled([
+ getToolkitTools("GOOGLE_SHEETS", body),
+ getToolkitTools("GOOGLE_DRIVE", body),
+ ]);
+
+ const googleSheetsTools = toolkitResults[0].status === "fulfilled"
+ ? toolkitResults[0].value
+ : {};
+ const googleDriveTools = toolkitResults[1].status === "fulfilled"
+ ? toolkitResults[1].value
+ : {};This approach ensures that if one toolkit is temporarily unavailable, users can still chat with other available toolkits.
📝 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 [googleSheetsTools, googleDriveTools] = await Promise.all([ | |
| getToolkitTools("GOOGLE_SHEETS", body), | |
| getToolkitTools("GOOGLE_DRIVE", body), | |
| ]); | |
| // Get tools for all Composio toolkits (continue even if one fails) | |
| const toolkitResults = await Promise.allSettled([ | |
| getToolkitTools("GOOGLE_SHEETS", body), | |
| getToolkitTools("GOOGLE_DRIVE", body), | |
| ]); | |
| const googleSheetsTools = toolkitResults[0].status === "fulfilled" | |
| ? toolkitResults[0].value | |
| : {}; | |
| const googleDriveTools = toolkitResults[1].status === "fulfilled" | |
| ? toolkitResults[1].value | |
| : {}; |
🤖 Prompt for AI Agents
In @lib/chat/setupToolsForRequest.ts around lines 34 - 37, Replace the
Promise.all call that fetches toolkit tools so a single toolkit failure does not
bail the whole request: call Promise.allSettled with the two
getToolkitTools("GOOGLE_SHEETS", body) and getToolkitTools("GOOGLE_DRIVE", body)
promises, inspect each result, set googleSheetsTools and googleDriveTools to the
fulfilled values (or an empty array/null) when rejected, and log the rejection
errors via your logging utility; ensure subsequent code that uses
googleSheetsTools/googleDriveTools can handle missing/empty values so the
request proceeds with available toolkits.
| if (userAccounts.items.length === 0) { | ||
| await authenticateToolkit(toolkitKey, accountId, options); | ||
| userAccounts = await composio.connectedAccounts.list({ | ||
| userIds: [accountId], | ||
| toolkitSlugs: [config.slug], | ||
| }); | ||
| } |
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.
Reconsider immediate retry after OAuth initiation.
The function calls authenticateToolkit and immediately retries listing accounts. However, OAuth authentication is an asynchronous process that requires user interaction in a browser redirect flow. The second list() call will likely still return zero accounts because the OAuth flow hasn't completed yet. This logic appears to assume the authentication completes synchronously, which isn't how OAuth works.
Consider whether this retry is necessary or if the authentication initiation should return a pending state that the caller handles differently. The current implementation may create confusion about when accounts will actually be available.
🤖 Prompt for AI Agents
In @lib/composio/getConnectedAccount.ts around lines 28 - 34,
getConnectedAccount currently calls authenticateToolkit and then immediately
re-invokes composio.connectedAccounts.list, which assumes OAuth completes
synchronously; instead, stop retrying immediately after authenticateToolkit.
Modify getConnectedAccount so that after calling authenticateToolkit it returns
a clear pending/auth-initiated result (or throws a specific AuthPending error)
that the caller can handle (e.g., trigger browser flow and poll later), or
alternatively document that the caller must re-run/get accounts after the OAuth
flow completes; do not call composio.connectedAccounts.list again directly after
authenticateToolkit. Ensure references to authenticateToolkit,
composio.connectedAccounts.list, and the userAccounts handling are updated
accordingly.
| const options: CreateConnectedAccountOptions = { | ||
| callbackUrl: `https://chat.recoupable.com?q=${encodeURIComponent( | ||
| latestUserMessageText | ||
| )}`, | ||
| }; |
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.
The callback URL is hardcoded to the production domain, which breaks OAuth redirect handling in development environments and is inconsistent with the client-side hook implementation.
View Details
📝 Patch Details
diff --git a/.env.example b/.env.example
index 1012a5c0..f5ead872 100644
--- a/.env.example
+++ b/.env.example
@@ -24,4 +24,6 @@ NEXT_PUBLIC_SPLASH_BACKGROUND_COLOR=FFFFFF
# COMPOSIO
COMPOSIO_API_KEY=
-COMPOSIO_GOOGLE_SHEETS_AUTH_CONFIG_ID=
\ No newline at end of file
+COMPOSIO_GOOGLE_SHEETS_AUTH_CONFIG_ID=
+COMPOSIO_GOOGLE_DRIVE_AUTH_CONFIG_ID=
+NEXT_PUBLIC_CHAT_BASE_URL=
\ No newline at end of file
diff --git a/lib/agents/composioAgent/getToolkitTools.ts b/lib/agents/composioAgent/getToolkitTools.ts
index 6fb78296..2179aca2 100644
--- a/lib/agents/composioAgent/getToolkitTools.ts
+++ b/lib/agents/composioAgent/getToolkitTools.ts
@@ -9,6 +9,7 @@ import {
} from "@/lib/composio/getConnectedAccount";
import { ComposioToolkitKey, getToolkitConfig } from "@/lib/composio/toolkits";
import { getLoginToolForToolkit } from "@/lib/tools/composio/loginTools";
+import { getCallbackUrl } from "@/lib/composio/getCallbackUrl";
/**
* Get tools for a Composio toolkit.
@@ -28,9 +29,7 @@ export async function getToolkitTools(
const latestUserMessageText = getLatestUserMessageText(messages);
const options: CreateConnectedAccountOptions = {
- callbackUrl: `https://chat.recoupable.com?q=${encodeURIComponent(
- latestUserMessageText
- )}`,
+ callbackUrl: getCallbackUrl(latestUserMessageText),
};
const composio = await getComposioClient();
diff --git a/lib/composio/getCallbackUrl.ts b/lib/composio/getCallbackUrl.ts
new file mode 100644
index 00000000..de69fcf9
--- /dev/null
+++ b/lib/composio/getCallbackUrl.ts
@@ -0,0 +1,19 @@
+/**
+ * Get the callback URL for Composio OAuth.
+ * Uses environment variable for the base URL to support dev and production environments.
+ *
+ * @param query - Optional query parameter (e.g., the latest user message text)
+ * @returns The callback URL for Composio OAuth redirects
+ */
+export function getCallbackUrl(query?: string): string {
+ // Use environment variable for base URL (set in .env or deployment config)
+ // This supports development (localhost:3000) and production (chat.recoupable.com)
+ const baseUrl =
+ process.env.NEXT_PUBLIC_CHAT_BASE_URL || "https://chat.recoupable.com";
+
+ if (query) {
+ return `${baseUrl}?q=${encodeURIComponent(query)}`;
+ }
+
+ return baseUrl;
+}
Analysis
Hardcoded production domain in Composio OAuth callback breaks development environments
What fails: getToolkitTools() in lib/agents/composioAgent/getToolkitTools.ts hardcodes the OAuth callback URL to https://chat.recoupable.com, preventing proper OAuth redirect handling in development environments where the application runs on http://localhost:3000.
How to reproduce:
- Set up local development environment (running on localhost:3000)
- Attempt to authenticate with Google Sheets or Google Drive via Composio
- When
setupToolsForRequest()is called server-side, it initiates OAuth with the hardcoded production domain as the callback URL - After OAuth completes, the user is redirected to the production domain instead of localhost
What happens vs expected:
- Current behavior: Server-side
getToolkitTools()storeshttps://chat.recoupable.comas the OAuth callback destination - Expected behavior: Should use the current environment's base URL (from
NEXT_PUBLIC_CHAT_BASE_URLenv variable or fallback to production), matching the client-sideuseComposioLogin.tswhich correctly useswindow.location.origin
Evidence:
- Commit 408ff1e explicitly states: "Use window.location.origin for dev/prod redirect compatibility" when introducing the client-side
useComposioLoginhook - The client-side code correctly implements this with:
const baseUrl = typeof window !== "undefined" ? window.location.origin : "https://chat.recoupable.com" - The server-side code was NOT updated to match, leaving it with a hardcoded production URL
- This creates an inconsistency where server-side OAuth initiation could interfere with the client-side dynamic flow
Fix implemented: Created lib/composio/getCallbackUrl.ts utility function that uses NEXT_PUBLIC_CHAT_BASE_URL environment variable to dynamically construct the callback URL, supporting both development and production environments. Updated getToolkitTools() to use this utility instead of hardcoding the domain.
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.
DRY principle - This component should already exist from google sheets login.
- why are you recreating existing components?
| // Use current origin so it works in both local dev and production | ||
| const baseUrl = typeof window !== "undefined" ? window.location.origin : "https://chat.recoupable.com"; |
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.
KISS principle - why not just always use https://chat.recoupable.com?
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.
KISS principle - is this hook really necessary?
- why not just directly import
useComposioLoginwherever you're usinguseGoogleDriveLogin?
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.
KISS principle - is this hook really necessary?
- why not just directly import useComposioLogin wherever you're using useGoogleSheetsLogin?
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.
incorrect file location
- actual:
lib/agents - required:
lib/composio
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.
actually, this should be moved server side in the recoup-api repo.
| */ | ||
| export async function authenticateToolkit( | ||
| toolkitKey: ComposioToolkitKey, | ||
| userId: string, |
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.
| userId: string, | |
| accountId: string, |
Summary by CodeRabbit
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.