Skip to content

Conversation

@mattsolo1
Copy link
Contributor

No description provided.

mattsolo1 and others added 14 commits November 5, 2025 10:47
This is the first implementation of gnomAD Assistant, an AI-powered chat interface
that helps users navigate and explore the gnomAD database.

Key changes:
- Created separate CopilotKit server to avoid GraphQL conflicts
- Added CopilotKit React dependencies to browser
- Integrated CopilotSidebar component with variant navigation action
- Added Copilot button to navigation bar
- Connected to gmd MCP tool server for gnomAD-specific functionality
- Updated pnpm-lock.yaml with new dependencies

The assistant can currently:
- Navigate to variant pages based on variant IDs or rsIDs
- Use the gmd tool server for additional gnomAD queries

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
… gnomAD Assistant

- Create new GnomadCopilot component with non-floating layout
- Chat panel takes up 1/3 of screen by default (was 50%)
- Add resizable functionality with drag handle
- Remove Copilot menu item from navbar
- Increase chat input font size to 14px
- Update initial greeting message

The assistant now integrates better with the page layout and provides
a more intuitive user experience with adjustable panel width.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add page context tracking to DocumentTitle using useCopilotReadable
- Pass gene/variant context from GenePage and VariantPage to assistant
- Add useGnomadCopilotActions hook for displaying variant data in chat
- Add styles for chat components to properly display tables and data

This enables the assistant to understand what the user is viewing
and display rich data components directly in the chat interface.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Change action name from 'showVariantInfo' to 'get_variant_summary'
- Update parameter names to match MCP tool (variantId -> variant_id)
- Add optional dataset parameter with fallback to current dataset

This fixes the variant table display in the chat interface by ensuring
the frontend action properly invokes the backend MCP tool.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Removed the get_variant_summary action and its associated useGnomadCopilotActions hook. This action is no longer needed in the CopilotKit integration.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add "Interpret this variant" suggestion on variant pages
- Set assistant chat window to be open by default
- Style suggestion chips with 14px font size to match chat UI
- Use conditional rendering to show suggestions only on relevant pages

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
…on registration

The CopilotSidebar component was registering a duplicate navigateToVariantPage action
which caused "Action argument unparsable" errors due to receiving malformed JSON.
Since this component is not imported anywhere and GnomadCopilot provides the same
functionality with a better split-screen layout, it's safe to remove.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add Progress component for displaying MCP tool activity logs
- Implement useMCPStateRender hook for inline tool state visualization
- Integrate agent state rendering into GnomadCopilot
- Remove old ActionMessage/CustomRenderMessage code in favor of new system
- Add collapsible parameter display and status indicators

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
mattsolo1 and others added 13 commits November 18, 2025 10:38
Add region context to MCP tools by passing region data to DocumentTitle
component and handling it in the context detection logic. This enables
the assistant to access genomic region information (chrom, start, stop).

Add example pill questions for Juha Genetics API queries across variant,
gene, and region pages. Users can now easily query for credible sets,
colocalizations, QTL data, and gene-disease associations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Create table column definitions for credible sets, colocalizations, and gene-disease data
- Implement JuhaToolsDisplay component with virtualized Grid tables for large datasets
- Add useJuhaActions hook defining 6 frontend MCP actions for Juha API tools
- Integrate Juha actions into GnomadCopilot
- Support proper sorting state management for each table type
- Fix React key uniqueness and variant link generation
- Add proper spacing to prevent visual overlap with LLM summaries

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…ent issues

Major changes:
- Integrate CopilotKit server into GraphQL API as colocalized service
- Upgrade CopilotKit packages from v1.0.0 to v1.10.6 (fixes duplicate request bug)
- Add MCP client with singleton pattern to support multi-turn conversations
- Configure nginx to disable caching for /api/copilotkit endpoint
- Add deployment scripts and documentation for CopilotKit artifacts
- Update Docker configuration to include gmd binary (linux/amd64) and Mendelian TSV
- Add comprehensive logging for CopilotKit requests and MCP tool executions
- Remove copilotkit-server from pnpm workspace (code moved to graphql-api)

Technical details:
- CopilotKit now runs as part of the GraphQL API server (not standalone)
- MCP client reuses single instance per pod to maintain conversation state
- GraphQL upgraded from v15 to v16 for CopilotKit compatibility
- Nginx proxy cache explicitly bypassed for chat endpoint
- Build artifacts prepared via deploy/scripts/prepare-copilotkit-artifacts.sh

Fixes:
- Fix duplicate HTTP requests caused by frontend/backend version mismatch
- Fix cached responses in production by disabling nginx caching for chat
- Fix missing Juha tools by using gmd binary from juha-api worktree
- Fix architecture mismatch by building linux/amd64 gmd binary for Docker

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add fullscreen toggle button to chat panel with expand/compress icons
- Implement three display modes: closed, side panel, and fullscreen
- Hide main toggle button when in fullscreen mode
- Only show resize handle in side panel mode
- Disable HTML element scrolling to prevent page-level scrollbars

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…ling

- Add settings modal with model selection and custom prompt configuration
- Replace bottom "Close Assistant" button with icon button in header
- Add close, settings, and fullscreen icon buttons to chat header
- Improve chat styling:
  - Fix input container width to prevent horizontal overflow
  - Add custom scrollbar styling (shows only when needed)
  - Increase suggestion chip font size to 14px for better readability
  - Add rounded corners to messages and input areas
  - Style suggestion chips with hover effects
- Apply gnomAD color scheme (#0d79d0) throughout chat UI
- Add proper overflow handling and box-sizing to prevent layout issues

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…y for gnomAD assistant

Implements comprehensive settings management for the CopilotKit assistant:

Backend changes:
- Created DynamicGeminiAdapter class to dynamically select AI model based on frontend parameters
- Added model logging to request/response middleware for better observability
- Support for Gemini 2.5/3 Flash and Pro models via forwardedParameters

Frontend changes:
- Moved model and prompt state management to App.tsx with localStorage persistence
- Added model badge displaying currently selected AI model with robot icon
- Added context badge showing current page context (gene/variant/region) and IDs
- Implemented saved prompts system with save/load/delete functionality
- Added settings modal with model selection and prompt management
- Fixed z-index layering to ensure proper modal display
- Connected settings UI to CopilotKit via forwardedParameters and useCopilotAdditionalInstructions

Storage keys:
- gnomad.copilot.model - Selected AI model
- gnomad.copilot.savedPrompts - Array of saved custom prompts
- gnomad.copilot.activePromptId - Currently active prompt ID

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…tion

- Add PostgreSQL-based chat history persistence for CopilotKit
  - Database schema with chat_threads and chat_messages tables
  - Database client with connection pooling and CRUD operations
  - Middleware hooks for message persistence in CopilotKit server
  - Thread management REST API endpoints (list, get, delete)
  - Health check endpoint for database connectivity

- Add chat history sidebar component
  - Displays thread list with titles and metadata
  - New chat button and thread selection
  - Integrated into fullscreen chat mode

- Fix environment variable configuration issues
  - Changed from CACHE_REDIS_URL/RATE_LIMITER_REDIS_URL to REDIS_HOST/REDIS_PORT
    (code was already using these legacy variables)
  - Added CopilotKit environment variables to start.sh
  - Fixed webpack proxy configuration for both GraphQL API and CopilotKit
  - Removed pathRewrite that was breaking API requests
  - Added IPv4 forcing (127.0.0.1) to avoid IPv6 connection issues
  - Fixed GNOMAD_API_URL for MCP server inside Docker container

- Improve Docker Compose configuration
  - Add PostgreSQL service for chat history
  - Fix Elasticsearch proxy cluster name (gnomad-v4)
  - Remove volume mounts to use compiled JavaScript (simpler, production-like)
  - Update to use correct environment variables

- Add CopilotKit utility scripts
  - init-db.sh: Initialize PostgreSQL schema
  - verify-copilot-build.sh: Prepare and verify CopilotKit artifacts
  - env.sh: Compartmentalized CopilotKit environment configuration

- Fix CopilotKit route ordering
  - Thread management endpoints must be registered before catch-all handler
  - Filter out messages without roles to avoid database constraint violations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…nstruction

This commit implements the ability to display historical chat messages when
users select a conversation from the chat history sidebar.

Key changes:
- Added useCopilotMessagesContext hook to manage CopilotKit's message state
- Implemented useEffect to fetch messages when threadId changes
- Added message class reconstruction to convert database JSON to proper
  CopilotKit message instances (TextMessage, ActionExecutionMessage, etc.)
- Added loading state UI to provide feedback during message fetching
- Added @copilotkit/runtime-client-gql dependency for message class imports

The critical fix was reconstructing message class instances from the database
objects, ensuring they have the required methods (like isResultMessage()) that
CopilotKit expects. This avoids serialization errors that occurred in previous
attempts.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
When users click "New Chat", the application now creates a thread record
in the database immediately, making it appear in the chat history sidebar.

Backend changes (graphql-api/src/copilotkit/server.ts):
- Added POST /api/copilotkit/threads endpoint to create/ensure threads
- Added express.json() middleware to parse JSON request bodies
- Endpoint accepts threadId and optional model parameters

Frontend changes (browser/src/App.tsx):
- Updated handleNewChat to be async and call the POST endpoint
- Creates thread in database before setting as active
- Includes error handling to still set threadId if API call fails
- Passes current selectedModel to backend

Frontend changes (browser/src/ChatHistorySidebar.tsx):
- Added currentThreadId as dependency to useEffect
- Sidebar now refreshes immediately when new chat is created
- New threads appear in sidebar list right away

This improves UX by making new chats immediately visible in the history
sidebar, even before any messages are sent.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Users can now delete individual chat threads from the history sidebar.
Each thread item now displays a "Delete" button that:

- Shows confirmation dialog before deleting
- Calls DELETE /api/copilotkit/threads/:threadId endpoint
- Removes thread from database and local state
- Automatically starts new chat if deleting current thread
- Prevents event bubbling to avoid triggering thread selection
- Shows error alert if deletion fails

UI changes:
- Added DeleteButton styled component with red hover effect
- Updated ThreadItem layout to flex with space-between
- Wrapped thread info in ThreadContent container for proper layout
- Button has subtle opacity that increases on hover

This gives users control over their chat history and allows cleanup
of unwanted conversations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add Auth0 authentication to the gnomAD Assistant, making chat threads
private and user-specific. Each authenticated user now has their own
isolated chat history.

Backend Changes:
- Add JWT validation middleware using express-oauth2-jwt-bearer and jose v4
- Update database layer to filter all operations by user_id
- Implement requestUserMap to pass userId context without modifying request body
- Protect all thread management API endpoints with JWT authentication
- Add thread ownership validation in CopilotKit runtime middleware
- Add comprehensive logging for debugging authentication flow

Frontend Changes:
- Integrate Auth0Provider for OAuth2/OIDC authentication
- Add token fetching with consent error handling
- Create Login and Logout components for authentication UI
- Update all API calls to include Authorization headers
- Display authenticated user info in settings modal
- Handle loading and error states during authentication

Configuration:
- Add Auth0 environment variables to Docker Compose configs
- Support toggling authentication via REACT_APP_AUTH0_ENABLE flag
- Configure webpack to inject Auth0 audience into browser bundle

Key Implementation Details:
- Used Map<threadId, userId> to avoid GraphQL validation errors
- Downgraded jose to v4 for CommonJS compatibility
- Added express.json() middleware before custom auth middleware
- Gracefully fallback to 'anonymous' userId when auth is disabled

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add development-specific Docker setup with hot reloading capabilities:
- Created api.dev.dockerfile with all dependencies including devDependencies
- Added @swc/core and @swc-node/register for ultra-fast TypeScript transpilation
- Configured Node.js built-in --watch mode (no nodemon needed)
- Mount graphql-api and dataset-metadata source directories for live code updates
- Optimized Docker build context with .dockerignore

Restart time is ~10s due to application initialization (GraphQL schema loading, service connections), with near-instant transpilation via SWC.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
mattsolo1 and others added 8 commits November 25, 2025 12:08
Implement comprehensive support for persisting and restoring tool execution
messages (ActionExecutionMessage and ResultMessage) in chat history:

Backend changes:
- Relax database schema to allow NULL for role and content fields
- Update database logic to handle all message types including tool messages
- Remove message filtering in server middleware to store complete history
- Add extensive logging for message types and field validation
- Fix init-db.sh script path bug

Frontend changes:
- Reconstruct tool messages from database with proper field handling
- Parse JSONB result and arguments fields when restoring messages
- Update render functions to handle stringified result data
- Add helper function for parsing results in Juha actions
- Fix VariantDisplay to handle both JSON data and text descriptions
- Filter empty threads from chat history sidebar

Known issues:
- Tool execution state restoration needs further investigation
- GraphQL validation errors when tool messages are in context

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Update hot reloading configuration to work with Docker volumes on macOS:
- Switch from Node's --watch to nodemon with legacyWatch mode
- Add nodemon.json configuration with polling for cross-platform compatibility
- Keep @swc-node/register for fast TypeScript transpilation

The legacyWatch option enables file polling, which is necessary for detecting
file changes in Docker volumes on macOS. Hot reloading now works successfully
with ~10s restart time (primarily application initialization).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…able

Refactor tool result handling to store large structured data payloads in a
separate `tool_results` table, preventing database bloat and payload size errors.

Backend changes (GraphQL API):
- Add `tool_results` table for storing large tool result payloads separately
- Add `tool_result_id` foreign key column to `chat_messages` table
- Implement result extraction logic to detect and store structured content
- Add API endpoint GET /api/copilotkit/tool_results/:resultId for fetching
- Parse JSON-stringified result fields before processing
- Derive parentMessageId from ResultMessage ID pattern (result-{actionId})
- Increase body-parser limit to 50MB to handle large payloads during save
- Add comprehensive logging for tool result processing

Frontend changes (Browser):
- Create useToolResult hook to centralize tool result fetching logic
- Refactor useGnomadVariantActions to use new VariantRenderComponent
- Refactor useJuhaActions to use new JuhaRenderComponent
- Fix React hooks error by converting render functions to proper components
- Simplify VariantDisplay to expect clean data format
- Simplify JuhaToolsDisplay to expect clean data format
- Update GnomadCopilot to not stringify ResultMessage result field

Build changes:
- Fix prepare-copilotkit-artifacts.sh path calculation for gmd source

Database schema:
- tool_results table with JSONB storage and user-based access control
- Unique constraint on (thread_id, message_id) to prevent duplicates
- Cascade delete on thread deletion, SET NULL on tool result deletion
- Indexes on user_id for efficient querying

This implementation follows a dual-channel pattern where the LLM receives
a text summary while the frontend receives rich structured data for
visualization, stored efficiently with on-demand fetching.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fixed issue where authenticated users' chat messages were being saved
with userId 'anonymous' instead of their actual user ID, preventing
proper data retrieval and thread ownership.

Root cause: The original requestUserMap was keyed by threadId, but
threadId wasn't available in req.body during JWT verification (it's
provided later by the CopilotKit runtime). This timing mismatch left
the map unpopulated.

Solution: Implemented request-scoped userId tracking using a
currentRequestUserId variable that's set during JWT verification and
captured by onBeforeRequest when the runtime provides the threadId.

Changes:
- Replaced broken requestUserMap with currentRequestUserId + threadUserIdMap
- Added handler wrapper to set/clear currentRequestUserId per request
- Updated onBeforeRequest to associate userId with threadId
- Updated onAfterRequest to retrieve userId from threadUserIdMap
- Simplified Express middleware to store userId on req.copilotUserId

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…gation tracking

Add comprehensive context tracking system that captures genes, variants, and regions
viewed during conversations, enabling rich chat history with meaningful summaries.

Backend changes:
- Add contexts JSONB column to chat_threads table with GIN index
- Implement smart title generation based on browsing context
- Add POST /api/copilotkit/threads/:threadId/context endpoint
- Track context changes and prevent duplicates
- Generate titles like "Chat about BRCA1, PCSK9" from visited entities

Frontend changes:
- Automatically capture initial context when starting new chats
- Track navigation changes and send context updates to backend
- Display context notification banner when user navigates (5s auto-dismiss)
- Redesign chat history sidebar with context pills showing genes/variants/regions
- Move sidebar to left side in fullscreen mode (320px wide)
- Implement optimistic UI to show new chats immediately
- Add query parameter support (?chat=fullscreen/side/closed)
- Fix message count to only include TextMessage types
- Eliminate flickering with silent background refreshes

User experience improvements:
- New chats appear instantly in history with current context
- Thread titles automatically reflect entities discussed
- Visual context pills help identify conversations at a glance
- Smooth transitions without loading spinners during navigation
- URL-based chat mode for shareable links and browser navigation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Replace modal-based settings with an inline view that takes over the chat
panel for a more seamless user experience.

Changes:
- Create new ChatSettingsView component encapsulating all settings UI
- Move settings from modal to full-screen panel that replaces chat content
- Remove old modal implementation and duplicate styled components
- Hide chat control buttons (close/fullscreen) when settings view is active
- Update state management from isSettingsModalOpen to isSettingsViewOpen
- Refactor handleSavePrompt to accept promptName as parameter
- Move promptName state local to ChatSettingsView component
- Integrate logout button into settings view for better UX

Benefits:
- More integrated, less disruptive settings experience
- Better code organization with dedicated settings component
- Cleaner separation of concerns
- Works consistently in both side-panel and fullscreen modes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add extensible section-based navigation to settings view with sidebar
navigation and content switching.

Changes:
- Add left sidebar navigation with section buttons (180px wide)
- Create General section containing all existing settings
- Add Favorites section stub with "coming soon" placeholder
- Implement section state management and content switching
- Add active state highlighting with blue accent and left border
- Update layout to flex-based system with independent scrolling
- Add SectionTitle component for consistent section headings

Architecture:
- Easy to extend with new sections via SettingsSection type
- Clean separation between navigation and content
- Render functions for each section (renderGeneralSection, renderFavoritesSection)
- Switch statement for content routing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add automatic title generation and updating for chat conversations using Google Generative AI. Titles are generated after the first few messages and updated periodically to reflect conversation content, with support for gene names and variant IDs.

- Add title generation service using Gemini 2.0 Flash model
- Implement async title generation triggered after message save
- Add database schema column to track title generation timing
- Configure thresholds for initial generation (4 messages) and updates (10 messages)
- Enhance sidebar UI to display titles on two lines for better readability
- Include gene/variant identification in title generation prompt

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Comment on lines +429 to +538
app.use('/api/copilotkit', express.json({ limit: '50mb' }), cors(corsOptions), async (req, res, next) => {
const startTime = Date.now();
const requestId = Math.random().toString(36).substring(7);

// --- Authorization for CopilotKit runtime ---
if (isAuthEnabled) {
try {
const authHeader = req.headers.authorization;
logger.info({
message: 'CopilotKit auth check',
requestId,
hasAuthHeader: !!authHeader,
authHeaderPrefix: authHeader?.substring(0, 20),
method: req.method,
path: req.path
});

if (!authHeader || !authHeader.startsWith('Bearer ')) {
logger.warn({ message: 'Missing or invalid auth header', requestId });
return res.status(401).json({ error: 'Authorization header is missing or invalid.' });
}

const token = authHeader.substring(7);
const payload = await verifyJwt(token);
const userId = payload?.sub;

logger.info({
message: 'JWT verified successfully',
requestId,
userId,
hasPayload: !!payload,
payloadKeys: payload ? Object.keys(payload) : []
});

if (!userId) {
logger.error({ message: 'User ID not found in token payload', requestId, payload });
return res.status(401).json({ error: 'User ID not found in token.' });
}

// Store userId on the request object so it's available to the runtime handler
// We can't modify req.body because GraphQL validates it strictly
(req as any).copilotUserId = userId;

logger.info({
message: 'Stored userId on request object',
requestId,
userId,
});
} catch (error: any) {
logger.error({
message: 'JWT validation error',
requestId,
error: error.message,
stack: error.stack
});
return res.status(401).json({ error: 'Invalid authentication token.' });
}
}

// Log the request with more detail about the conversation
let threadId: string | undefined;
let messageCount: number | undefined;
let model: string | undefined;
try {
const body = req.body || {};
threadId = body.threadId;
messageCount = body.messages?.length || 0;
model = body.forwardedParameters?.model || 'gemini-2.5-flash';
} catch (e) {
// ignore parsing errors
}

logger.info({
message: 'CopilotKit request',
requestId,
threadId,
messageCount,
model,
method: req.method,
path: req.path,
userAgent: req.headers['user-agent'],
});

// Wrap the response to log completion
const originalSend = res.send;
res.send = function(data) {
const duration = Date.now() - startTime;
logger.info({
message: 'CopilotKit response',
requestId,
model,
method: req.method,
path: req.path,
statusCode: res.statusCode,
duration: `${duration}ms`,
});
return originalSend.call(this, data);
};

(async () => handler(req, res))().catch((error) => {
logger.error({
message: 'CopilotKit error',
error: error.message,
stack: error.stack,
method: req.method,
path: req.path,
});
next(error);
});
});

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.

Copilot Autofix

AI 8 days ago

To mitigate denial-of-service risks, we should add a rate limiting middleware to the /api/copilotkit route (the handler at line 429). The best practice is to use the well-known express-rate-limit package, which is battle-tested and suitable for Express applications.

  • General solution: Install and import express-rate-limit. Create a rate limiter instance – a typical configuration could limit each IP to, e.g., 100 requests per 15 minutes. Then, add this limiter as a middleware in the handler chain for /api/copilotkit, placing it early in the middleware array (preferably before authentication and before heavy logic).
  • Detailed steps:
    1. Import express-rate-limit at the top of the file.
    2. Define a rateLimiter instance with suitable parameters (e.g., 100 requests per 15 minutes).
    3. Add the rateLimiter middleware to the /api/copilotkit route, before the main handler at line 429.
    4. This should be done only in the code block shown; do not change unrelated routes.
  • What to change:
    • Update the imports section to import express-rate-limit.
    • Insert the rate limiter definition after the other middleware/config values.
    • Add the rate limiter middleware as the first element for the .use('/api/copilotkit', ...) call.

