Architecture Diagrams
VibeSDK Architecture Diagrams
Current architecture
flowchart LR
U[User] <--> UI[VibeSDK UI on Workers]
UI <--> T[ThinkAgentCloudflare Think + Durable Object]
T <--> G[Models through AI Gateway]
T <--> S[SpaceDOworkspace and files]
S <--> A[Cloudflare Artifactscommits and history]
S --> B[worker-bundler]
B --> L[Worker Loader]
L --> P[Dynamic Worker preview]
P <--> F[App Facetisolated SQLite]Think runs the iterative model-and-tool loop. Its explicit tools edit files in SpaceDO, create Artifacts-backed restore points, deploy Dynamic Worker previews, inspect browser logs, and repair errors. SpaceDO is the workspace and file layer; Cloudflare Artifacts is the durable git and version-history layer.
Rollback applies a selected commit tree to the current branch, creates a new commit, and redeploys without rewriting history. Generated applications export an App Durable Object class that SpaceDO hosts as a Facet with isolated SQLite storage.
Legacy architecture reference
The diagrams below document the retired phase-based sandbox architecture. They are retained only as historical context and must not be used to describe the current Think, SpaceDO, Artifacts, Worker Loader, and App Facet implementation.
Presentation-Ready Architecture Diagram
Copy-paste this beautiful diagram directly into your slides
/* Detailed source-code truncated for AI context efficiency. */Detailed System Diagrams
1. Overall System Architecture
/* Detailed source-code truncated for AI context efficiency. */2. Hybrid Agent System Architecture
/* Detailed source-code truncated for AI context efficiency. */3. Authentication & User Management Flow
sequenceDiagram
participant User
participant Frontend
participant AuthController
participant OAuth
participant D1
participant JWT
User->>Frontend: Login Request
Frontend->>AuthController: POST /api/auth/providers
AuthController->>Frontend: Available auth methods
alt OAuth Flow
Frontend->>AuthController: POST /api/auth/oauth/github
AuthController->>OAuth: Redirect to GitHub
OAuth->>User: Authorization page
User->>OAuth: Grant permission
OAuth->>AuthController: Callback with code
AuthController->>OAuth: Exchange code for tokens
OAuth->>AuthController: User profile + tokens
AuthController->>D1: Store/update user
AuthController->>JWT: Generate tokens
JWT->>AuthController: Access/Refresh tokens
AuthController->>Frontend: Set HTTP-only cookies
else Email/Password Flow
Frontend->>AuthController: POST /api/auth/login
AuthController->>D1: Verify credentials
D1->>AuthController: User data
AuthController->>JWT: Generate tokens
JWT->>AuthController: Access/Refresh tokens
AuthController->>Frontend: Set HTTP-only cookies
end
Frontend->>User: Login success
Note over AuthController,D1: Session stored in D1JWT tokens in HTTP-only cookiesAutomatic token refresh4. Sandbox System & Deployment Pipeline
/* Detailed source-code truncated for AI context efficiency. */5. Database Schema & Relationships
/* Detailed source-code truncated for AI context efficiency. */
```mermaid
journey
title User Creates and Deploys an App
section Getting Started
Visit Homepage: 5: User
Enter App Description: 4: User
Start Generation: 5: User
section Code Generation
AI Creates Blueprint: 3: System
Phase-wise Implementation: 4: System
Real-time Code Review: 4: System
Error Detection & Fixing: 3: System
section Live Preview
Sandbox Container Starts: 5: System
Live Preview Available: 5: User
Runtime Error Detection: 4: System
Iterative Improvements: 4: User, System
section Quality Assurance
Static Analysis: 4: System
Code Review Cycle: 3: System
Auto-fix Critical Issues: 4: System
User Feedback Integration: 5: User
section Deployment
Resource Provisioning: 3: System
Template Parsing: 3: System
Cloudflare Workers Deploy: 5: System
Live App URL Generated: 5: User
section Post-Deployment
Save to Dashboard: 4: User
Share with Community: 3: User
GitHub Export: 4: User7. Real-time Communication Flow
sequenceDiagram
participant User
participant Frontend
participant Agent
participant AIGateway
participant Sandbox
participant WebSocket
User->>Frontend: Send message
Frontend->>Agent: WebSocket message
Agent->>WebSocket: Broadcast generation_started
WebSocket->>Frontend: Real-time update
loop Phase Generation
Agent->>AIGateway: Phase planning request
AIGateway->>Agent: Streaming response
Agent->>WebSocket: Broadcast phase_update
WebSocket->>Frontend: Live phase progress
end
loop Code Implementation
Agent->>AIGateway: Code generation request
AIGateway->>Agent: SCOF streaming format
Agent->>WebSocket: Broadcast file_generated
WebSocket->>Frontend: Live file updates
end
Agent->>Sandbox: Deploy to container
Sandbox->>Agent: Preview URL ready
Agent->>WebSocket: Broadcast preview_ready
WebSocket->>Frontend: Preview available
loop Quality Assurance
Sandbox->>Agent: Runtime errors detected
Agent->>AIGateway: Fix generation request
AIGateway->>Agent: Fixed code
Agent->>Sandbox: Update files
Agent->>WebSocket: Broadcast fixes_applied
WebSocket->>Frontend: Updated preview
end
User->>Frontend: Deploy to Cloudflare
Frontend->>Agent: Deploy request
Agent->>Sandbox: Trigger deployment
Sandbox->>Agent: Deployment complete
Agent->>WebSocket: Broadcast deployment_complete
WebSocket->>Frontend: Live app URL8. AI Operations Pipeline
flowchart TD
%% User Input
Input["๐ฃ๏ธ User PromptNatural Language Request"]
%% Planning Phase
subgraph "๐ Planning Phase"
TemplateSelection["๐ท๏ธ Template SelectionCloudflare Stack Templates"]
BlueprintGen["๐ Blueprint GenerationPRD + Architecture Design"]
end
%% State Machine Controller
subgraph "๐ค Deterministic State Machine"
StateMachine["โ๏ธ Agent State ControllergenerateAllFiles()"]
subgraph "๐ State Transitions"
PhaseGenerating["๐ PHASE_GENERATING"]
PhaseImplementing["๐ PHASE_IMPLEMENTING"]
Reviewing["๐ REVIEWING"]
Finalizing["โจ FINALIZING"]
Idle["โ
IDLE"]
end
end
%% Operations Layer
subgraph "๐ง AI Operations"
PhaseGenOp["๐ Phase GenerationDevelopment Phases"]
PhaseImplOp["๐ Phase ImplementationFile Generation + SCOF"]
CodeReviewOp["๐ Code ReviewIssue Detection"]
FastCodeFixerOp["โก Fast Code FixerQuick Issue Fixes"]
FileRegenOp["๐ ๏ธ File RegenerationSurgical Code Repairs"]
ScreenshotAnalysisOp["๐ท Screenshot AnalysisVisual Validation"]
UserConvOp["๐ฌ User ConversationFeedback Processing"]
end
%% Quality Assurance
subgraph "๐ Quality Assurance"
ReviewCycles["๐ Review CyclesUp to 5 iterations"]
IssueCheck{{"โ ๏ธ Issues Found?"}}
StaticAnalysis["๐ Static AnalysisCode Validation"]
end
%% External Integration
subgraph "๐ค AI Gateway"
AIGateway["๐ช Cloudflare AI GatewayMulti-Provider Router"]
subgraph "๐ AI Providers"
Gemini["๐ Gemini (Primary)"]
GPT["๐ง GPT-4"]
Claude["๐ญ Claude"]
Cerebras["๐งช Cerebras"]
end
end
%% Main Flow
Input --> TemplateSelection
TemplateSelection --> BlueprintGen
BlueprintGen --> StateMachine
%% State Machine Flow
StateMachine --> PhaseGenerating
PhaseGenerating --> PhaseImplementing
PhaseImplementing --> Reviewing
Reviewing --> Finalizing
Finalizing --> Idle
%% Operations Execution
PhaseGenerating --> PhaseGenOp
PhaseImplementing --> PhaseImplOp
Reviewing --> CodeReviewOp
%% Quality Control Loop
CodeReviewOp --> ReviewCycles
ReviewCycles --> IssueCheck
IssueCheck -->|"โ
Yes"| FastCodeFixerOp
IssueCheck -->|"โ
Yes"| FileRegenOp
IssueCheck -->|"โ No"| StaticAnalysis
FastCodeFixerOp --> PhaseImplementing
FileRegenOp --> PhaseImplementing
%% User Interaction
UserConvOp --> PhaseGenerating
ScreenshotAnalysisOp --> CodeReviewOp
%% AI Integration
TemplateSelection --> AIGateway
BlueprintGen --> AIGateway
PhaseGenOp --> AIGateway
PhaseImplOp --> AIGateway
CodeReviewOp --> AIGateway
FastCodeFixerOp --> AIGateway
FileRegenOp --> AIGateway
ScreenshotAnalysisOp --> AIGateway
UserConvOp --> AIGateway
AIGateway --> Gemini
AIGateway --> GPT
AIGateway --> Claude
AIGateway --> Cerebras
%% Enhanced Styling
classDef planning fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#000
classDef statemachine fill:#fff3e0,stroke:#f57c00,stroke-width:3px,color:#000
classDef states fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#000
classDef operations fill:#e8f5e8,stroke:#388e3c,stroke-width:2px,color:#000
classDef quality fill:#ffebee,stroke:#d32f2f,stroke-width:2px,color:#000
classDef ai fill:#ff9900,stroke:#cc7a00,stroke-width:3px,color:#fff
classDef decision fill:#fff8e1,stroke:#f57f17,stroke-width:2px,color:#000
classDef success fill:#66ff66,stroke:#2e7d32,stroke-width:3px,color:#000
classDef providers fill:#fce4ec,stroke:#c2185b,stroke-width:2px,color:#000
class Input,TemplateSelection,BlueprintGen planning
class StateMachine statemachine
class PhaseGenerating,PhaseImplementing,Reviewing,Finalizing states
class Idle success
class PhaseGenOp,PhaseImplOp,CodeReviewOp,FastCodeFixerOp,FileRegenOp,ScreenshotAnalysisOp,UserConvOp operations
class ReviewCycles,StaticAnalysis quality
class IssueCheck decision
class AIGateway ai
class Gemini,GPT,Claude,Cerebras providers9. Technology Stack Overview
graph TB
subgraph "Frontend Layer"
React[React 18]
Vite[Vite Build Tool]
TailwindCSS[Tailwind CSS]
ShadcnUI[shadcn/ui Components]
ReactRouter[React Router]
end
subgraph "Backend Layer"
CFWorkers[Cloudflare Workers]
AgentsSDK[Cloudflare Agents SDK]
TypeScript[TypeScript]
HonoRouter[Hono Router]
AuthMiddleware[Auth Middleware]
BaseController[Base Controller]
end
subgraph "Data Layer"
D1[Cloudflare D1 SQLite]
Drizzle[Drizzle ORM]
KV[Cloudflare KV]
R2[Cloudflare R2]
DatabaseService[Database Service]
UserService[User Service]
AppService[App Service]
AuthService[Auth Service]
end
subgraph "AI & External Services"
AIGateway[Cloudflare AI Gateway]
OpenAI[OpenAI GPT-4]
GitHubAPI[GitHub API]
OAuth[OAuth Providers]
end
subgraph "Infrastructure"
CFContainers[Cloudflare Containers]
SandboxSDK[Cloudflare Sandbox SDK]
WebSockets[WebSocket API]
WorkersAnalytics[Workers Analytics]
end
React --> CFWorkers
Vite --> React
TailwindCSS --> React
ShadcnUI --> React
ReactRouter --> React
CFWorkers --> AgentsSDK
TypeScript --> CFWorkers
HonoRouter --> CFWorkers
AuthMiddleware --> CFWorkers
BaseController --> CFWorkers
CFWorkers --> DatabaseService
DatabaseService --> Drizzle
Drizzle --> D1
DatabaseService --> UserService
DatabaseService --> AppService
DatabaseService --> AuthService
CFWorkers --> KV
CFWorkers --> R2
CFWorkers --> AIGateway
AIGateway --> OpenAI
CFWorkers --> GitHubAPI
CFWorkers --> OAuth
CFWorkers --> CFContainers
CFContainers --> SandboxSDK
CFWorkers --> WebSockets
CFWorkers --> WorkersAnalytics
style CFWorkers fill:#ff9900
style AgentsSDK fill:#ff9900
style D1 fill:#ff9900
style AIGateway fill:#ff9900
style CFContainers fill:#ff9900/* Detailed source-code truncated for AI context efficiency. */
bun install
bun run setup
bun run dev
bun run typecheck
bun run lint
bun run test
bun run build/* Detailed source-code truncated for AI context efficiency. */
# LLM Providers
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
GOOGLE_AI_STUDIO_API_KEY=...
# Authentication
JWT_SECRET=your-secret-key
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...
# Cloudflare
CLOUDFLARE_ACCOUNT_ID=...
CLOUDFLARE_API_TOKEN=...
# Sandbox Service
SANDBOX_SERVICE_URL=https://sandbox.example.com
SANDBOX_SERVICE_TOKEN=...Setup:
# Install dependencies
npm install
# Setup local D1 database
npm run db:migrate:local
# Start dev servers
npm run dev # Frontend (Vite)
npm run dev:worker # Backend (Wrangler)**Common Development Tasks**
Task: Change LLM model for an operation
File: /worker/agents/inferutils/config.ts
export const AGENT_CONFIG = {
blueprint: {
name: GEMINI_2_5_PRO, // Change this
reasoning_effort: 'medium',
max_tokens: 64000,
temperature: 0.7
},
// ... other operations
};Task: Modify system prompt for conversation agent
File: /worker/agents/operations/UserConversationProcessor.ts
- Line ~50: System prompt starts
- Defines Orange AI personality, tool usage rules, behavior
Task: Add new WebSocket message
See "Getting Started - Common Tasks" section below (line 1605)
Task: Debug Durable Object state
In code:
// In simpleGeneratorAgent.ts
this.logger().info('Current state', {
devState: this.state.currentDevState,
filesCount: Object.keys(this.state.generatedFilesMap).length,
currentPhase: this.state.currentPhase
});Via Cloudflare dashboard:
- Go to Workers & Pages โ Durable Objects
- Find your DO instance
- View SQLite database directly
๐ฏ Core Principles & Non-Negotiable Rules
**1. Strict Type Safety**
- โ NEVER use
anytype - find or create proper types - โ
All frontend types imported from
@/api-types(which re-exports from worker) orshared/types/ - โ Search codebase for existing types before creating new ones
- โ Extend/compose existing types rather than duplicating
Type Import Pattern:
// โ
CORRECT - Single source of truth
import { BlueprintType, WebSocketMessage } from '@/api-types';
// โ WRONG - Direct worker imports in frontend
import { BlueprintType } from 'worker/agents/schemas';**2. DRY Principle**
- Search for similar functionality before implementing
- Extract reusable utilities, hooks, and components
- Never copy-paste code - refactor into shared functions
**3. Follow Existing Patterns**
- Frontend APIs: All defined in
/src/lib/api-client.ts - Backend Routes: Controllers in
worker/api/controllers/, routes inworker/api/routes/ - Database Services: In
worker/database/services/ - Types: Shared types in
shared/types/, API types insrc/api-types.ts
**4. File Naming Conventions**
- React Components:
PascalCase.tsx - Utilities/Hooks:
kebab-case.ts - Backend Services:
PascalCase.ts - Match the naming style of surrounding files
**5. Code Quality Standards**
- โ Production-ready code only - no TODOs or placeholders
- โ Proper TypeScript types with no implicit any
- โ Clean, maintainable code
- โ No hacky workarounds
- โ No overly verbose AI-like comments
- โ No emojis in code (only in markdown docs)
**6. Comments Style**
// โ
GOOD - Explains code's purpose
// Calculate exponential backoff with max cap
const delay = Math.min(Math.pow(2, attempt) * 1000, 30000);
// โ BAD - Verbose AI narration
// Here we are calculating the delay using exponential backoff...๐๏ธ Project Architecture
**Tech Stack**
- Frontend: React 18, TypeScript, Vite, TailwindCSS, React Router v7
- Backend: Cloudflare Workers, Durable Objects, D1 (SQLite)
- AI/LLM: OpenAI, Anthropic, Google AI Studio (Gemini)
- WebSocket: PartySocket for real-time communication
- Sandbox: Custom container service with CLI tools
- Templates: Project scaffolding system with template catalog
**Complete Directory Structure**
/* Detailed source-code truncated for AI context efficiency. */๐ฌ Chat View Architecture
**Core Component:** `/src/routes/chat/chat.tsx`
Layout:
- Left Panel (40%): Chat messages, phase timeline, deployment controls, chat input
- Right Panel (60%): Editor view, Preview iframe, or Blueprint markdown
**State Management:** `/src/routes/chat/hooks/use-chat.ts`
This hook manages all chat state:
{
files: FileType[] // Generated files
phaseTimeline: PhaseTimelineItem[] // Phase progress
messages: ChatMessage[] // Chat history
websocket: WebSocket // Real-time connection
isGenerating: boolean // Generation state
previewUrl: string // Preview deployment URL
// ... deployment, blueprint, bootstrap state
}**WebSocket Message Handler**
Location: /src/routes/chat/utils/handle-websocket-message.ts
Critical Messages:
agent_connected- Restore full state on connectconversation_state- Load chat history with deduplicationfile_generating/file_generated- File generation progressphase_implementing/phase_implemented- Phase progressdeployment_completed- Preview URL readyconversation_response- AI message (streaming or complete)generation_stopped- User cancelled, mark phases as "cancelled"
**Phase Timeline**
Component: /src/routes/chat/components/phase-timeline.tsx
Status States:
generating- Active (orange spinner)validating- Code review (blue spinner)completed- Success (green checkmark)cancelled- Interrupted (orange X)error- Failed (red alert)
**Message Deduplication**
Problem: Tool execution causes duplicate AI messages
Solution: Multi-layer approach
- Backend skips redundant LLM calls (empty tool results)
- Frontend utilities (
deduplicate-messages.ts) for live and restored messages - System prompt teaches LLM not to repeat
๐ง Backend Architecture
**Durable Objects Pattern**
Each chat session is a Durable Object instance:
class SimpleCodeGeneratorAgent implements DurableObject {
// Persisted in SQLite
private state: CodeGenState;
// In-memory only (ephemeral)
private currentAbortController?: AbortController;
private deepDebugPromise: Promise<any> | null = null;
}/* Detailed source-code truncated for AI context efficiency. */
/* Detailed source-code truncated for AI context efficiency. *//* Detailed source-code truncated for AI context efficiency. */
git clone https://vibesdk.com/git/{agentId}/* Detailed source-code truncated for AI context efficiency. */
await git.commit([], 'feat: Add authentication');**2. reset(ref, options?)**
Aligns with: git reset --hard <commit>
await git.reset('abc123', { hard: true });
// Moves HEAD to commit, updates working directory
// No new commit created (destructive)Behavior:
- Moves HEAD to specified commit
- Updates working directory (hard: true by default)
- Does NOT create a new commit
- Triggers
onFilesChangedCallback
**3. log(limit?)**
Query commit history - standard git log.
**4. show(oid)**
Show commit details - files changed in commit.
**5. setOnFilesChangedCallback(callback)**
Register callback to be notified after git operations that change files.
git.setOnFilesChangedCallback(() => {
fileManager.syncGeneratedFilesMapFromGit();
});**6. getAllFilesFromHead()**
Get all files from HEAD commit for syncing.
const files = await git.getAllFilesFromHead();
// Returns: [{ filePath: string, fileContents: string }]**FileManager Sync Pattern**
File: /worker/agents/services/implementations/FileManager.ts
FileManager is self-contained - it registers with GitVersionControl during construction and auto-syncs after git operations.
constructor(stateManager, getTemplateDetailsFunc, git) {
// Auto-register callback with git
this.git.setOnFilesChangedCallback(() => {
this.syncGeneratedFilesMapFromGit();
});
}
private async syncGeneratedFilesMapFromGit(): Promise<void> {
// Get all files from HEAD commit
const gitFiles = await this.git.getAllFilesFromHead();
// Preserve existing file purposes
const oldMap = this.stateManager.getState().generatedFilesMap;
// Build new map
const newMap = {};
for (const file of gitFiles) {
newMap[file.filePath] = {
...file,
filePurpose: oldMap[file.filePath]?.filePurpose || 'Generated file',
lastDiff: ''
};
}
// Update state
this.stateManager.setState({ generatedFilesMap: newMap });
}Flow:
- FileManager constructed โ Registers callback with git
- User performs operations โ Dual-write continues (map + git)
- User calls git reset/checkout โ Git modifies files
- Git calls callback โ FileManager.syncGeneratedFilesMapFromGit()
- Sync reads from HEAD โ Updates generatedFilesMap
- State synchronized โ
**Git Tool - Access Control**
Location: /worker/agents/tools/toolkit/git.ts
The git tool has parameterized access control - different commands available in different contexts.
**Tool Creation**
export function createGitTool(
agent: CodingAgentInterface,
logger: StructuredLogger,
options?: { excludeCommands?: GitCommand[] }
): ToolDefinition<...> {
const allowedCommands = options?.excludeCommands
? allCommands.filter(cmd => !options.excludeCommands!.includes(cmd))
: allCommands;
// Dynamic enum and description based on allowed commands
return {
function: {
enum: allowedCommands,
? "... WARNING: reset is destructive!"
: "...",
}
};
}**Access by Context**
| Context | Available Commands | File | Notes |
|---|---|---|---|
| User Conversations | commit, log, show | /worker/agents/tools/customTools.ts (line 56) |
โ Safe - no destructive ops |
| Deep Debugger | commit, log, show, reset | /worker/agents/tools/customTools.ts (line 71) |
โ ๏ธ Full access with warnings |
User Conversations:
// Safe version - no reset
createGitTool(agent, logger, { excludeCommands: ['reset'] })Deep Debugger:
// Full access - includes reset
createGitTool(session.agent, logger) // No restrictions**Reset Command - Safety**
Deep debugger prompt warnings:
- Marked as UNTESTED and DESTRUCTIVE
- Only use when:
- User explicitly requests it
- Tried everything else
- Absolutely certain it's necessary
- Must warn user before using
- Prefer alternatives: regenerate_file, generate_files
**Why This Architecture?**
โ
Single implementation - DRY principle maintained
โ
Type-safe - TypeScript enforces valid commands
โ
Context-aware - Different access in different contexts
โ
Flexible - Easy to add more restrictions
โ
Safe default - Users can't accidentally reset commits
โ
Git CLI semantics - Aligns with actual git behavior
**Removed Methods**
inferPurposeFromPath()- Removed as requestedrevert()- Was creating incorrect "revert commits"restoreCommit()- Renamed to internal helperreadFilesFromCommit
**Why These Limits?**
MAX_PHASES = 12:
- Prevents infinite generation loops
- Forces focused, efficient implementation
- Keeps projects manageable
MAX_TOOL_CALLING_DEPTH = 7:
- Prevents infinite recursion
- Typically need 2-3 levels max
- Safety against runaway LLM behavior
MAX_IMAGES_PER_MESSAGE = 2:
- Balance between utility and cost
- Vision API costs are high
- Usually 1-2 images sufficient for context
MAX_LLM_MESSAGES = 200:
- Prevents conversation from growing unbounded
- Compactification kicks in before this
- Typical session has 20-50 messages
**State Machine - Detailed Flow**
States: Defined in CurrentDevState enum
/* Detailed source-code truncated for AI context efficiency. *//* Detailed source-code truncated for AI context efficiency. */
// Structure in /worker/agents/tools/toolkit/{tool-name}.ts
export function createToolName(agent: CodingAgentInterface, logger: StructuredLogger) {
return {
type: 'function',
function: {
name: 'tool_name',
parameters: { /* JSON schema */ }
},
implementation: async (args) => {
// Tool logic here
return result;
}
};
}**To Add a New Tool:**
- Create
/worker/agents/tools/toolkit/my-tool.ts - Export
createMyTool(agent, logger)function - Import in
/worker/agents/tools/customTools.ts - Add to either
buildTools()(conversation) orbuildDebugTools()(debugging) - Tool automatically available to LLM
**Diagnostic Priority (in system prompt)**
- run_analysis first (fast, no user interaction needed)
- get_runtime_errors second (focused errors)
- get_logs last resort (verbose, cumulative)
Can fix multiple files in parallel - regenerate_file called simultaneously on different files
Concurrency: Cannot run while code generation active - checked via agent.isCodeGenerating()
๐ WebSocket Communication
**Connection Flow**
- User visits
/chat/:chatId - Frontend calls
apiClient.connectToAgent(chatId) - API returns
websocketUrl - Frontend connects via PartySocket
- Backend sends
agent_connectedwith full state - Frontend restores UI
**State Restoration**
case 'agent_connected': {
// Backend sends snapshot
setState(message.state);
websocket.send({ type: 'get_conversation_state' });
}
case 'conversation_state': {
// Restore with deduplication
const deduplicated = deduplicateMessages(message.messages);
setMessages(prev => [...prev, ...deduplicated]);
}**Streaming Pattern**
// Backend sends chunks
ws.send({
type: 'conversation_response',
conversationId: 'abc',
message: 'chunk',
isStreaming: true,
});
// Frontend updates in place
setMessages(prev => updateOrAppendMessage(prev, id, content));๐ ๏ธ Implementation Patterns
**Adding New API Endpoint**
1. Define types (src/api-types.ts):
export interface GetFeatureRequest {
id: string;
}
export interface GetFeatureResponse {
feature: Feature;
}2. Add to API client (src/lib/api-client.ts):
export const apiClient = {
async getFeature(req: GetFeatureRequest): Promise<GetFeatureResponse> {
const response = await fetch('/api/features', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(req),
});
if (!response.ok) throw new ApiError(response);
return response.json();
},
};3. Create service (worker/database/services/FeatureService.ts):
export class FeatureService {
constructor(private env: Env) {}
async getFeature(id: string): Promise<Feature> {
// Database logic
}
}4. Create controller (worker/api/controllers/feature/controller.ts):
export const featureController = {
async getFeature(c: Context<AppEnv>) {
const body = await c.req.json<GetFeatureRequest>();
const service = new FeatureService(c.env);
const feature = await service.getFeature(body.id);
return c.json({ feature });
},
};5. Add route (worker/api/routes/feature-routes.ts):
export const featureRoutes = new Hono<AppEnv>();
featureRoutes.post('/', featureController.getFeature);6. Register in main router (worker/api/routes/index.ts):
router.route('/api/features', featureRoutes);**Creating Custom Hook**
Pattern: /src/hooks/use-{feature}.ts
export function useFeature(params: FeatureParams) {
const [data, setData] = useState<FeatureData | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
async function fetch() {
try {
const result = await apiClient.getFeature(params);
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}
fetch();
}, [params]);
const refetch = useCallback(() => {
setLoading(true);
// ... refetch logic
}, [params]);
return { data, loading, error, refetch };
}**Adding LLM Tool**
1. Create tool file (worker/agents/tools/toolkit/my-tool.ts):
export function createMyToolDefinition() {
return {
type: 'function' as const,
function: {
name: 'my_tool',
parameters: {
type: 'object',
properties: {
param: { type: 'string', description: 'Param description' },
},
required: ['param'],
},
},
};
}
export async function myToolImplementation(
args: { param: string },
context: ToolContext,
streamCb?: StreamCallback
): Promise<ToolResult> {
// Check concurrency if needed
if (context.agent.isCodeGenerating()) {
return { error: 'GENERATION_IN_PROGRESS' };
}
// Implementation
const result = await doWork(args.param);
return { result };
}2. Register tool (worker/agents/tools/customTools.ts):
import { createMyToolDefinition, myToolImplementation } from './toolkit/my-tool';
export function buildTools(agent: CodingAgentInterface) {
return [
// ... existing tools
createTool(createMyToolDefinition(), myToolImplementation),
];
}๐งช Testing Patterns
**Frontend Tests**
- Component tests in
__tests__/directories - Integration tests for hooks
- E2E tests with Playwright (if applicable)
**Backend Tests**
- Unit tests for services
- Integration tests for API endpoints
- Tool execution tests
๐ Documentation Standards
**Code Comments**
- Explain WHY, not WHAT (code should be self-documenting)
- Keep comments brief and to the point
- Update comments when code changes
- No emojis in code comments
**Type Documentation**
// โ
GOOD
/**
* Represents a generated file in the chat interface.
* Contains both metadata and content.
*/
export interface FileType {
filePath: string;
fileContents: string;
isGenerating: boolean;
}
// โ BAD
// This is a file type that we use to represent files
export interface FileType { ... }๐ Debugging Guide
**Frontend Debugging**
- Use React DevTools for component state
- Check browser console for errors
- Monitor WebSocket messages in Network tab
- Use Debug Panel in chat interface
**Backend Debugging**
- Check Cloudflare Workers logs
- Use
wrangler tailfor live logs - Add strategic console.log with prefixes:
[TOOL_CALL_DEBUG],[WS_DEBUG] - Check DO storage for persisted state
**Common Issues**
Empty Deep Debug Transcript:
- Check
max_tokensis sufficient (32000+) - Verify tool calls are completing
- Check for abort signals
Duplicate Messages:
- Verify deduplication utilities are used
- Check backend history management
- Ensure tool results aren't causing re-calls
WebSocket Disconnects:
- Check retry logic in
use-chat.ts - Verify DO isn't being evicted prematurely
- Check for abort controller issues
๐ฆ Deployment
**Frontend**
- Built with Vite
- Deployed as static assets
- Served by Cloudflare Pages or Workers
**Backend**
- Deployed via Wrangler
- Durable Objects for stateful agents
- D1 for persistent database
- KV for caching (if used)
**Database Migrations**
# Generate migration
npm run db:generate
# Apply migrations (local)
npm run db:migrate:local
# Apply migrations (production)
npm run db:migrate:remote๐ Continuous Improvement
**Keep This Document Updated**
When you:
- Add new features or components
- Discover undocumented patterns
- Find inaccuracies or outdated info
- Learn domain-specific knowledge
- Identify new best practices
Update sections:
- Add to relevant section
- Create new section if needed
- Mark outdated info with โ ๏ธ and correction
- Add examples for clarity
- Keep it concise but complete
**Document Structure**
This guide is organized by:
- Core principles (rules that never change)
- Architecture (how things are structured)
- Patterns (how to implement features)
- Examples (concrete implementations)
Keep this structure when adding content.
๐ WebSocket Communication - Complete Reference
**WebSocket Message Types**
Location: /worker/agents/constants.ts
**Request Messages (Frontend โ Backend):**
WebSocketMessageRequests:
- GENERATE_ALL: 'generate_all' // Start code generation
- DEPLOY: 'deploy' // Deploy to Cloudflare Workers
- PREVIEW: 'preview' // Deploy to sandbox preview
- STOP_GENERATION: 'stop_generation' // Cancel current operation
- RESUME_GENERATION: 'resume_generation' // Resume paused generation
- USER_SUGGESTION: 'user_suggestion' // User message (conversational AI)
- CLEAR_CONVERSATION: 'clear_conversation' // Reset chat history
- GET_CONVERSATION_STATE: 'get_conversation_state' // Request history
- GET_MODEL_CONFIGS: 'get_model_configs' // Request model info
- CAPTURE_SCREENSHOT: 'capture_screenshot' // Capture preview screenshot
- GITHUB_EXPORT: 'github_export' // DEPRECATED - use OAuth flow**Response Messages (Backend โ Frontend):**
/* Detailed source-code truncated for AI context efficiency. */**WebSocket Message Flow Examples**
**1. Code Generation Flow:**
User clicks "Generate" button
โ
Frontend: GENERATE_ALL
โ
Backend: generation_started
โ
[For each phase]
Backend: phase_generating (LLM thinking)
Backend: phase_generated (plan ready)
Backend: phase_implementing (files starting)
[For each file]
Backend: file_generating
Backend: file_chunk_generated (streaming)
Backend: file_generated
Backend: deployment_started
Backend: deployment_completed (preview URL)
Backend: code_reviewing (static analysis)
Backend: code_reviewed (results)
Backend: phase_implemented (phase done)
โ
Backend: generation_complete**2. User Conversation Flow:**
User types message โ clicks send
โ
Frontend: USER_SUGGESTION { message, images? }
โ
Backend: conversation_response { isStreaming: true } (chunks)
โ
[If tool calls]
Backend: conversation_response { tool: { name, status: 'start' } }
Backend: conversation_response { tool: { name, status: 'success', result } }
โ
Backend: conversation_response { isStreaming: false } (final)**3. Abort Generation Flow:**
User clicks abort button
โ
Frontend: STOP_GENERATION
โ
Backend:
- Calls agent.cancelCurrentInference()
- Aborts active AbortController
- Sets shouldBeGenerating = false
โ
Backend: generation_stopped
โ
Frontend:
- Marks active phases as 'cancelled'
- Shows orange X icon
- Disables abort button**Critical State Flags**
**`shouldBeGenerating` Flag**
Purpose: Persistent intent to generate code, survives page refreshes.
When set to true:
- User clicks "Generate" button
- User resumes generation
When set to false:
- User clicks "Stop" button
- Generation completes successfully
- Generation fails permanently
Why it matters:
- On page refresh, if
shouldBeGenerating=trueand no active generation โ restart - Prevents abandoned generation sessions
- Used by frontend to show "generating" vs "cancelled" phases
๐ค Conversational AI System ("Orange")
**Purpose**
Orange is the AI interface between users and the development agent. It handles:
- User questions and discussions
- Feature/bug requests (via
queue_requesttool) - Immediate debugging (via
deep_debugtool) - Web searches for information
**System Prompt Philosophy**
CRITICAL: Orange speaks AS IF it's the developer:
- โ "I'll add that feature"
- โ "I'm fixing that bug"
- โ NEVER: "The team will...", "The agent will..."
Two Options for User Requests:
Immediate Action (deep_debug):
- For active bugs needing instant fixes
- Transfers control to autonomous debug agent
- Returns transcript after completion
- User sees real-time progress
Queued Implementation (queue_request):
- For features or non-urgent fixes
- Relays to development agent
- Implemented in next phase
- Tell user: "I'll have that in the next phase or two"
**Available Tools**
Location: /worker/agents/tools/customTools.ts โ buildTools()
1. queue_request: Queue modification requests
2. get_logs: Fetch sandbox logs (USE SPARINGLY)
3. deep_debug: Autonomous debugging (immediate fixes)
4. git: Version control (commit, log, show) - Safe version without reset
5. wait_for_generation: Wait for code generation
6. wait_for_debug: Wait for debug session
7. deploy_preview: Redeploy sandbox
8. clear_conversation: Clear chat history
9. rename_project: Rename the project
10. alter_blueprint: Modify blueprint fields
11. web_search: Search the web
12. feedback: Submit platform feedbackNote: User conversations get safe git tool (no reset command). Deep debugger gets full git tool (includes reset with warnings).
**Tool Call Rendering**
Pattern:
// Tool calls appear as expandable UI in chat messages
conversation_response {
tool: {
name: 'deep_debug',
status: 'start' | 'success' | 'error',
args: { issue: "..."},
result: "transcript or error"
}
}Frontend displays:
- Tool name with icon
- Status indicator (spinner/check/alert)
- Expandable arguments
- Expandable result (for deep_debug, shows full transcript)
**Conversation History Management**
Two-Tier Storage:
Running History (Compact):
- Used for LLM context
- Size-limited for token efficiency
- Can be archived/summarized
- Stored in
compact_conversationstable
Full History:
- Complete conversation log
- Used for UI restoration
- Never truncated
- Stored in
full_conversationstable
Compactification:
COMPACTIFICATION_CONFIG:
- MAX_TURNS: 40 conversation turns
- MAX_ESTIMATED_TOKENS: 100,000 tokens
- PRESERVE_RECENT_MESSAGES: 10 messages always kept
- CHARS_PER_TOKEN: 4 (estimation)Update Pattern:
addConversationMessage(message) {
// Update or append to both histories
if (exists) {
// Update existing (for streaming)
runningHistory[index] = message;
} else {
// Append new message
runningHistory.push(message);
}
// Same for fullHistory
save();
}๐ ๏ธ Tool System Architecture
**Tool Definition Pattern**
Location: /worker/agents/tools/toolkit/{tool-name}.ts
// 1. Define tool schema
export function createMyToolDefinition() {
return {
type: 'function' as const,
function: {
name: 'my_tool',
parameters: {
type: 'object',
properties: {
param: {
type: 'string',
}
},
required: ['param'],
},
},
};
}
// 2. Implement tool logic
export async function myToolImplementation(
args: { param: string },
context: ToolContext,
streamCb?: StreamCallback
): Promise<ToolResult> {
// Validation
if (!args.param) {
return { error: 'Missing required parameter' };
}
// Concurrency checks (if needed)
if (context.agent.isCodeGenerating()) {
return { error: 'GENERATION_IN_PROGRESS' };
}
// Execute tool logic
const result = await doWork(args.param);
// Stream progress if callback provided
streamCb?.('Processing...');
return { result };
}**Tool Registration**
For Conversation Tools:
// File: /worker/agents/tools/customTools.ts
import { createMyToolDefinition, myToolImplementation } from './toolkit/my-tool';
export function buildTools(
agent: CodingAgentInterface,
logger: StructuredLogger,
toolRenderer: RenderToolCall,
streamCb: (chunk: string) => void
): ToolDefinition[] {
return [
// ... existing tools
createTool(createMyToolDefinition(), myToolImplementation),
];
}For Debug Tools:
export function buildDebugTools(
session: DebugSession,
logger: StructuredLogger
): ToolDefinition[] {
return [
createReadFilesTool(session.agent, logger),
createRunAnalysisTool(session.agent, logger),
createRegenerateFileTool(session.agent, logger),
// ... more debug-specific tools
];
}**Tool Lifecycle Hooks**
const tool = {
function: {...},
implementation: async (args) => {...},
// Optional hooks for UI feedback
onStart: (args) => {
toolRenderer({
name: 'my_tool',
status: 'start',
args
});
},
onComplete: (args, result) => {
toolRenderer({
name: 'my_tool',
status: 'success',
args,
result: JSON.stringify(result)
});
}
};๐๏ธ Database Schema Overview
**Core Tables**
Location: /worker/database/schema.ts
**1. Users Table**
users {
id: text (PK)
email: text (unique)
username: text (unique, nullable)
displayName: text
avatarUrl: text
provider: 'github' | 'google' | 'email'
providerId: text
passwordHash: text (for email provider)
// Security
emailVerified: boolean
failedLoginAttempts: number
lockedUntil: timestamp
// Preferences
theme: 'light' | 'dark' | 'system'
timezone: text
// Status
isActive: boolean
isSuspended: boolean
// Timestamps
createdAt, updatedAt, lastActiveAt, deletedAt
}**2. Apps Table**
apps {
id: text (PK)
title: text
iconUrl: text
// Generation
originalPrompt: text
finalPrompt: text
framework: text
// Ownership
userId: text (FK โ users, nullable for anonymous)
sessionToken: text (for anonymous)
// Visibility
visibility: 'private' | 'public'
status: 'generating' | 'completed'
// Deployment
deploymentId: text
githubRepositoryUrl: text
// Metadata
isArchived: boolean
isFeatured: boolean
version: number
parentAppId: text (for forks)
screenshotUrl: text
// Timestamps
createdAt, updatedAt, lastDeployedAt
}**3. Sessions Table**
sessions {
id: text (PK)
userId: text (FK โ users)
// Session data
deviceInfo: text
userAgent: text
ipAddress: text
// Security
isRevoked: boolean
accessTokenHash: text
refreshTokenHash: text
// Timing
expiresAt: timestamp
createdAt: timestamp
lastActivity: timestamp
}**4. Stars & Favorites**
stars {
id: text (PK)
userId: text (FK โ users)
appId: text (FK โ apps)
starredAt: timestamp
// Unique constraint on (userId, appId)
}
favorites {
id: text (PK)
userId: text (FK โ users)
appId: text (FK โ apps)
createdAt: timestamp
// Unique constraint on (userId, appId)
}**5. Analytics Tables**
appViews {
id: text (PK)
appId: text (FK โ apps)
userId: text (FK โ users, nullable)
sessionId: text
viewedAt: timestamp
// Indexes for fast counting
}
userModelConfigs {
id: text (PK)
userId: text (FK โ users)
agentActionName: text
// Model overrides
modelName: text
maxTokens: number
temperature: number
reasoningEffort: 'low' | 'medium' | 'high'
fallbackModel: text
// Unique per user+action
}**Database Service Pattern**
// File: /worker/database/services/DomainService.ts
export class DomainService {
private db: D1Database;
constructor(env: Env) {
this.db = env.DB;
}
async getItem(id: string): Promise {
// Use Drizzle ORM for type safety
const result = await this.db
.select()
.from(itemsTable)
.where(eq(itemsTable.id, id))
.get();
if (!result) throw new ApiError(404, 'Not found');
return result;
}
async createItem(data: CreateInput): Promise {
// Insert with validation
const id = generateId();
await this.db
.insert(itemsTable)
.values({ id, ...data });
return this.getItem(id);
}
}๐ธ Image Attachment System
**Supported Formats**
Location: /worker/types/image-attachment.ts
SUPPORTED_IMAGE_MIME_TYPES = [
'image/png',
'image/jpeg',
'image/webp',
]
MAX_IMAGE_SIZE_BYTES = 10 * 1024 * 1024; // 10MB
MAX_IMAGES_PER_MESSAGE = 2;**Image Flow**
1. User uploads/drags image
โ
2. Frontend: Validate size/type
โ
3. Frontend: Convert to base64
โ
4. Frontend: Show preview
โ
5. User sends message
โ
6. Frontend โ Backend: USER_SUGGESTION { message, images: [...] }
โ
7. Backend: Upload to R2 storage
โ
8. Backend: Pass to LLM with vision model
โ
9. LLM: Analyze image + generate response**Image Types**
// Raw upload from user
interface ImageAttachment {
id: string;
filename: string;
mimeType: SupportedImageMimeType;
base64Data: string; // Without data URL prefix
size: number;
dimensions?: { width: number; height: number };
}
// After R2 upload
interface ProcessedImageAttachment {
mimeType: SupportedImageMimeType;
base64Data?: string; // Optional, may be cleared after upload
r2Key: string; // R2 storage key
publicUrl: string; // Public URL
hash: string; // Content hash
}**Frontend Validation**
Location: /src/hooks/use-image-upload.ts
Validation checks:
1. File type in SUPPORTED_IMAGE_MIME_TYPES
2. File size โค MAX_IMAGE_SIZE_BYTES
3. Total images โค MAX_IMAGES_PER_MESSAGE
Rejection behavior:
- Show error toast
- Don't add to preview
- Log validation failure**Backend Validation**
Location: /worker/agents/core/websocket.ts
case USER_SUGGESTION:
if (images && images.length > MAX_IMAGES_PER_MESSAGE) {
sendError(`Maximum ${MAX_IMAGES_PER_MESSAGE} images allowed`);
return;
}
for (const image of images) {
if (image.size > MAX_IMAGE_SIZE_BYTES) {
sendError(`Image exceeds ${MAX_IMAGE_SIZE_BYTES / 1024 / 1024}MB`);
return;
}
}๐ Authentication Guards
Location: /src/hooks/useAuthGuard.ts and useActionGuard.ts
Purpose: Protect actions requiring authentication (star, fork, etc.)
Flow:
- User clicks protected action (not authenticated)
- Guard shows auth modal
- User logs in via GitHub/Google OAuth
- OAuth callback creates session
- Redirects back with
?action=starparameter - Frontend detects parameter, executes pending action
- Clears action parameter
Options: requireFullAuth (reject anonymous), actionContext ("to star this app"), onSuccess callback
๐ GETTING STARTED - Common Tasks
**Adding a New LLM Tool**
Steps:
- Create tool file:
/worker/agents/tools/toolkit/my-new-tool.ts - Structure:
import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent';
import { StructuredLogger } from '../../../logger';
export function createMyNewTool(agent: CodingAgentInterface, logger: StructuredLogger) {
return {
type: 'function' as const,
function: {
name: 'my_new_tool',
parameters: {
type: 'object',
properties: {
input: {
type: 'string',
}
},
required: ['input']
}
},
implementation: async (args: { input: string }) => {
logger.info('Tool called', { args });
// Your logic here
return { result: 'success' };
}
};
}/* Detailed source-code truncated for AI context efficiency. */
export class UserService extends BaseService {
// Existing methods...
async getNewMethod(userId: string): Promise<ResultType> {
// Use 'fresh' for user's own data
const readDb = this.getReadDb('fresh');
const result = await readDb
.select()
.from(schema.users)
.where(eq(schema.users.id, userId));
if (!result) throw new Error('Not found');
return result;
}
}Call from controller:
const userService = new UserService(c.env);
const data = await userService.getNewMethod(userId);/* Detailed source-code truncated for AI context efficiency. */
// User-based (authenticated)
user:abc123
// Token-based (JWT hash)
token:sha256_hash_16_chars
// IP-based (anonymous)
ip:192.168.1.1**Rate Limit Types**
Location: /worker/services/rate-limit/config.ts
- API_ENDPOINT - HTTP endpoint rate limit
- LLM_REQUEST - LLM inference rate limit
- AUTH_ATTEMPT - Login/signup attempts
- APP_CREATION - New app creation
- GITHUB_EXPORT - GitHub push operations
**Configuration Structure**
interface DORateLimitConfig {
limit: number; // Max requests per period
period: number; // Time window in seconds
burst?: number; // Burst allowance
burstWindow?: number; // Burst time window
bucketSize: number; // Bucket size for sliding window
dailyLimit?: number; // Optional daily cap
}**Usage**
// In API route
const allowed = await RateLimitService.enforce(
env,
request,
RateLimitType.API_ENDPOINT,
user
);
if (!allowed) {
throw new RateLimitExceededError('Too many requests');
}**LLM Model-Specific Rates**
Different models have different rate increments:
- GPT-4o: 10 units
- GPT-4o-mini: 1 unit
- Claude Sonnet: 15 units
- Gemini Pro: 20 units
- Gemini Flash: 5 units
Why? Expensive models consume more quota to prevent abuse.
GitHub Service
Location: /worker/services/github/GitHubService.ts
Purpose: Export generated apps to GitHub repositories
**Key Operations**
1. Create Repository
static async createUserRepository(options: {
token: string; // User's GitHub PAT
name: string; // Repo name
description?: string;
private: boolean; // Public or private
auto_init?: boolean; // Create with README
})2. Push Generated Code
static async pushCodeToRepository({
token,
owner,
repo,
gitObjects, // Agent's git objects
templateDetails, // Template base
appQuery, // Original user prompt
branch = 'main'
})Process:
- Build git repo with
GitCloneService(rebases on template) - Push all commits to GitHub via Octokit
- Add README with app description + Cloudflare deploy button
- Return repository URL
3. Add Deploy to Cloudflare Button
static async addCloudflareDeployButton({
token,
owner,
repo,
templateName
})Appends markdown button to README for one-click Cloudflare deployment.
OAuth Service
Location: /worker/services/oauth/
Providers: Google, GitHub
**Base Pattern** (`base.ts`)
All providers extend BaseOAuthProvider:
abstract class BaseOAuthProvider {
abstract getAuthorizationUrl(params): string;
abstract exchangeCodeForToken(code, verifier): Promise<TokenResponse>;
abstract getUserInfo(token): Promise;
}**OAuth Flow**
Step 1: Generate Auth URL
const provider = OAuthProviderFactory.create('google', env);
const { url, state, codeVerifier } = await provider.getAuthorizationUrl({
redirectUri: 'https://app.com/auth/callback',
state: csrfToken,
scopes: ['openid', 'email', 'profile']
});
// Store state + verifier in oauthStates table
// Redirect user to urlStep 2: Handle Callback
// Verify state (CSRF protection)
const storedState = await db.getOAuthState(state);
if (!storedState || storedState.used) throw new Error('Invalid state');
// Exchange code for token
const tokenData = await provider.exchangeCodeForToken(
code,
storedState.codeVerifier
);
// Get user info
const userInfo = await provider.getUserInfo(tokenData.access_token);
// Create or update user
const user = await authService.findOrCreateOAuthUser({
provider: 'google',
providerId: userInfo.id,
email: userInfo.email,
displayName: userInfo.name
});
// Create session
const session = await sessionService.createSession(user);Step 3: Cleanup
// Mark state as used
await db.markOAuthStateUsed(state);
// Cleanup expired states (runs periodically)
await db.cleanupExpiredOAuthStates();**PKCE (Proof Key for Code Exchange)**
Purpose: Prevent authorization code interception
Flow:
- Generate random
codeVerifier(128 chars) - Create
codeChallenge= SHA256(codeVerifier) - Send challenge in auth URL
- Store verifier in oauthStates table
- Send verifier in token exchange
- Provider verifies: SHA256(verifier) == challenge
Google Implementation:
- Uses
code_challenge_method=S256 - Requires
openidscope
GitHub Implementation:
- Standard OAuth 2.0 (no PKCE)
- Uses
statefor CSRF only
Analytics Service
Location: /worker/services/analytics/
Purpose: Track app views, stars, user activity
**Database Service**
File: /worker/database/services/AnalyticsService.ts
Key Operations:
1. Track View
await analyticsService.trackView(appId, userId, ipAddress);- Creates view record
- Deduplicates by IP (1 view per IP per day)
- Updates app.viewsCount
2. Star App
const result = await analyticsService.toggleStar(appId, userId);
// result: { starred: true } or { starred: false }- Adds/removes star
- Updates app.starsCount
- Returns new state
3. Get Activity Stats
const stats = await analyticsService.getUserActivity(userId, days = 30);
// Returns: appsCreated, totalViews, totalStars, recentActivity[]**Ranking Impact**
Views and stars affect app rankings:
- Popular:
(views ร 1) + (stars ร 3)DESC - Trending:
(recent_activity ร 1000000 + recency_bonus)DESC
Cache Service
Location: /worker/services/cache/
Purpose: Cache expensive operations
**Cache Strategies**
1. Git Packfile Cache
// Cache generated packfiles for git clone
await cacheService.set(
`git:packfile:${agentId}`,
packfileBuffer,
3600 // 1 hour TTL
);2. Static Analysis Cache
// Cache TypeScript analysis results
await cacheService.set(
`analysis:${fileHash}`,
analysisResults,
300 // 5 min TTL
);3. Template Cache
// Cache template file trees
await cacheService.set(
`template:${templateName}`,
templateFiles,
86400 // 24 hours
);**Implementation**
Uses Cloudflare Cache API:
const cache = caches.default;
await cache.put(request, response);
const cached = await cache.match(request);CSRF Protection
Location: /worker/services/csrf/
Purpose: Prevent cross-site request forgery
**Token Generation**
// Generate token for OAuth state
const csrfToken = await crypto.subtle.digest(
'SHA-256',
crypto.getRandomValues(new Uint8Array(32))
);**Validation**
// In OAuth callback
if (callbackState !== storedState.state) {
throw new SecurityError('CSRF token mismatch');
}**Storage**
CSRF tokens stored in oauthStates table:
statecolumn = CSRF tokenexpiresAt= 10 minutesusedflag prevents replay
User Secrets Store (Durable Object)
Location: /worker/services/secrets/
Purpose: Secure, encrypted storage for user API keys and secrets with key rotation support
**Architecture**
Storage: Durable Object with SQLite backend
- One DO instance per user (userId as DO ID)
- XChaCha20-Poly1305 encryption (AEAD)
- Hierarchical key derivation: MEK โ UMK โ DEK
- Key rotation metadata tracking
Core Components:
- UserSecretsStore (
UserSecretsStore.ts) - Main DO class - KeyDerivation (
KeyDerivation.ts) - PBKDF2-based key derivation - EncryptionService (
EncryptionService.ts) - XChaCha20-Poly1305 encryption - Types (
types.ts) - Type definitions
**Key Features**
1. Hierarchical Key Derivation
Master Encryption Key (MEK)
โ PBKDF2 with userId salt
User Master Key (UMK)
โ PBKDF2 with secret-specific salt
Data Encryption Key (DEK) - unique per secret2. Encryption
- Algorithm: XChaCha20-Poly1305 (AEAD)
- Unique salt per secret (16 bytes)
- Unique nonce per encryption (24 bytes)
- Authentication tag for integrity verification
3. Key Rotation
- Tracks master key fingerprint (SHA-256)
- Detects key changes automatically
- Re-encrypts all secrets with new key
- Maintains rotation statistics
4. Security Features
- Access counting (tracks how many times secret accessed)
- Secret expiration timestamps
- Soft deletion (90-day retention)
- Key preview masking (shows first/last 4 chars)
**Database Schema**
Tables:
-- Main secrets table
CREATE TABLE secrets (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
name TEXT NOT NULL,
secret_type TEXT NOT NULL,
encrypted_value BLOB NOT NULL,
nonce BLOB NOT NULL,
salt BLOB NOT NULL,
key_preview TEXT NOT NULL,
metadata TEXT,
access_count INTEGER DEFAULT 0,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
expires_at INTEGER,
is_active INTEGER DEFAULT 1,
key_fingerprint TEXT NOT NULL
);
-- Key rotation tracking
CREATE TABLE key_rotation_metadata (
id INTEGER PRIMARY KEY DEFAULT 1,
current_key_fingerprint TEXT NOT NULL,
last_rotation_at INTEGER NOT NULL,
rotation_count INTEGER DEFAULT 0
);**API Methods (RPC - No Exceptions)**
Critical: All DO RPC methods return null or boolean on error, never throw exceptions.
// Store new secret
async storeSecret(request: StoreSecretRequest): Promise<SecretMetadata | null>
// Get decrypted value
async getSecretValue(secretId: string): Promise<SecretWithValue | null>
// List secrets (metadata only)
async listSecrets(): Promise<SecretMetadata[]>
// Update secret
async updateSecret(secretId: string, updates: UpdateSecretRequest): Promise<SecretMetadata | null>
// Delete secret (soft delete)
async deleteSecret(secretId: string): Promise
// Get key rotation info
async getKeyRotationInfo(): Promise<KeyRotationInfo>**Type Definitions**
interface StoreSecretRequest {
name: string;
secretType: 'api_key' | 'oauth_token' | 'webhook_secret' | 'encryption_key' | 'other';
value: string;
metadata?: Record<string, unknown>;
expiresAt?: number;
}
interface SecretMetadata {
id: string;
userId: string;
name: string;
secretType: string;
keyPreview: string;
metadata?: Record<string, unknown>;
accessCount: number;
createdAt: number;
updatedAt: number;
expiresAt?: number;
}
interface SecretWithValue {
value: string;
metadata: SecretMetadata;
}
interface KeyRotationInfo {
currentKeyFingerprint: string;
lastRotationAt: number;
rotationCount: number;
totalSecrets: number;
secretsRotated: number;
}**Usage Example**
// Get DO stub
const id = env.UserSecretsStore.idFromName(user.id);
const store = env.UserSecretsStore.get(id);
// Store secret
const metadata = await store.storeSecret({
name: 'OpenAI API Key',
secretType: 'api_key',
value: 'sk-...',
metadata: { provider: 'openai' }
});
if (!metadata) {
throw new Error('Failed to store secret');
}
// Retrieve decrypted value
const secret = await store.getSecretValue(metadata.id);
if (!secret) {
throw new Error('Secret not found or expired');
}
console.log(secret.value); // Decrypted value
console.log(secret.metadata.accessCount); // Incremented on each access
// List all secrets (no values)
const secrets = await store.listSecrets();
// Update secret
const updated = await store.updateSecret(metadata.id, {
name: 'OpenAI API Key (Production)',
expiresAt: Date.now() + 86400000 // 24 hours
});
// Delete secret
const deleted = await store.deleteSecret(metadata.id);**Controller Integration**
Location: /worker/api/controllers/user-secrets/controller.ts
// Example: Get secret value
static async getSecretValue(
request: Request,
env: Env,
ctx: ExecutionContext,
context: RouteContext
): Promise<ControllerResponse<ApiResponse>> {
const user = context.user!;
const secretId = context.pathParams.secretId;
const stub = this.getUserSecretsStub(env, user.id);
const result = await stub.getSecretValue(secretId);
if (!result) {
return UserSecretsController.createErrorResponse(
'Secret not found or has expired',
404
);
}
return UserSecretsController.createSuccessResponse(result);
}**Key Rotation Process**
Automatic Detection:
- On DO initialization, checks current master key fingerprint
- Compares with stored fingerprint in database
- If different, triggers key rotation
Re-encryption:
async performKeyRotation() {
// 1. Fetch all active secrets
const secrets = this.ctx.storage.sql.exec(`
SELECT * FROM secrets WHERE is_active = 1
`);
// 2. Decrypt with old key, encrypt with new key
for (const secret of secrets) {
const decrypted = await this.decrypt(secret.encrypted_value, ...);
const encrypted = await this.encrypt(decrypted);
// 3. Update in database atomically
}
// 4. Update rotation metadata
}**Security Considerations**
โ Good Practices:
- Master key stored in Worker environment variable
- Unique salt per secret
- AEAD encryption with integrity verification
- Key rotation support
- Soft deletion for recovery
- Access tracking for audit
โ ๏ธ Important Notes:
- DO RPC methods return
null/booleaninstead of throwing exceptions - Master key must be 64 hex characters (32 bytes)
- Expired secrets automatically filtered from results
- Soft deleted secrets retained for 90 days
**Testing**
Location: /test/worker/services/secrets/
Comprehensive test suite with 90+ tests (3 test files):
- KeyDerivation.test.ts - 17 unit tests for key derivation
- EncryptionService.test.ts - 18 unit tests for encryption/decryption
- UserSecretsStore.test.ts - 55+ E2E tests for full DO lifecycle
Run tests:
npm test test/worker/services/secrets
# Or with Bun:
bun run test:bun test/worker/services/secretsTest Coverage:
- CRUD operations
- Encryption/decryption
- Key rotation
- Expiration handling
- Concurrency (10 parallel operations)
- Large scale (20+ secrets, 5KB values)
- Data integrity verification
- Error handling
**Configuration**
Wrangler Configuration:
{
"durable_objects": {
"bindings": [
{
"name": "UserSecretsStore",
"class_name": "UserSecretsStore"
}
]
},
"migrations": [
{
"tag": "v3",
"new_sqlite_classes": ["UserSecretsStore"]
}
]
}๐จ FRONTEND RENDERING PATTERNS
Component Architecture
**Atomic Design Structure**
Hierarchy:
1. Primitives (ui/) - shadcn/ui base components
โ
2. Shared (shared/) - App-specific reusable components
โ
3. Features (routes/) - Page-specific components
โ
4. Pages (routes/*.tsx) - Full page views**Example: Button Hierarchy**
// 1. Primitive: /components/ui/button.tsx
export const Button = forwardRef<HTMLButtonElement, ButtonProps>((
{ className, variant, size, ...props },
ref
) => {
return (
);
});
// 2. Shared: /components/shared/AppCard.tsx
export function AppCard({ app }: { app: App }) {
return (
<Card>
<CardHeader>
<h3>{app.title}</h3>
</CardHeader>
<CardFooter>
navigate(`/app/${app.id}`)}>
View App
</CardFooter>
</Card>
);
}
// 3. Feature: /routes/apps/apps-list.tsx
export function AppsList() {
const { apps } = useApps();
return (
{apps.map(app => <AppCard key={app.id} app={app} />)}
);
}State Management Patterns
**1. Local State (useState)**
Use for: UI-only state (modals, dropdowns, form inputs)
const [isOpen, setIsOpen] = useState(false);
const [selectedFile, setSelectedFile] = useState<FileType | null>(null);**2. Server State (Custom Hooks)**
Use for: Data from API
// /hooks/use-apps.ts
export function useApps(filters?: AppFilters) {
const [apps, setApps] = useState<App[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
apiClient.getApps(filters).then(setApps);
}, [filters]);
return { apps, loading, refetch };
}
// Usage
const { apps, loading } = useApps({ sortBy: 'popular' });**3. Global State (Context)**
Use for: Cross-component shared state (auth, theme)
// /contexts/auth-context.tsx
const AuthContext = createContext<AuthContextType>(null!);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
return (
<AuthContext.Provider value={{ user, setUser }}>
{children}
</AuthContext.Provider>
);
}
// Usage
const { user } = useAuth();**4. WebSocket State (use-chat hook)**
Use for: Real-time agent state
Location: /src/routes/chat/hooks/use-chat.ts
Pattern:
export function useChat(chatId: string) {
// Local state
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [files, setFiles] = useState<FileType[]>([]);
const [isGenerating, setIsGenerating] = useState(false);
// WebSocket connection
const [websocket, setWebSocket] = useState<WebSocket | null>(null);
// Message handler
useEffect(() => {
if (!websocket) return;
websocket.onmessage = (event) => {
const message = JSON.parse(event.data);
handleWebSocketMessage(message, {
setMessages,
setFiles,
setIsGenerating,
// ... other setters
});
};
}, [websocket]);
return {
messages,
files,
isGenerating,
websocket,
sendMessage: (text) => {
websocket?.send(JSON.stringify({ type: 'USER_MESSAGE', text }));
}
};
}Rendering Optimization
**1. useMemo for Expensive Computations**
const sortedFiles = useMemo(() => {
return files.sort((a, b) => a.path.localeCompare(b.path));
}, [files]);**2. useCallback for Event Handlers**
const handleFileClick = useCallback((fileId: string) => {
setSelectedFile(files.find(f => f.id === fileId));
}, [files]);**3. React.memo for Pure Components**
export const FileTreeNode = memo(({ file, onSelect }: Props) => {
return (
onSelect(file.id)}>
{file.name}
);
});**4. Virtual Scrolling for Large Lists**
// For 1000+ items
import { useVirtualizer } from '@tanstack/react-virtual';
const virtualizer = useVirtualizer({
count: apps.length,
getScrollElement: () => containerRef.current,
estimateSize: () => 200, // Card height
});Data Fetching Patterns
**1. Single Resource**
// /hooks/use-app.ts
export function useApp(appId?: string) {
const [app, setApp] = useState<App | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
if (!appId) return;
setLoading(true);
apiClient.getApp(appId)
.then(setApp)
.catch(setError)
.finally(() => setLoading(false));
}, [appId]);
return { app, loading, error, refetch };
}**2. Paginated List**
// /hooks/use-apps.ts
export function useApps(filters?: AppFilters) {
const [apps, setApps] = useState<App[]>([]);
const [hasMore, setHasMore] = useState(true);
const currentPageRef = useRef(1);
const isLoadingMoreRef = useRef(false);
const fetchApps = useCallback(async (loadMore = false) => {
if (isLoadingMoreRef.current) return;
isLoadingMoreRef.current = true;
const page = loadMore ? currentPageRef.current + 1 : 1;
const result = await apiClient.getApps({ ...filters, page });
if (loadMore) {
setApps(prev => [...prev, ...result.apps]);
} else {
setApps(result.apps);
}
setHasMore(result.hasMore);
currentPageRef.current = page;
isLoadingMoreRef.current = false;
}, [filters]);
const loadMore = () => fetchApps(true);
return { apps, hasMore, loadMore };
}**3. Infinite Scroll**
const { apps, hasMore, loadMore } = useApps();
const observerRef = useRef();
const sentinelRef = useRef<HTMLDivElement>(null);
useEffect(() => {
observerRef.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting && hasMore) {
loadMore();
}
});
if (sentinelRef.current) {
observerRef.current.observe(sentinelRef.current);
}
return () => observerRef.current?.disconnect();
}, [hasMore, loadMore]);
return (
{apps.map(app => <AppCard key={app.id} app={app} />)}
{/* Sentinel element */}
);Form Handling
**Controlled Components**
const [formData, setFormData] = useState({
title: '',
isPublic: true
});
const handleChange = (field: keyof typeof formData) => (
e: React.ChangeEvent<HTMLInputElement>
) => {
setFormData(prev => ({ ...prev, [field]: e.target.value }));
};
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
await apiClient.createApp(formData);
};
return (
<form onSubmit={handleSubmit}>
Create
</form>
);**Form Validation**
const [errors, setErrors] = useState<Record<string, string>>({});
const validate = () => {
const newErrors: Record<string, string> = {};
if (!formData.title) {
newErrors.title = 'Title is required';
}
if (formData.title.length < 3) {
newErrors.title = 'Title must be at least 3 characters';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
if (!validate()) return;
await apiClient.createApp(formData);
};Modal Patterns
**Simple Modal State**
const [isOpen, setIsOpen] = useState(false);
return (
<>
setIsOpen(true)}>Open Modal
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Modal Title</DialogTitle>
</DialogHeader>
{/* Modal content */}
</DialogContent>
</Dialog>
</>
);**Modal with Data**
const [selectedApp, setSelectedApp] = useState<App | null>(null);
return (
<>
{apps.map(app => (
setSelectedApp(app)}>
Edit
))}
{selectedApp && (
<EditAppModal
app={selectedApp}
onClose={() => setSelectedApp(null)}
/>
)}
</>
);Error Handling
**Error Boundary**
// /components/ErrorBoundary.tsx
export class ErrorBoundary extends Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
// Send to Sentry
}
render() {
if (this.state.hasError) {
return <ErrorFallback error={this.state.error} />;
}
return this.props.children;
}
}**API Error Handling**
try {
const app = await apiClient.getApp(appId);
setApp(app);
} catch (error) {
if (error instanceof ApiError) {
if (error.status === 404) {
setError('App not found');
} else if (error.status === 403) {
setError('Access denied');
} else {
setError('Something went wrong');
}
}
}/* Detailed source-code truncated for AI context efficiency. */
/* Detailed source-code truncated for AI context efficiency. *//* Detailed source-code truncated for AI context efficiency. */
โ createInstance(templateName, projectName, webhookUrl)
โ { instanceId, url, status }
โ Save instanceId to stateFile Synchronization
textโ Collect all files from generatedFilesMap โ Format as { path, content, encoding: 'utf-8' }[] โ writeFiles(instanceId, files, "Deploy generated code") โ { success: true, filesWritten: 42 }Package.json Sync
textโ Check if package.json changed โ If changed: executeCommands(['npm install']) โ Wait for completion (timeout: 60s) โ Cache new package.json in stateBootstrap Commands (if needed)
textโ Execute commandsHistory (previously run user commands) โ Validates/filters dangerous commands โ Runs: npm install, setup scripts, etc.Start Dev Server
textโ Already running from instance creation โ Or trigger via command if stopped โ Monitor startup logsHealth Check Loop
textโ setInterval(30s): ping sandbox โ Check status endpoint โ If unhealthy: log warning, may resetReturn Preview URL
textโ Send URL to frontend via WebSocket โ User can open in iframe or new tab โ App is live and interactive
Redeployment (Incremental Updates)
When files change after initial deploy:
Diff Detection
- Compare file hashes in generatedFilesMap
- Only sync changed files
Partial Sync
textโ writeFiles(instanceId, [changedFiles]) โ Hot reload triggered automaticallyNo Full Rebuild
- Dev server hot reloads changes
- Fast iteration (< 1s typically)
Deployment Errors & Recovery
Common errors:
Timeout (60s)
- Cause: npm install too slow, network issues
- Recovery: Reset sessionId, retry with fresh instance
Instance Not Found
- Cause: Container crashed or evicted
- Recovery: Create new instance, redeploy all files
Command Execution Failed
- Cause: Invalid package.json, dependency conflicts
- Recovery: Show error to user, allow editing
Health Check Failed
- Cause: Dev server crashed, port conflict
- Recovery: Reset session on next deploy attempt
๐ค LLM INFERENCE SYSTEM
Overview
Location: /worker/agents/inferutils/
Centralized inference engine that all operations use to call LLMs.
Key features:
- Multi-provider support (OpenAI, Anthropic via Cloudflare AI Gateway)
- Streaming responses
- Tool calling with recursive execution
- Retry logic with exponential backoff
- Cancellation support (AbortController)
- Token tracking
Inference Flow
Operation (PhaseImplementation, UserConversationProcessor, etc.)
โ
getOperationOptions() โ InferenceContext
โ
executeInference(args, context)
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Retry Loop (max 3 attempts) โ
โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโ
โ
infer(args)
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ OpenAI SDK (via AI Gateway) โ
โ - Model selection โ
โ - Token streaming โ
โ - Tool call parsing โ
โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโ
โ
Tool calls present?
โ
โโโโโโโโโดโโโโโโโโ
Yes No
โ โ
โ โ
Execute tools Return response
Recursive inferModel Selection
Location: /worker/agents/inferutils/config.ts
Available models:
- GPT-4o - Fast, good for most tasks
- GPT-4o-mini - Cheapest, simple operations
- Claude 3.5 Sonnet - Best for complex reasoning
- Gemini 2.0 Flash - Fast, experimental
- Gemini 2.5 Pro - Highest quality, deep debugging
Selection by operation:
- Blueprint generation: GPT-4o
- Phase planning: GPT-4o
- File generation: GPT-4o
- Conversation: GPT-4o-mini
- Deep debugging: Gemini 2.5 Pro (reasoning_effort: high)
- Code review: GPT-4o
Streaming
When enabled:
- User conversation responses
- Deep debugger output
- Real-time code generation feedback
How it works:
- LLM sends Server-Sent Events (SSE)
infer()yields chunks via async generator- Operation accumulates + forwards to WebSocket
- Frontend renders progressively
Tool Calling
Recursive execution:
- LLM response includes
tool_callsarray infer()executes each tool in parallel- Results collected
- Filtered (empty/null results skipped)
- If results exist: call LLM again with tool outputs
- Repeat until LLM provides final response
Max depth: Configurable per operation
Retry Logic
Triggers retry:
- Rate limit errors (429)
- Network timeouts
- Temporary API failures (5xx)
Does NOT retry:
- Cancelled operations (AbortError)
- Invalid API key (401)
- Malformed requests (400)
Backoff: Exponential (1s, 2s, 4s)
Cancellation
Each operation gets AbortSignal:
User clicks stop button
โ
WebSocket: STOP_GENERATION
โ
agent.cancelCurrentInference()
โ
AbortController.abort()
โ
OpenAI SDK cancels HTTP request
โ
infer() throws InferError('cancelled')
โ
No retry, immediate propagationNested operations: Share same AbortController
Tool calls: All cancelled together
๐ AUTHENTICATION & AUTHORIZATION SYSTEM
Overview
The auth system implements a comprehensive JWT-based authentication with OAuth 2.0 social login (Google, GitHub), session management, API keys, and security auditing. All auth operations are centralized through services that interact with D1 database.
Core Components:
- AuthService - Main authentication orchestrator
- SessionService - JWT session management with D1 persistence
- JWTUtils - Token creation, verification, signing
- OAuth Providers - Google & GitHub implementations with PKCE
- Middleware - Route protection and token extraction
- Security - Password hashing, rate limiting, audit logs
Auth Architecture Flow
/* Detailed source-code truncated for AI context efficiency. *//* Detailed source-code truncated for AI context efficiency. */
/* Detailed source-code truncated for AI context efficiency. */BaseService Pattern
Location: /worker/database/services/BaseService.ts
**Purpose**
Provides common database functionality to all domain services:
- Database connection management
- Read replica access (D1 Sessions API)
- Type-safe where condition building
- Error handling patterns
- Logging
**Implementation**
abstract class BaseService {
protected logger = createLogger(this.constructor.name);
protected db: DatabaseService;
protected env: Env;
constructor(env: Env) {
this.db = createDatabaseService(env);
this.env = env;
}
// Direct database access (primary)
protected get database() {
return this.db.db;
}
// Read replica access (optimized latency)
protected getReadDb(strategy: 'fast' | 'fresh' = 'fast') {
return this.db.getReadDb(strategy);
}
// Build type-safe WHERE conditions
protected buildWhereConditions(
conditions: (SQL | undefined)[]
): SQL | undefined {
const validConditions = conditions.filter(
(c): c is SQL => c !== undefined
);
if (validConditions.length === 0) return undefined;
if (validConditions.length === 1) return validConditions[0];
return and(...validConditions);
}
// Standard error handling
protected handleDatabaseError(
error: unknown,
operation: string,
context?: Record<string, unknown>
): never {
this.logger.error(`Database error in ${operation}`, { error, context });
throw error;
}
}D1 Sessions API - Read Replicas
**What is D1 Sessions?**
Cloudflare D1 Sessions API provides read replicas for D1 databases distributed globally. This dramatically reduces latency for read queries by serving them from the nearest replica.
**Strategies**
Location: /worker/database/database.ts
class DatabaseService {
getReadDb(strategy: 'fast' | 'fresh' = 'fast') {
if (strategy === 'fast') {
// Lowest latency - may be slightly stale
return drizzle(this.env.DB, { ... });
} else {
// Latest data - may have higher latency
return drizzle(this.env.DB.withSession({ strategy: 'fresh' }), { ... });
}
}
}**When to Use Each Strategy**
**'fast' Strategy (Default)**
Use for:
- Public app listings
- Public app details
- Analytics and stats
- Search results
- Any read-only public data
Benefits:
- Lowest latency (served from nearest replica)
- Suitable for data that can tolerate slight staleness (few seconds)
- Most queries should use this
Example:
// Public apps - use fast replicas
async getPublicApps(options: PublicAppQueryOptions) {
const readDb = this.getReadDb('fast'); // โ Use fast strategy
const apps = await readDb
.select()
.from(schema.apps)
.where(eq(schema.apps.visibility, 'public'));
}**'fresh' Strategy**
Use for:
- User's own data (own apps, favorites)
- Immediately after writes (read-after-write)
- Auth/session validation
- Account settings
- Any data where staleness is unacceptable
Benefits:
- Latest data from primary or recent replica
- Ensures user sees their own changes immediately
Example:
// User's own apps - use fresh data
async getUserApps(userId: string) {
const readDb = this.getReadDb('fresh'); // โ Use fresh strategy
const apps = await readDb
.select()
.from(schema.apps)
.where(eq(schema.apps.userId, userId));
}**NEVER Use Read Replicas For:**
โ Write operations - Always use primary (this.database)
โ Auth validation - Use primary to avoid security issues
โ Immediately after INSERT/UPDATE - Read from primary
โ Critical consistency - Password changes, payments, etc.
Drizzle ORM Patterns
**Basic Queries**
**SELECT**
// Simple select
const users = await db
.select()
.from(schema.users)
.where(eq(schema.users.email, email));
// Select specific columns
const users = await db
.select({
id: schema.users.id,
email: schema.users.email
})
.from(schema.users);
// With JOIN
const apps = await db
.select({
app: schema.apps,
userName: schema.users.displayName
})
.from(schema.apps)
.leftJoin(schema.users, eq(schema.apps.userId, schema.users.id));**INSERT**
// Insert one
const [user] = await db
.insert(schema.users)
.values({
id: generateId(),
email: '[email protected]',
displayName: 'User',
createdAt: new Date()
})
.returning();
// Insert many
await db
.insert(schema.apps)
.values([
{ id: id1, title: 'App 1', ... },
{ id: id2, title: 'App 2', ... }
]);**UPDATE**
await db
.update(schema.users)
.set({
displayName: 'New Name',
updatedAt: new Date()
})
.where(eq(schema.users.id, userId));**DELETE**
// Hard delete
await db
.delete(schema.sessions)
.where(eq(schema.sessions.id, sessionId));
// Soft delete (preferred)
await db
.update(schema.users)
.set({ deletedAt: new Date() })
.where(eq(schema.users.id, userId));**Complex Queries**
**Aggregations**
// COUNT
const result = await db
.select({ count: sql<number>`COUNT(*)` })
.from(schema.apps)
.where(eq(schema.apps.visibility, 'public'));
const total = result[0].count;
// SUM, AVG
const stats = await db
.select({
totalViews: sql<number>`SUM(${schema.appViews.id})`,
avgViews: sql<number>`AVG(view_count)`
})
.from(schema.apps);**Subqueries**
// Subquery in WHERE
const apps = await db
.select()
.from(schema.apps)
.where(
inArray(
schema.apps.id,
db.select({ id: schema.favorites.appId })
.from(schema.favorites)
.where(eq(schema.favorites.userId, userId))
)
);**Conditional WHERE Clauses**
// Use BaseService.buildWhereConditions()
const conditions: WhereCondition[] = [];
if (framework) {
conditions.push(eq(schema.apps.framework, framework));
}
if (search) {
conditions.push(
or(
sql`LOWER(${schema.apps.title}) LIKE ${`%${search}%`}`,
sql`LOWER(${schema.apps.description}) LIKE ${`%${search}%`}`
)
);
}
const whereClause = this.buildWhereConditions(conditions);
const apps = await db
.select()
.from(schema.apps)
.where(whereClause);/* Detailed source-code truncated for AI context efficiency. */
// Add to websocket.ts handleWebSocketMessage()
logger.info('Received WebSocket message', { type: message.type, data: message });**Issue: LLM not calling tools**
Common causes:
- Tool description unclear โ LLM doesn't know when to use it
- Tool not registered in
buildTools()orbuildDebugTools() - Parameter schema too complex โ simplify
- System prompt doesn't mention tool
Fix:
- Keep tool description to 2-3 clear lines
- Make parameters simple (prefer strings over complex objects)
- Add tool to relevant system prompt
**Issue: Database query returning stale data**
Cause: Using read replica for data that needs to be fresh
Fix:
// WRONG - uses fast replica
const readDb = this.getReadDb('fast');
// RIGHT - uses fresh data
const readDb = this.getReadDb('fresh');
// OR use primary for critical consistency
const result = await this.database.select()...**Issue: "Rate limit exceeded" during development**
Quick fix:
// In UserConversationProcessor.ts or codeDebugger.ts
// Temporarily increase max_tokens or reduce frequencyBetter fix: Use cheaper model for testing
// In config.ts
conversationalResponse: {
name: GEMINI_2_5_FLASH, // Fast & cheap
max_tokens: 4000,
}**Issue: Type errors after schema change**
Steps:
- Regenerate Drizzle types:
npm run db:generate - Restart TypeScript server in IDE
- Check migration applied:
npm run db:migrate:local
**Issue: Sandbox deployment failing**
Check logs:
// In DeploymentManager.ts, enable verbose logging
this.logger.info('Deployment attempt', {
sessionId: this.getSessionId(),
filesCount: files.length
});Common causes:
- Sandbox service unreachable
- Invalid template name
- sessionId mismatch (check
agent.state.sessionId) - npm install timeout โ increase timeout or split commands
**Issue: Agent state not persisting**
Verify:
- Check DO storage: Cloudflare dashboard โ Durable Objects
- Ensure
setState()called after changes - Check for exceptions in state serialization
Test:
const currentState = this.getState();
this.logger().info('State before save', { currentState });
this.setState(newState);
this.logger().info('State after save', { newState });**Where to Look for Logs**
Local development:
- Frontend: Browser console
- Worker: Terminal where
npm run dev:workeris running - Durable Objects: Same terminal, prefixed with DO ID
Production:
- Cloudflare dashboard โ Workers & Pages โ Logs
- Real-time logs via
wrangler tail - Sentry (if configured)
**Useful Debug Snippets**
Log all WebSocket messages:
// In websocket.ts
logger.info('[WS_IN]', { type: message.type, keys: Object.keys(message) });Log all tool calls:
// In customTools.ts executeToolWithDefinition()
logger.info('[TOOL_CALL]', { name: toolDef.function.name, args });Log state transitions:
// In simpleGeneratorAgent.ts launchStateMachine()
logger.info('[STATE_TRANSITION]', {
from: currentDevState,
to: executionResults.currentDevState
});๐ RATE LIMITING
Overview
Location: /worker/middleware/rate-limiter.ts
Rate limiting protects API endpoints from abuse using token bucket algorithm with Durable Object storage.
Implementation
Middleware: Applied to all API routes except health checks
Strategy:
- Token bucket algorithm - Tokens refill over time
- Per-user basis - Keyed by userId (authenticated) or IP (anonymous)
- Durable Object storage - Distributed rate limit state
- Graceful degradation - Falls back on DO errors
Rate Limits
| User Type | Requests | Window | Burst |
|---|---|---|---|
| Authenticated | 100 | 1 minute | 150 |
| Anonymous | 20 | 1 minute | 30 |
| API Keys | 300 | 1 minute | 400 |
Burst: Maximum requests in short burst before throttling
Response Headers
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1698765432On rate limit exceeded:
HTTP/1.1 429 Too Many Requests
Retry-After: 42
Content-Type: application/json
{
"error": "Rate limit exceeded",
"retryAfter": 42
}Frontend Handling
Location: /src/routes/chat/utils/message-helpers.ts
export function handleRateLimitError(
error: RateLimitExceededError,
setMessages: (fn: (prev: ChatMessage[]) => ChatMessage[]) => void
) {
const retryAfter = error.retryAfter || 60;
const message = `Rate limit exceeded. Please wait ${retryAfter} seconds.`;
setMessages(prev => [
...prev,
createAIMessage('rate-limit', message)
]);
}Usage:
catch (error) {
if (error instanceof RateLimitExceededError) {
handleRateLimitError(error, setMessages);
return;
}
// ... other error handling
}Bypassing for Internal Tools
Some endpoints bypass rate limiting:
- Health checks (
/health,/api/health) - WebSocket connections (rate limited separately)
- Internal service-to-service calls (authenticated with service tokens)
Configuration:
// In rate-limiter.ts
const EXEMPT_PATHS = ['/health', '/api/health'];Monitoring
Cloudflare Analytics:
- 429 response rate
- Peak request times
- Top rate-limited IPs
Custom Logs:
logger.warn('Rate limit exceeded', {
userId: ctx.userId,
ip: ctx.ip,
path: ctx.path,
remaining: 0
});/* Detailed source-code truncated for AI context efficiency. */
POST /api/auth/register
{
"email": "[email protected]",
"password": "SecurePassword123!",
"name": "Test User"
}
POST /api/auth/login
{
"email": "[email protected]",
"password": "SecurePassword123!"
}2. OAuth Authentication
- Google OAuth:
GET /api/auth/oauth/google - GitHub OAuth:
GET /api/auth/oauth/github
3. Session-Based Authentication
- Uses secure HTTP-only cookies
- Sessions are automatically maintained across requests
- CSRF protection via
X-CSRF-Tokenheader
๐ฑ Core API Workflows
1. Create a New App with AI
# 1. Login or use OAuth
POST /api/auth/login
# 2. Start code generation
POST /api/agent
{
"query": "Create a React todo app with TypeScript and Tailwind CSS",
"agentMode": "smart",
"language": "typescript",
"frameworks": ["react", "tailwindcss"],
"selectedTemplate": "react-typescript"
}
# 3. Connect to WebSocket for real-time updates
GET /api/agent/{agentId}/ws (WebSocket)
# 4. Deploy preview when ready
GET /api/agent/{agentId}/preview2. Browse and Interact with Apps
# Get public apps (no auth required)
GET /api/apps/public?page=1&limit=20&sort=stars&order=desc
# Get app details (no auth required)
GET /api/apps/{appId}
# Star an app (requires auth)
POST /api/apps/{appId}/star
# Fork an app (requires auth)
POST /api/apps/{appId}/fork3. Configure AI Models
# Get available models and providers
GET /api/model-configs/byok-providers
# Update model configuration for specific agent action
PUT /api/model-configs/planner
{
"modelName": "claude-3-5-sonnet-20241022",
"maxTokens": 4096,
"temperature": 0.7,
"reasoningEffort": "medium"
}
# Test model configuration
POST /api/model-configs/test
{
"agentActionName": "planner",
"useUserKeys": true
}4. Manage API Keys and Secrets
# Get secret templates
GET /api/secrets/templates
# Store an API key
POST /api/secrets
{
"templateId": "openai_api_key",
"name": "My OpenAI API Key",
"envVarName": "OPENAI_API_KEY",
"value": "sk-your-api-key-here"
}
# Create custom model provider
POST /api/user/providers
{
"name": "My Custom OpenAI Provider",
"baseUrl": "https://api.openai.com/v1",
"apiKey": "sk-your-key",
"models": [...]
}๐ง Advanced Features
Environment Variables
The collection uses these automatically managed variables:
| Variable | Description | Auto-populated |
|---|---|---|
csrf_token |
CSRF protection token | โ |
user_id |
Current user ID | โ |
session_id |
Current session ID | โ |
agent_id |
Current agent/app ID | โ |
app_id |
Current app ID | โ |
provider_id |
Model provider ID | Manual |
secret_id |
Secret ID | Manual |
Request Automation
- CSRF Tokens: Automatically fetched and included
- Session Management: Cookies handled transparently
- Variable Population: IDs extracted from responses
- Error Handling: Test scripts validate responses
WebSocket Testing
For WebSocket endpoints like agent communication:
- Use a WebSocket client (wscat, Postman WebSocket, etc.)
- Connect to:
ws://localhost:8787/api/agent/{agentId}/ws - Include authentication cookies
- Send/receive real-time messages during code generation
๐ ๏ธ Development Setup
Local Development
Start Wrangler Dev Server:
bashcd /path/to/vibesdk bun run devUpdate Environment:
- Set
baseUrltohttp://localhost:8787 - Ensure
.dev.varscontains required environment variables
- Set
Test Authentication:
- OAuth may require ngrok for localhost callback URLs
- Email auth works directly with localhost
Production Testing
- Update
baseUrlto your production domain - Ensure OAuth apps are configured with correct callback URLs
- Test with real OAuth credentials
๐ API Documentation
Authentication Levels
- Public: No authentication required
- Authenticated: Requires valid session
- Owner Only: Requires ownership of the resource
Common Parameters
| Parameter | Description | Example |
|---|---|---|
page |
Page number for pagination | 1 |
limit |
Items per page | 20 |
sort |
Sort field | createdAt, stars |
order |
Sort order | asc, desc |
period |
Time period filter | today, week, month, all |
search |
Search query | "todo app" |
Response Format
All API responses follow this structure:
{
"success": true,
"data": { ... },
"message": "Optional message",
"pagination": { // For paginated responses
"page": 1,
"limit": 20,
"total": 100,
"totalPages": 5
}
}Error Responses
{
"success": false,
"error": "Error message",
"code": "ERROR_CODE",
"details": { ... } // Optional additional details
}/* Detailed source-code truncated for AI context efficiency. */
# Bun is recommended. Install it first if needed.
curl -fsSL https://bun.sh/install | bash
# Then install dependencies and run setup
bun install
bun run setup/* Detailed source-code truncated for AI context efficiency. */
Enter your custom domain (or press Enter to skip): myapp.com
โ
Custom domain set: myapp.com
Use remote Cloudflare resources (KV, D1, R2, etc.)? (Y/n):
Configure for production deployment? (Y/n):Without Custom Domain:
Enter your custom domain (or press Enter to skip): [press Enter]
โ ๏ธ No custom domain provided.
โข Remote Cloudflare resources: Not available
โข Production deployment: Not available
โข Only local development will be configured
Continue with local-only setup? (Y/n):/* Detailed source-code truncated for AI context efficiency. */
openid user-details.read ai.read ai.write aig.read aig.run aig.write offline_accessThe scopes and the Cloudflare OAuth endpoint URLs are hardcoded in the worker
(worker/services/oauth/cloudflare-connect.ts) and are not configurable โ just make
sure the OAuth client is authorized for all of these scopes, or the authorization
request fails with invalid_scope.
2. Set the environment variables
Add the client credentials to .dev.vars (and .prod.vars for production):
CLOUDFLARE_OAUTH_CLIENT_ID="<your-oauth-client-id>" # required for Login with Cloudflare
CLOUDFLARE_OAUTH_CLIENT_SECRET="<your-oauth-client-secret>"
CF_OAUTH_ENCRYPTION_KEY="<32-byte base64 key>" # required for AI Gateway; encrypts the token cookie/* Detailed source-code truncated for AI context efficiency. */
cp .dev.vars.example .dev.vars2. Configure Required Variables
# Essential
CLOUDFLARE_API_TOKEN="your-api-token"
CLOUDFLARE_ACCOUNT_ID="your-account-id"
# Security
JWT_SECRET="generated-secret"
# Domain (optional)
CUSTOM_DOMAIN="your-domain.com"3. Create Cloudflare Resources
Create required resources in your Cloudflare account:
- KV Namespace for
VibecoderStore - D1 Database named
vibesdk-db - R2 Bucket named
vibesdk-templates
4. Update `wrangler.jsonc`
Update resource IDs in wrangler.jsonc with the IDs from step 3.
Starting Development
After setup is complete:
# Set up database
bun run db:migrate:local
# Start development server
bun run dev/* Detailed source-code truncated for AI context efficiency. */
# Add your company's Root CA certificate for corporate network access
COPY your-root-ca.pem /usr/local/share/ca-certificates/your-root-ca.crt
RUN update-ca-certificates
# Set SSL environment variables for cloudflared and other tools
ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
ENV NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/your-root-ca.crt
ENV CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crtโ ๏ธ Security Warning: Never commit corporate CA certificates to public repositories. Use .gitignore to exclude certificate files and only use this for local development.
Getting Help
- Check the setup report for specific issues and suggestions
- Review the Cloudflare Workers documentation
- Ensure all prerequisites are met
Production Deployment
If you configured remote deployment during setup, you'll have a .prod.vars file ready for production. Deploy with:
bun run deployThis will:
- Build the application
- Update Cloudflare resources
- Deploy to Cloudflare Workers
- Apply database migrations
- Configure custom domain routing (if specified)
Production-Only Setup
If you only set up for local development initially, you can configure production later:
- Run setup again and choose "yes" for remote deployment configuration
- Provide production domain when prompted
- Deploy using
bun run deploy
Manual Production Setup
Alternatively, create .prod.vars manually based on .dev.vars but with:
- Production domain in
CUSTOM_DOMAIN - Production API keys and secrets
ENVIRONMENT="prod"
Next Steps
Once setup is complete:
- Start developing with
bun run dev - Visit
http://localhost:5173to access VibeSDK - Try generating your first AI-powered application
- Deploy to production when ready with
bun run deploy
File Structure After Setup
The setup script creates and modifies these files:
vibesdk/
โโโ .dev.vars # Local development environment variables
โโโ .prod.vars # Production environment variables (if configured)
โโโ wrangler.jsonc # Updated with resource IDs and domain
โโโ vite.config.ts # Updated for remote/local bindings
โโโ migrations/ # Database migration files
โโโ templates/ # Template repository (downloaded)Summary
The VibeSDK setup script provides a comprehensive, intelligent configuration experience:
**Key Features:**
- Simplified domain setup - One-time domain configuration with clear feature implications
- Intelligent AI provider selection - Multi-provider support with automatic configuration
- AI Gateway automation - Automatic token setup and configuration
- Custom provider support - Dynamic API key generation and worker configuration updates
- Production-ready - Both local development and production deployment configuration
- User-friendly defaults - Y/n prompts with clear default indicators
**What Gets Configured:**
- Cloudflare resources (KV, D1, R2, AI Gateway, dispatch namespaces)
- Environment variables (.dev.vars and .prod.vars)
- Worker configuration (wrangler.jsonc, worker-configuration.d.ts)
- Database setup and migrations
- Template deployment
- ARM64 compatibility
The setup script handles everything from basic Cloudflare resource creation to advanced AI provider configuration, making it easy to get started regardless of your Cloudflare plan or AI provider preferences.
For any issues during setup, check the troubleshooting section above or refer to the comprehensive status report the script provides at the end.
Important Caveats & Known Issues
**Legacy tunnel and container configuration**
USE_TUNNEL_FOR_PREVIEW, SandboxDockerfile, and container instance settings belong to the retired sandbox preview path. Current generated-app previews run as Dynamic Workers loaded by SpaceDO. Do not troubleshoot the current preview path as a Docker or cloudflared tunnel unless you are intentionally running legacy tooling.
**"Deploy to Cloudflare" Button Limitations (Chat Interface)**
The "Deploy to Cloudflare" button in the chat interface (for generated apps) has specific requirements for local development:
Note: This refers to the deployment button within the VibeSDK platform's chat interface, not the GitHub repository deploy button.
Requirements:
- Custom domain must be properly configured during setup
- Initial deployment - Project must be deployed at least once to your Cloudflare account
- Remote dispatch bindings -
wrangler.jsoncmust have remote dispatch namespace enabled - Dispatch worker - A dispatch worker must be running in your account
Why These Requirements?
- The deploy feature uses Cloudflare's dispatch namespace system
- Dispatch requires a running worker in your account to handle deployment requests
- Local-only development isn't yet supported for this in vibesdk
Current Status: Making "Deploy to Cloudflare" work completely in local-only mode is not yet implemented.
**Dynamic Worker preview troubleshooting**
For current previews, verify the SpaceDO Durable Object binding, Worker Loader binding, branch deployment, and signed preview URL. Verify the Artifacts namespace only when ENABLE_ARTIFACTS="true". Build failures originate in @cloudflare/worker-bundler; application runtime failures should be inspected through browser console logs. If an issue persists, open a GitHub issue with the setup report and deployment error.