How OmniRoute's Cloud Agent Integration Unifies Codex Cloud, Devin, and Jules

OmniRoute standardizes Codex Cloud, Devin, and Jules through a single abstract contract (CloudAgentBase) that enforces uniform task creation, status polling, and messaging while allowing each provider to implement provider-specific REST API calls.

OmniRoute's Cloud Agent integration treats external AI coding agents as interchangeable plugins, enabling developers to orchestrate autonomous tasks across multiple providers without modifying application logic. The architecture centers on a common base class defined in src/lib/cloudAgent/baseAgent.ts, with concrete agent implementations registered in src/lib/cloudAgent/registry.ts to provide seamless routing between Codex Cloud, Devin, and Jules APIs.

The CloudAgentBase Contract

All cloud agents in OmniRoute inherit from CloudAgentBase, an abstract TypeScript class located at src/lib/cloudAgent/baseAgent.ts. This contract enforces architectural consistency across providers while remaining agnostic to specific API implementations.

Core responsibilities of the base class include:

  • Task ID generation via generateTaskId() and activity ID generation via generateActivityId() for OmniRoute-side tracking
  • Status normalization through the mapStatus() helper, which converts provider-specific strings (e.g., "in_progress", "running") into the canonical CLOUD_AGENT_STATUS enum defined in src/lib/cloudAgent/types.ts
  • Abstract method signatures for createTask(), getStatus(), approvePlan(), sendMessage(), and listSources(), forcing each provider to supply its own HTTP logic

Concrete agents implement these stubs using provider-specific REST endpoints, ensuring that higher-level components interact with a homogeneous interface regardless of which cloud agent executes the work.

Agent Registry and Provider Mapping

The src/lib/cloudAgent/registry.ts file maintains a central mapping between provider identifiers and concrete agent instances. The registry exports a getAgent(providerId: string): CloudAgentBase function that returns the appropriate implementation:

  • "codex-cloud"CodexCloudAgent
  • "devin"DevinAgent
  • "jules"JulesAgent

This registry pattern allows OmniRoute to resolve agents dynamically at runtime based on configuration or user selection, decoupling the orchestration logic from specific provider implementations.

Codex Cloud Implementation

The CodexCloudAgent class in src/lib/cloudAgent/agents/codex.ts integrates with OpenAI's Codex Cloud API using https://api.openai.com/v1 as its base URL.

Task creation submits a POST request to /codex/cloud/tasks with a JSON payload containing the prompt and optional repository_context, branch, and environment fields:

// src/lib/cloudAgent/agents/codex.ts
await fetch(`${this.baseUrl}/codex/cloud/tasks`, {
  method: "POST",
  headers: { "Authorization": `Bearer ${credentials.apiKey}` },
  body: JSON.stringify({ prompt, repository_context, branch, environment })
})

Status polling retrieves current state via GET /codex/cloud/tasks/{externalId}. The response transforms into a GetStatusResult containing mapped status values, activity logs, and completion results.

Message sending POSTs to /codex/cloud/tasks/{externalId}/followup with { message } in the request body, allowing mid-task intervention.

Notably, CodexCloudAgent does not support plan approval—calling approvePlan() throws an error indicating the operation is unsupported.

Devin Implementation

Located in src/lib/cloudAgent/agents/devin.ts, the DevinAgent communicates with https://api.devin.ai/v1 and manages autonomous coding sessions.

Session creation issues a POST to /sessions with prompt and repo_url:

// src/lib/cloudAgent/agents/devin.ts
await fetch(`${this.baseUrl}/sessions`, {
  method: "POST",
  headers: { "Authorization": `Bearer ${credentials.apiKey}` },
  body: JSON.stringify({ prompt, repo_url: source.repoUrl })
})

Status monitoring polls GET /sessions/{externalId} and extracts the status, messages array, and optional result object containing PR URLs or execution summaries. Activities populate from the messages array to provide granular progress tracking.

Real-time messaging uses POST /sessions/{externalId}/message with a payload of { content: message }.

Like Codex Cloud, Devin lacks explicit plan approval capabilities in this integration.

Jules Implementation

The JulesAgent in src/lib/cloudAgent/agents/jules.ts provides the most feature-rich integration, supporting plan approval and granular activity tracking against the Jules API base URL defined in src/lib/cloudAgent/julesApi.ts.

Session initialization POSTs to /sessions (constructed via buildJulesApiUrl) with extended parameters including autoCreatePr and planApprovalRequired flags:

// src/lib/cloudAgent/agents/jules.ts
await fetch(buildJulesApiUrl("/sessions"), {
  method: "POST",
  body: JSON.stringify({
    prompt,
    title: source.repoName,
    sourceContext: { repoUrl: source.repoUrl, branch: source.branch },
    autoCreatePr: options.autoCreatePr,
    planApprovalRequired: options.planApprovalRequired
  })
})

Dual-fetch status polling simultaneously retrieves session data and activities via parallel GET requests to /sessions/{sessionId} and /sessions/{sessionId}/activities?pageSize=30. The agent transforms raw activities using mapJulesActivity() and infers the canonical status through inferJulesStatus(), which analyzes session data, activity lists, and error fields. Final results extract from the outputs array via extractJulesResult().

Plan approval—unique to this agent—calls POST /sessions/{sessionId}:approvePlan.

Message exchange uses POST /sessions/{sessionId}:sendMessage with { prompt: message }.

Common Task Lifecycle Workflow

Regardless of provider, OmniRoute's Cloud Agent integration follows a standardized five-phase workflow:

  1. Agent retrievalregistry.getAgent(providerId) returns the concrete implementation
  2. Task creationcreateTask(params, credentials) accepts a CreateTaskParams object and returns a CloudAgentTask containing both an internal OmniRoute id and the provider's externalId
  3. Polling loopgetStatus(externalId, credentials) returns GetStatusResult with canonical CLOUD_AGENT_STATUS, activity arrays, and optional results
  4. InteractionsendMessage() feeds additional prompts; approvePlan() available for Jules when planApprovalRequired is true
  5. Source enumerationlistSources() queries available repositories for UI dropdowns

All agents share identical credential handling: callers supply an AgentCredentials object containing an apiKey, which agents inject into Authorization headers (or provider-specific authentication schemes) before each HTTP call.

Practical Implementation Example

The following TypeScript demonstrates the unified interface across providers:

import { getAgent } from "@/lib/cloudAgent/registry";
import { CloudAgentBase } from "@/lib/cloudAgent/baseAgent";

// Retrieve the appropriate agent
const agent: CloudAgentBase = getAgent("jules"); // or "codex-cloud", "devin"

// Create a new task
const task = await agent.createTask(
  {
    prompt: "Add a new endpoint to my API",
    source: { 
      repoUrl: "https://github.com/example/app", 
      repoName: "app", 
      branch: "main" 
    },
    options: { 
      autoCreatePr: true, 
      planApprovalRequired: false 
    },
  },
  { apiKey: process.env.JULES_API_KEY! }
);

console.log("OmniRoute task ID:", task.id);
console.log("Provider session ID:", task.externalId);

// Poll for status with exponential back-off
let status = task.status;
while (["QUEUED", "RUNNING", "AWAITING_APPROVAL"].includes(status)) {
  const result = await agent.getStatus(
    task.externalId, 
    { apiKey: process.env.JULES_API_KEY! }
  );
  
  console.log("Current status:", result.status);
  console.log("Activities:", result.activities);
  
  status = result.status;
  await new Promise(r => setTimeout(r, 2000));
}

// Inspect final results
if (status === "COMPLETED") {
  const { result } = await agent.getStatus(
    task.externalId, 
    { apiKey: process.env.JULES_API_KEY! }
  );
  console.log("PR URL:", result?.prUrl);
}

Summary

  • OmniRoute's Cloud Agent integration relies on CloudAgentBase in src/lib/cloudAgent/baseAgent.ts to enforce uniform interfaces across Codex Cloud, Devin, and Jules.
  • Provider registration occurs in src/lib/cloudAgent/registry.ts, mapping string identifiers to concrete agent instances.
  • Jules offers the richest feature set including plan approval (approvePlan), parallel status/activity fetching, and detailed output extraction.
  • Codex Cloud and Devin provide core task creation, status polling, and messaging capabilities but do not support explicit plan approval workflows.
  • Canonical status enums in src/lib/cloudAgent/types.ts normalize disparate provider states into consistent CLOUD_AGENT_STATUS values.
  • Credential injection happens uniformly via the AgentCredentials interface, allowing seamless API key rotation and header management.

Frequently Asked Questions

How does OmniRoute normalize different status formats across cloud agents?

Each concrete agent implements the mapStatus() method inherited from CloudAgentBase in src/lib/cloudAgent/baseAgent.ts. This method translates provider-specific strings (such as Devin's "running" or Jules' "in_progress") into the canonical CLOUD_AGENT_STATUS enum defined in src/lib/cloudAgent/types.ts. Higher-level code only interacts with standardized statuses like QUEUED, RUNNING, AWAITING_APPROVAL, or COMPLETED, eliminating provider-specific conditional logic.

Why does the Jules agent support plan approval while Codex and Devin do not?

The JulesAgent implementation in src/lib/cloudAgent/agents/jules.ts includes the approvePlan() method that POSTs to /sessions/{sessionId}:approvePlan, reflecting Jules' native capability for human-in-the-loop plan validation. In contrast, CodexCloudAgent and DevinAgent throw errors when approvePlan() is invoked, as their respective APIs (api.openai.com and api.devin.ai) do not expose explicit plan approval endpoints. OmniRoute accurately reflects these upstream API differences rather than forcing artificial feature parity.

How does OmniRoute manage API credentials across different cloud providers?

All agents accept an AgentCredentials object (containing at minimum an apiKey) as the second parameter to methods like createTask() and getStatus(). Each agent implementation injects these credentials into the appropriate HTTP headers—typically Authorization: Bearer {apiKey}—before making requests to provider-specific endpoints. This approach keeps API keys out of the base class, allowing each agent to handle provider-specific authentication schemes while maintaining a uniform interface for credential passing.

Can I extend OmniRoute to support additional cloud agents beyond Codex, Devin, and Jules?

Yes. To add a new provider, create a class in src/lib/cloudAgent/agents/ that extends CloudAgentBase and implements all abstract methods (createTask, getStatus, sendMessage, etc.). Register the new agent in src/lib/cloudAgent/registry.ts by adding a mapping between a unique providerId string and your new class instance. Ensure your implementation handles the provider's REST API endpoints and maps status strings to the CLOUD_AGENT_STATUS enum using the inherited mapStatus() helper.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →