Skip to main content

Daytona Integration

CodeJam uses Daytona to provide ephemeral, isolated sandbox environments where users can execute code safely during boss battles and coding challenges.

Overview

The Daytona integration enables:
  • Ephemeral Sandboxes: Temporary, isolated execution environments
  • Multi-Language Support: Python, TypeScript, and JavaScript
  • Secure Execution: Code runs in isolated containers without affecting the main platform
  • Session Management: Track and manage user sandbox sessions for boss battles

Supported Languages

The integration supports three programming languages:
const supportedLanguages = ['python', 'typescript', 'javascript'];
The language parameter is case-insensitive and normalized to lowercase before validation.

Architecture

The Daytona integration is implemented in convex/daytona.ts and consists of two main actions:

1. Creating Sandboxes

Sandboxes are created on-demand when a user starts a boss battle or challenge:
export const create = action({
  args: { language: v.string() },
  handler: async (ctx, args) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) throw new Error("Unauthorized");
    
    const daytona = getDaytona();

    // Verify language is supported by SDK
    const supportedLanguages = ['python', 'typescript', 'javascript'];
    const lang = args.language.toLowerCase();
    
    if (!supportedLanguages.includes(lang)) {
        throw new Error(`Unsupported language: ${args.language}`);
    }

    const sandbox = await daytona.create({ language: lang });
    
    return { sandboxId: sandbox.id };
  },
});

2. Executing Code

Once a sandbox is created, code can be executed within it:
export const run = action({
  args: { sandboxId: v.string(), code: v.string() },
  handler: async (ctx, args) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) throw new Error("Unauthorized");

    const daytona = getDaytona();
    let sandbox;
    try {
        sandbox = await daytona.get(args.sandboxId);
    } catch (e) {
        console.error("Failed to get sandbox:", e);
        throw new Error("Could not restore sandbox session. Please refresh.");
    }

    // Ensure sandbox is started and ready
    try {
        if (typeof sandbox.start === 'function') {
            await sandbox.start();
        }
        if (typeof sandbox.waitUntilStarted === 'function') {
            await sandbox.waitUntilStarted();
        }
    } catch (err) {
        console.warn("Sandbox start/ready check failed:", err);
    }

    const response = await sandbox.process.codeRun(args.code);
    return response.result;
  },
});

SDK Initialization

The Daytona SDK is initialized with an API key from environment variables:
const getDaytona = () => {
  const apiKey = process.env.DAYTONA_API_KEY;
  if (!apiKey) throw new Error("DAYTONA_API_KEY not configured");
  return new Daytona({ apiKey });
};
Ensure DAYTONA_API_KEY is properly configured in your environment variables before using the integration.

Boss Session Tracking

CodeJam tracks active sandbox sessions for boss battles using the boss_sessions table. This ensures users can resume their sessions and prevents multiple concurrent sessions.

Schema

boss_sessions: defineTable({
  userId: v.id("users"),
  sandboxId: v.string(),
  language: v.string(),
  status: v.union(v.literal("active"), v.literal("closed")),
  createdAt: v.number(),
}).index("by_user_status", ["userId", "status"])

Creating Session Records

When a sandbox is created, a session record is also created:
export const createSessionRecord = mutation({
  args: {
    sandboxId: v.string(),
    language: v.string(),
  },
  handler: async (ctx, args) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) throw new Error("Unauthorized");
    
    // Close existing active sessions for this user
    const existing = await ctx.db
        .query("boss_sessions")
        .withIndex("by_user_status", q => q.eq("userId", userId).eq("status", "active"))
        .collect();
        
    for (const session of existing) {
        await ctx.db.patch(session._id, { status: "closed" });
    }

    return await ctx.db.insert("boss_sessions", {
      userId,
      sandboxId: args.sandboxId,
      language: args.language,
      status: "active",
      createdAt: Date.now(),
    });
  },
});

Retrieving Active Sessions

To restore a user’s active sandbox session:
export const getActiveSession = query({
  args: {},
  handler: async (ctx) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) return null;

    return await ctx.db
      .query("boss_sessions")
      .withIndex("by_user_status", (q) => 
        q.eq("userId", userId).eq("status", "active")
      )
      .first();
  },
});

Usage Flow

1

User starts boss battle

When a user initiates a boss battle, CodeJam determines the required language for the challenge.
2

Create sandbox

The create action is called with the language parameter, creating a new Daytona sandbox.
3

Record session

A boss_sessions record is created, linking the sandbox ID to the user and marking any previous sessions as closed.
4

Execute code

As the user writes and submits code, the run action executes it in the sandbox and returns results.
5

Session cleanup

When the battle ends or the user creates a new session, the previous session is marked as “closed”.

Error Handling

The integration includes robust error handling:
if (!supportedLanguages.includes(lang)) {
    throw new Error(`Unsupported language: ${args.language}`);
}

Security Considerations

  • Authentication Required: All sandbox operations require authentication via getAuthUserId
  • Isolated Execution: Code runs in ephemeral containers, isolated from the main platform
  • Session Ownership: Users can only access their own sandbox sessions
  • API Key Security: The Daytona API key is stored securely in environment variables

Next Steps

Boss Battles

Learn how boss battles use Daytona sandboxes

Campaign System

Explore the campaign structure and progression

Build docs developers (and LLMs) love