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

> Unify Codex Cloud Devin and Jules with OmniRoute's Cloud Agent integration. Standardize task management and messaging across providers using a single abstract contract.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: how-to-guide
- Published: 2026-07-25

---

**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`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/baseAgent.ts), with concrete agent implementations registered in [`src/lib/cloudAgent/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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:

```typescript
// 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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`:

```typescript
// 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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/julesApi.ts).

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

```typescript
// 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 retrieval** – `registry.getAgent(providerId)` returns the concrete implementation
2. **Task creation** – `createTask(params, credentials)` accepts a `CreateTaskParams` object and returns a `CloudAgentTask` containing both an internal OmniRoute `id` and the provider's `externalId`
3. **Polling loop** – `getStatus(externalId, credentials)` returns `GetStatusResult` with canonical `CLOUD_AGENT_STATUS`, activity arrays, and optional results
4. **Interaction** – `sendMessage()` feeds additional prompts; `approvePlan()` available for Jules when `planApprovalRequired` is true
5. **Source enumeration** – `listSources()` 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:

```typescript
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`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/baseAgent.ts) to enforce uniform interfaces across Codex Cloud, Devin, and Jules.
- **Provider registration** occurs in [`src/lib/cloudAgent/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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.