Skip to content

feat(executor): add Devin (Codeium Cascade) provider - #180

Open
jroth1111 wants to merge 5 commits into
kaitranntt:mainfrom
jroth1111:feat/devin-provider
Open

feat(executor): add Devin (Codeium Cascade) provider#180
jroth1111 wants to merge 5 commits into
kaitranntt:mainfrom
jroth1111:feat/devin-provider

Conversation

@jroth1111

@jroth1111 jroth1111 commented Aug 14, 2026

Copy link
Copy Markdown

Summary

  • Adds a new Devin executor that talks to the Codeium Cascade backend (server.codeium.com) using the Connect-RPC protocol, impersonating the Devin CLI (chisel)
  • Enables CLIProxyAPIPlus to serve Devin's free models (GLM-5.2 High, SWE-1.7, SWE-1.6) through the standard OpenAI chat completions API
  • Supports streaming, non-streaming, system prompts, multi-turn conversations, and tool calling (including multi-turn tool results)
  • Wire format verified against the Devin CLI v3000.4.25 via side-by-side mitmproxy comparison

Implementation

New files

File Purpose
internal/runtime/executor/devin_protobuf.go Protobuf encoding/decoding for GetChatMessageRequest/Response using protowire (no codegen needed)
internal/runtime/executor/devin_executor.go DevinExecutor implementing ProviderExecutor — OpenAI → Devin translation, Connect-RPC framing, streaming response parsing, tool call fragment merging
internal/runtime/executor/devin_executor_test.go 19 unit tests covering encoding/decoding, frame parsing, request building, response generation, and tool calling
docs/devin.md Provider documentation with usage examples (basic, streaming, system prompts, tool calling, multi-turn)

Modified files

File Change
sdk/cliproxy/service_executors.go Register DevinExecutor in the provider switch + baseline auths
sdk/cliproxy/service_models.go Register Devin models in registerModelsForAuthWithCache switch
internal/registry/model_definitions.go Add Devin field to staticModelsJSON + GetDevinModels()
internal/registry/models/models.json Add 6 free Devin models (glm-5-2, glm-5-2-1m, glm-5-2-max, swe-1-7, swe-1-7-medium, swe-1-6)

How it works

  1. Request translation: OpenAI chat completion JSON → Devin GetChatMessageRequest protobuf
    • System messages → prompt field (field 2)
    • User/assistant/tool messages → chat_message_prompts (field 3, repeated)
    • Tools → tools (field 10, repeated) as ChatToolDefinition
    • temperature, max_tokens, top_pCompletionConfiguration (field 8)
  2. Connect-RPC framing: 5-byte header (flag + big-endian length) + raw protobuf payload (flag 0x00, uncompressed)
  3. HTTP headers: Content-Type: application/connect+proto, Connect-Protocol-Version: 1, Authorization: Basic <token>-<token>, User-Agent: "", Accept-Encoding: identity
  4. Streaming response: Parses Connect frames → GetChatMessageResponse protobuf → OpenAI SSE chunks (raw JSON; the proxy's SSE handler wraps each chunk with data: %s\n\n)
  5. Non-streaming mode: Collects all stream chunks from ExecuteStream() and assembles a single chat.completion response, merging fragmented tool call arguments
  6. Tool calling: Tool definitions are encoded as ChatToolDefinition (field 10). Response tool call deltas (delta_tool_calls, field 6) are converted to OpenAI's tool_calls format. Multi-turn tool results are encoded as ChatMessagePrompt with source=TOOL and tool_call_id.

Auth configuration

Create a JSON file in the auth directory (e.g., auths/devin.json):

{
  "type": "devin",
  "devin_session_token": "devin-session-token$eyJhbGci..."
}

The token is the same session token used by the Devin CLI (found in ~/.local/share/devin/credentials.toml).

Metadata identity (matching Devin CLI)

Field Value
ide_name devin-cli
extension_version / ide_version 3000.4.25
extension_name / ide_type chisel
locale en
os darwin
f (attestation) Deterministic 366-byte hex from install ID

Completion config defaults

Field Default Overridable
max_tokens 128000 ✅ via max_tokens
temperature 1.0 ✅ via temperature
top_p 0.95 ✅ via top_p
top_k 40
max_newlines 400

Wire format verification

Side-by-side comparison of the Devin CLI (v3000.4.25) and CLIProxyAPIPlus requests captured via mitmproxy:

Request comparison (field-by-field)

Protobuf field CLI Proxy Match
1: metadata (990 bytes) Identical structure (ide_name, version, attestation, etc.)
2: prompt (system prompt) 204 bytes 0 bytes* ✅ (empty when no system message sent)
3: chat_message_prompts 63 bytes 63 bytes ✅ Identical
7: request_type 5 (CASCADE) 5 (CASCADE)
8: configuration 128000/400/1.0/40/0.95 128000/400/1.0/40/0.95 ✅ Identical
10: tools ChatToolDefinition ChatToolDefinition ✅ Same encoding (name, description, json_schema_string)
16: cascade_id UUID UUID
20: planner_mode 1 (DEFAULT) 1 (DEFAULT)
21: chat_model_uid model name model name

*The CLI sends a system prompt by default; the proxy only sends one when the OpenAI request includes a system message.

Response comparison

Both the CLI and proxy responses use the same Connect frame structure with identical field layout:

  • field 1: message_id, field 2: timestamp, field 3: delta_text, field 4: delta_tokens
  • field 5: stop_reason, field 6: delta_tool_calls (repeated), field 7: usage, field 9: delta_thinking
  • field 12: latency (double), field 17: cascade_id

Tool calling validation (mitmproxy)

Captured a complete 3-turn tool calling flow via mitmproxy:

Turn 1 (1898 bytes request, 12562 bytes response):

  • Request: 1 user message + 2 tools (run_command, edit_file) encoded as field 10
  • Response: 2 tool calls with fragmented arguments:
    • run_command({"command": "ls"}) — 5 frames (id+name, then arg fragments)
    • edit_file({"path": "hello.txt", "content": "Hello World"}) — 10 frames

Turn 2 (2088 bytes request):

  • Request: 3 messages (user + assistant with tool_calls + tool result) + 2 tools
  • Tool result encoded as ChatMessagePrompt with source=TOOL and tool_call_id

Turn 3 (2343 bytes request):

  • Request: 5 messages (user + assistant + tool + assistant + tool) + 2 tools
  • Response: natural language summary using tool results

End-to-end test results

All 6 models tested with streaming, non-streaming, system prompts, multi-turn, and tool calling:

Model Non-streaming Streaming System prompt Multi-turn Tool calling Multi-turn tools
glm-5-2
glm-5-2-1m
glm-5-2-max
swe-1-7
swe-1-7-medium
swe-1-6

Test plan

  • go build -o test-output ./cmd/server && rm test-output succeeds
  • go test -v -run TestDevin ./internal/runtime/executor/ — 19 pass, 0 fail
  • gofmt -w . — all files formatted
  • Protobuf encoding: metadata, completion config, chat message prompts, tool definitions, tool calls
  • Connect frame encoding/decoding: single frame, multiple frames, short buffer error
  • Response parsing: message_id, delta_text, stop_reason, usage stats, delta_tool_calls
  • OpenAI request parsing: messages, tools, temperature, max_tokens, top_p, stream
  • Devin request building: metadata identity, config overrides, message conversion, tool encoding
  • OpenAI response generation: non-streaming chat completion, streaming SSE chunks, tool calls
  • Token normalization: prefix handling, empty token
  • Attestation generation: deterministic, 732 hex chars, different inputs → different outputs
  • Wire format comparison: mitmproxy capture of CLI vs proxy — request fields match
  • Wire format comparison: response frame structure matches
  • End-to-end: all 6 models work with streaming and non-streaming
  • End-to-end: system prompts correctly passed through
  • End-to-end: multi-turn conversations retain history
  • End-to-end: tool calling works (non-streaming merges fragments, streaming accumulates)
  • End-to-end: multi-turn with tool results (model uses tool output in follow-up)
  • Wire format: 3-turn tool calling flow validated via mitmproxy (run_command + edit_file)

Generated with Devin

Add a new Devin executor that talks to the Codeium Cascade backend
(server.codeium.com) using the Connect-RPC protocol, impersonating the
Devin CLI (chisel). This enables CLIProxyAPIPlus to serve Devin's free
models (GLM-5.2 High, SWE-1.7, SWE-1.6) through the standard OpenAI
chat completions API.

The executor implements the ProviderExecutor interface with:
- OpenAI chat completion → Devin GetChatMessage protobuf translation
- Connect-RPC framing (uncompressed, flag 0x00) matching the Devin CLI
- Streaming response parsing (7-frame: init, thinking, text, finish,
  usage, display stats, trailer) with OpenAI SSE conversion
- Non-streaming mode that collects stream chunks into a single response
- Basic auth header (Basic <token>-<token>) matching the CLI pattern
- Session token normalization (devin-session-token$ prefix)
- Deterministic attestation field (f) generation
- Caller overrides for maxTokens, temperature, and topP

Auth is configured via a JSON file in the auth directory with:
  {"type": "devin", "devin_session_token": "<token>"}

Models are registered statically in models.json with 6 free Devin
models. The executor is registered in service_executors.go and included
in the baseline executor auths.

Wire format verified against the Devin CLI v3000.4.25 via mitmproxy:
- Metadata fields match (ideName, extensionName, ideType, version, os, f)
- Completion config defaults match (maxTokens=128000, temperature=1.0,
  topK=40, topP=0.95, maxNewlines=400)
- Request fields match (requestType=CASCADE, plannerMode=DEFAULT)
- HTTP headers match (authorization, content-type, connect-protocol-version)

All 15 unit tests pass covering protobuf encoding/decoding, Connect
frame parsing, OpenAI request parsing, Devin request building, and
OpenAI response/SSE generation.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

gwizz and others added 4 commits August 14, 2026 20:10
…l registration

- Register Devin models in service_models.go switch (case "devin") so
  the /v1/models endpoint lists them when a Devin auth file is loaded.
- Return raw JSON from buildOpenAIStreamChunk (no "data: " prefix or
  "\n\n" suffix) since the proxy's SSE handler wraps each chunk itself.
  Remove the manual "data: [DONE]" sentinel for the same reason.
- Fix JSON structure: usage was appended after the root object's closing
  brace; move it inside before closing.
- Only include usage in stream chunks when token counts are non-zero
  (the Devin server sends zero-valued usage in every frame).
- Fix Execute() (non-streaming) to parse OpenAI JSON chunks from
  ExecuteStream() instead of calling parseGetChatMessageResponse()
  (which expects raw protobuf). Use gjson to extract content, reasoning,
  finish_reason, usage, and tool_calls from each chunk.
- Change default max_tokens from 128000 to 64000 to match the Devin CLI.
- Update unit tests for the new max_tokens default.

Verified end-to-end: both streaming and non-streaming chat completions
return correct content, reasoning, finish_reason, and usage.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Fresh mitmproxy capture of the Devin CLI (v3000.4.25) confirms the CLI
sends max_tokens=128000 in the CompletionConfiguration, not 64000.
Reverted the previous change.

Verified via side-by-side mitmproxy comparison of CLI vs proxy requests:
- Metadata (field 1, 990 bytes): identical structure
- Configuration (field 8): identical (128000, 400, 1.0, 40, 0.95)
- request_type=5, planner_mode=1, cascade_id, chat_model_uid: all match
- Response frame structure matches (field 1/3/4/5/7/9/12/17)

Tested all 6 models (glm-5-2, glm-5-2-1m, glm-5-2-max, swe-1-7,
swe-1-7-medium, swe-1-6) with streaming, non-streaming, system prompts,
and multi-turn conversations.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Non-streaming assembly: tool call arguments arrive as fragments across
multiple stream chunks (first chunk has id+name, subsequent chunks have
argument pieces with empty id/name). Merge fragments into the last tool
call instead of treating each as a separate entry.

Add docs/devin.md with usage examples including tool calling and
multi-turn tool results.

Verified tool calling end-to-end:
- Non-streaming: single merged tool call with complete arguments
- Streaming: fragments accumulate correctly
- Multi-turn: model correctly uses tool results in follow-up response

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Tests cover:
- Tool definition encoding (name, description, json_schema_string)
- Tool call encoding (id, name, arguments_json)
- Non-streaming response with multiple tool calls
- Streaming SSE chunk with tool call deltas

All 19 Devin unit tests pass.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant