Skip to content

feat: Add --frontend-dev-mode CLI option for enhanced frontend development #1590

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 183 additions & 0 deletions FRONTEND_DEV_MODE_FEATURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# ADK FastAPI Frontend Development Mode

## Overview

The `--frontend-dev-mode` option is a new CLI flag for ADK FastAPI servers that significantly enhances the development experience when building frontend applications that connect to ADK agents. This feature automatically configures common development settings and provides additional debugging capabilities.

## Features

### 1. Automatic CORS Configuration
When `--frontend-dev-mode` is enabled, the server automatically adds CORS origins for common frontend development servers:

- `http://localhost:3000` - React, Next.js default
- `http://localhost:3001` - React alternative port
- `http://localhost:4200` - Angular CLI default
- `http://localhost:5173` - Vite default
- `http://localhost:8080` - Vue CLI default
- Plus `127.0.0.1` variants of all above

These are added in addition to any origins specified via `--allow_origins`.

### 2. Enhanced Error Responses
In frontend dev mode, error responses include detailed debugging information:

```json
{
"error": "Session not found",
"type": "HTTPException",
"traceback": "Traceback (most recent call last):\n File \"...\", line 123...",
"request_info": {
"method": "GET",
"url": "http://localhost:8000/apps/my-app/users/user123/sessions/session456",
"headers": {...}
}
}
```

### 3. Development Headers
All responses include helpful headers for frontend development:

- `X-Frontend-Dev-Mode: true` - Indicates dev mode is active
- `X-Server-Time: <timestamp>` - Server timestamp for debugging
- Enhanced CORS headers for preflight requests

### 4. Development Info Endpoint
A new `/dev-info` endpoint provides configuration information:

```json
{
"frontend_dev_mode": true,
"cors_origins": ["http://localhost:3000", "..."],
"server_time": 1234567890,
"features": {
"detailed_errors": true,
"dev_headers": true,
"auto_cors": true
}
}
```

## Usage

### Basic Usage

```bash
# Enable frontend dev mode with web UI
adk web --frontend-dev-mode /path/to/agents

# Enable frontend dev mode with API server only
adk api_server --frontend-dev-mode --port 8000 /path/to/agents
```

### Combined with Custom CORS Origins

```bash
# Add custom origins in addition to auto-configured dev origins
adk web --frontend-dev-mode --allow_origins https://myapp.com --allow_origins https://staging.myapp.com /path/to/agents
```

### Production vs Development

```bash
# Development - with all dev features enabled
adk web --frontend-dev-mode --reload /path/to/agents

# Production - clean configuration without dev features
adk web --allow_origins https://myapp.com --no-reload /path/to/agents
```

## Implementation Details

### Files Modified

1. **`src/google/adk/cli/cli_tools_click.py`**
- Added `--frontend-dev-mode` flag to `fast_api_common_options()`
- Updated `cli_web()` and `cli_api_server()` functions to accept and pass the parameter

2. **`src/google/adk/cli/fast_api.py`**
- Modified `get_fast_api_app()` to accept `frontend_dev_mode` parameter
- Added automatic CORS origin configuration logic
- Implemented development middleware for enhanced error handling and headers
- Added `/dev-info` endpoint

3. **`tests/unittests/cli/test_fast_api.py`**
- Added comprehensive tests for the new functionality

### Key Implementation Features

- **Backwards Compatible**: The feature is opt-in via CLI flag
- **Additive CORS**: Combines user-specified origins with auto-configured dev origins
- **Conditional Middleware**: Development features only active when flag is enabled
- **Error Safety**: Enhanced error handling doesn't break existing error responses

## Benefits for Frontend Developers

1. **Zero Configuration CORS**: No need to manually configure CORS for common dev servers
2. **Better Debugging**: Detailed error responses help identify issues quickly
3. **Development Visibility**: Headers and endpoints provide insight into server state
4. **Faster Iteration**: Automatic reload and dev-friendly settings speed up development

## Security Considerations

- **Development Only**: This feature should only be used in development environments
- **Detailed Errors**: Error responses include sensitive debugging information
- **Permissive CORS**: Automatically allows common localhost origins
- **Production Safety**: Feature is disabled by default and requires explicit opt-in

## Frontend Integration Examples

### React/Next.js (localhost:3000)
```javascript
// Automatically allowed when --frontend-dev-mode is enabled
const response = await fetch('http://localhost:8000/apps/my-app/users/user123/sessions');
```

### Angular (localhost:4200)
```typescript
// Automatically allowed when --frontend-dev-mode is enabled
this.http.get('http://localhost:8000/dev-info').subscribe(info => {
console.log('Server dev mode:', info.frontend_dev_mode);
});
```

### Vue/Vite (localhost:5173)
```javascript
// Check if server is in dev mode
const devInfo = await fetch('http://localhost:8000/dev-info').then(r => r.json());
if (devInfo.frontend_dev_mode) {
console.log('Server is in development mode');
}
```

## Migration Guide

### Existing Projects
No changes required for existing projects. The feature is opt-in and backwards compatible.

### New Projects
Consider using `--frontend-dev-mode` during development:

```bash
# Old way - manual CORS configuration
adk web --allow_origins http://localhost:3000 /path/to/agents

# New way - automatic dev configuration
adk web --frontend-dev-mode /path/to/agents
```

## Troubleshooting

### CORS Issues
- Verify `--frontend-dev-mode` is enabled
- Check `/dev-info` endpoint to see configured origins
- Ensure your frontend dev server is using a supported port

### Missing Debug Information
- Confirm `--frontend-dev-mode` flag is set
- Check response headers for `X-Frontend-Dev-Mode: true`
- Verify error responses include `traceback` field

### Performance Concerns
- Development middleware adds minimal overhead
- Use `--no-reload` in production environments
- Disable `--frontend-dev-mode` for production deployments
15 changes: 15 additions & 0 deletions src/google/adk/cli/cli_tools_click.py
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,17 @@ def decorator(func):
help="Optional. Any additional origins to allow for CORS.",
multiple=True,
)
@click.option(
"--frontend-dev-mode",
is_flag=True,
show_default=True,
default=False,
help=(
"Optional. Enable frontend development mode. Automatically configures "
"CORS for common frontend dev ports (3000, 3001, 4200, 5173, 8080), "
"enables detailed error responses, and sets up proxy-friendly headers."
),
)
@click.option(
"--log_level",
type=LOG_LEVELS,
Expand Down Expand Up @@ -566,6 +577,7 @@ def cli_web(
agents_dir: str,
log_level: str = "INFO",
allow_origins: Optional[list[str]] = None,
frontend_dev_mode: bool = False,
host: str = "127.0.0.1",
port: int = 8000,
trace_to_cloud: bool = False,
Expand Down Expand Up @@ -617,6 +629,7 @@ async def _lifespan(app: FastAPI):
artifact_service_uri=artifact_service_uri,
memory_service_uri=memory_service_uri,
allow_origins=allow_origins,
frontend_dev_mode=frontend_dev_mode,
web=True,
trace_to_cloud=trace_to_cloud,
lifespan=_lifespan,
Expand Down Expand Up @@ -656,6 +669,7 @@ def cli_api_server(
agents_dir: str,
log_level: str = "INFO",
allow_origins: Optional[list[str]] = None,
frontend_dev_mode: bool = False,
host: str = "127.0.0.1",
port: int = 8000,
trace_to_cloud: bool = False,
Expand Down Expand Up @@ -686,6 +700,7 @@ def cli_api_server(
artifact_service_uri=artifact_service_uri,
memory_service_uri=memory_service_uri,
allow_origins=allow_origins,
frontend_dev_mode=frontend_dev_mode,
web=False,
trace_to_cloud=trace_to_cloud,
),
Expand Down
81 changes: 79 additions & 2 deletions src/google/adk/cli/fast_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ def get_fast_api_app(
artifact_service_uri: Optional[str] = None,
memory_service_uri: Optional[str] = None,
allow_origins: Optional[list[str]] = None,
frontend_dev_mode: bool = False,
web: bool,
trace_to_cloud: bool = False,
lifespan: Optional[Lifespan[FastAPI]] = None,
Expand Down Expand Up @@ -245,15 +246,77 @@ async def internal_lifespan(app: FastAPI):
# Run the FastAPI server.
app = FastAPI(lifespan=internal_lifespan)

if allow_origins:
# Configure CORS based on frontend development mode and explicit origins
cors_origins = list(allow_origins) if allow_origins else []

if frontend_dev_mode:
# Add common frontend development server ports
dev_origins = [
"http://localhost:3000", # React, Next.js default
"http://localhost:3001", # React alternative
"http://localhost:4200", # Angular default
"http://localhost:5173", # Vite default
"http://localhost:8080", # Vue CLI default
"http://127.0.0.1:3000",
"http://127.0.0.1:3001",
"http://127.0.0.1:4200",
"http://127.0.0.1:5173",
"http://127.0.0.1:8080",
]
cors_origins.extend(dev_origins)

# Remove duplicates while preserving order
cors_origins = list(dict.fromkeys(cors_origins))

if cors_origins:
app.add_middleware(
CORSMiddleware,
allow_origins=allow_origins,
allow_origins=cors_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

# Add frontend development mode enhancements
if frontend_dev_mode:
from fastapi import Request, Response
from fastapi.responses import JSONResponse
import traceback

@app.middleware("http")
async def frontend_dev_middleware(request: Request, call_next):
try:
response = await call_next(request)

# Add helpful headers for frontend development
response.headers["X-Frontend-Dev-Mode"] = "true"
response.headers["X-Server-Time"] = str(int(time.time()))

# Add CORS headers for preflight requests if not already handled
if request.method == "OPTIONS":
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "*"

return response
except Exception as e:
# In development mode, provide detailed error information
error_detail = {
"error": str(e),
"type": type(e).__name__,
"traceback": traceback.format_exc() if frontend_dev_mode else None,
"request_info": {
"method": request.method,
"url": str(request.url),
"headers": dict(request.headers),
}
}
return JSONResponse(
status_code=500,
content=error_detail,
headers={"X-Frontend-Dev-Mode": "true"}
)

runner_dict = {}

eval_sets_manager = LocalEvalSetsManager(agents_dir=agents_dir)
Expand Down Expand Up @@ -312,6 +375,20 @@ async def internal_lifespan(app: FastAPI):
# initialize Agent Loader
agent_loader = AgentLoader(agents_dir)

@app.get("/dev-info")
def get_dev_info() -> dict[str, Any]:
"""Provides information about development mode settings for frontend developers."""
return {
"frontend_dev_mode": frontend_dev_mode,
"cors_origins": cors_origins if cors_origins else [],
"server_time": int(time.time()),
"features": {
"detailed_errors": frontend_dev_mode,
"dev_headers": frontend_dev_mode,
"auto_cors": frontend_dev_mode,
}
}

@app.get("/list-apps")
def list_apps() -> list[str]:
base_path = Path.cwd() / agents_dir
Expand Down
Loading