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 viagenerateActivityId()for OmniRoute-side tracking - Status normalization through the
mapStatus()helper, which converts provider-specific strings (e.g., "in_progress", "running") into the canonicalCLOUD_AGENT_STATUSenum defined insrc/lib/cloudAgent/types.ts - Abstract method signatures for
createTask(),getStatus(),approvePlan(),sendMessage(), andlistSources(), 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:
- Agent retrieval –
registry.getAgent(providerId)returns the concrete implementation - Task creation –
createTask(params, credentials)accepts aCreateTaskParamsobject and returns aCloudAgentTaskcontaining both an internal OmniRouteidand the provider'sexternalId - Polling loop –
getStatus(externalId, credentials)returnsGetStatusResultwith canonicalCLOUD_AGENT_STATUS, activity arrays, and optional results - Interaction –
sendMessage()feeds additional prompts;approvePlan()available for Jules whenplanApprovalRequiredis true - 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:
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
CloudAgentBaseinsrc/lib/cloudAgent/baseAgent.tsto 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.tsnormalize disparate provider states into consistentCLOUD_AGENT_STATUSvalues. - Credential injection happens uniformly via the
AgentCredentialsinterface, 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →