Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/jaypopat/cf_ai_duet/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Duet uses Cloudflare Workers to provide serverless AI-powered pair programming assistance. The worker routes requests to room-specific DuetAgent Durable Object instances based on room IDs extracted from the URL path.

Worker Architecture

The main worker acts as a router that:
  • Extracts room IDs from incoming request URLs
  • Routes requests to the appropriate DuetAgent Durable Object
  • Handles room cleanup operations

Request Routing

The worker matches URLs against the pattern /api/rooms/{roomId}/{endpoint} using regex:
const REGEX_ROOM_ID_PATH = /^\/api\/rooms\/([^/]+)(\/.*)?$/;

const match = url.pathname.match(REGEX_ROOM_ID_PATH);
const [, roomId, restPath] = match;
Source: ~/workspace/source/cf-worker/index.ts:28-42

Supported Endpoints

The worker supports three main endpoints:
  1. POST /api/rooms//message - Send messages to the AI agent
  2. POST /api/rooms//sandbox/exec - Execute commands in the room’s sandbox
  3. DELETE /api/rooms/ - Clean up room resources
if (restPath !== "/message" && restPath !== "/sandbox/exec") {
  return new Response(
    "not found - supported: POST /message, POST /sandbox/exec, DELETE /",
    { status: 404 }
  );
}
Source: ~/workspace/source/cf-worker/index.ts:60-65

Health Check

A simple health check endpoint is available:
if (url.pathname === "/health") {
  return new Response("ok");
}
Source: ~/workspace/source/cf-worker/index.ts:34-36

DuetAgent Durable Object

The DuetAgent class extends the Agent base class and maintains conversation state for each room. Each room gets its own DuetAgent instance.

State Management

interface DuetAgentState {
  messages: DuetMessage[];
}

interface DuetMessage {
  role: "user" | "agent";
  userId?: string;
  text: string;
  ts: number;
}
Source: ~/workspace/source/cf-worker/index.ts:18-27

Request Handling

The onRequest method routes incoming requests to appropriate handlers:
override async onRequest(request: Request): Promise<Response> {
  const url = new URL(request.url);
  const roomId = request.headers.get("x-room-id") || "default";

  if (request.method === "DELETE" && url.pathname === "/cleanup") {
    return this.handleCleanup(roomId);
  }

  switch (url.pathname) {
    case "/message":
      return this.handleMessage(roomId, rawBody);
    case "/sandbox/exec":
      return this.handleSandboxExec(roomId, rawBody);
    default:
      return Response.json({ error: "the available endpoints are /message and /sandbox/exec" }, { status: 404 });
  }
}
Source: ~/workspace/source/cf-worker/index.ts:92-120

Configuration

The worker is configured via wrangler.toml:
name = "duet-cf-worker"
main = "index.ts"
compatibility_date = "2025-12-13"
compatibility_flags = ["nodejs_compat"]

[durable_objects]
bindings = [
  { name = "DUET_AGENT", class_name = "DuetAgent" },
  { name = "Sandbox", class_name = "Sandbox" },
]

[ai]
binding = "AI"
Source: ~/workspace/source/cf-worker/wrangler.toml:1-13 The configuration binds:
  • DUET_AGENT - DuetAgent Durable Object instances
  • Sandbox - Sandbox Durable Object instances
  • AI - Cloudflare AI binding for LLM access

Client Integration

The Go backend communicates with the worker using the AI client:
// SendMessage sends a message to the AI and returns the response
func (c *Client) SendMessage(ctx context.Context, roomID, text, userID string) (*MessageResponse, error) {
  url := fmt.Sprintf("%s/api/rooms/%s/message", c.baseURL, roomID)
  // ... POST request with MessageRequest body
}
Source: ~/workspace/source/internal/ai/client.go:68-103

Next Steps

Build docs developers (and LLMs) love