Suggested changeset 2
graphql-api/src/copilotkit/server.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/graphql-api/src/copilotkit/server.ts b/graphql-api/src/copilotkit/server.ts
--- a/graphql-api/src/copilotkit/server.ts
+++ b/graphql-api/src/copilotkit/server.ts
@@ -1,4 +1,5 @@
 import express, { Application, Request, Response } from 'express';
+import rateLimit from 'express-rate-limit';
 import cors from 'cors';
 import {
   CopilotRuntime,
@@ -16,6 +17,15 @@
 
 // Authorization middleware
 const isAuthEnabled = process.env.REACT_APP_AUTH0_ENABLE === 'true';
+
+// Rate limiter for CopilotKit endpoints
+const copilotKitRateLimiter = rateLimit({
+  windowMs: 15 * 60 * 1000, // 15 minutes
+  max: 100, // Limit each IP to 100 requests per windowMs
+  standardHeaders: true,
+  legacyHeaders: false,
+  message: { error: "Too many requests, please try again later." }
+});
 const checkJwt = isAuthEnabled ? auth({
   issuerBaseURL: process.env.AUTH0_ISSUER_BASE_URL || '',
   audience: process.env.AUTH0_AUDIENCE || '',
@@ -426,7 +436,12 @@
   // Mount the handler on the provided Express app with its own CORS middleware
   // Add JSON body parser first so we can access req.body
   // Use a large limit to handle tool results with structured data
-  app.use('/api/copilotkit', express.json({ limit: '50mb' }), cors(corsOptions), async (req, res, next) => {
+  app.use(
+    '/api/copilotkit',
+    copilotKitRateLimiter,
+    express.json({ limit: '50mb' }),
+    cors(corsOptions),
+    async (req, res, next) => {
     const startTime = Date.now();
     const requestId = Math.random().toString(36).substring(7);
 
EOF
@@ -1,4 +1,5 @@
import express, { Application, Request, Response } from 'express';
import rateLimit from 'express-rate-limit';
import cors from 'cors';
import {
CopilotRuntime,
@@ -16,6 +17,15 @@

// Authorization middleware
const isAuthEnabled = process.env.REACT_APP_AUTH0_ENABLE === 'true';

// Rate limiter for CopilotKit endpoints
const copilotKitRateLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
standardHeaders: true,
legacyHeaders: false,
message: { error: "Too many requests, please try again later." }
});
const checkJwt = isAuthEnabled ? auth({
issuerBaseURL: process.env.AUTH0_ISSUER_BASE_URL || '',
audience: process.env.AUTH0_AUDIENCE || '',
@@ -426,7 +436,12 @@
// Mount the handler on the provided Express app with its own CORS middleware
// Add JSON body parser first so we can access req.body
// Use a large limit to handle tool results with structured data
app.use('/api/copilotkit', express.json({ limit: '50mb' }), cors(corsOptions), async (req, res, next) => {
app.use(
'/api/copilotkit',
copilotKitRateLimiter,
express.json({ limit: '50mb' }),
cors(corsOptions),
async (req, res, next) => {
const startTime = Date.now();
const requestId = Math.random().toString(36).substring(7);

graphql-api/package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/graphql-api/package.json b/graphql-api/package.json
--- a/graphql-api/package.json
+++ b/graphql-api/package.json
@@ -42,7 +42,8 @@
     "pg": "^8.11.3",
     "redis": "^3.1.1",
     "ts-migrate": "^0.1.35",
-    "typescript": "^5.0.4"
+    "typescript": "^5.0.4",
+    "express-rate-limit": "^8.2.1"
   },
   "devDependencies": {
     "@swc-node/register": "^1.10.9",
EOF
@@ -42,7 +42,8 @@
"pg": "^8.11.3",
"redis": "^3.1.1",
"ts-migrate": "^0.1.35",
"typescript": "^5.0.4"
"typescript": "^5.0.4",
"express-rate-limit": "^8.2.1"
},
"devDependencies": {
"@swc-node/register": "^1.10.9",
This fix introduces these dependencies
Package Version Security advisories
express-rate-limit (npm) 8.2.1 None
Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants