Repository navigation
feat(devin): Cognition cloud provider, carried from #4078 and hardened #4285
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 8 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
c7e5132
feat(devin): add experimental Cognition (Devin/Windsurf) adapter
lidge-jun b84e04e
fix(devin): harden the cloud-direct adapter before it ships
lidge-jun 1ed5e4c
test(devin): assemble the userinfo URL so the privacy scanner does no…
lidge-jun 3566d93
feat(devin): pin the client version to the shipped release and accept…
lidge-jun e3c392b
docs(devin): carry the upstream MIT notice on the derived cloud-direc…
lidge-jun dd18a75
docs(devin): record that the cloud chat path is unverified on a free …
lidge-jun ab9a46d
fix(devin): explain the opaque cloud denial instead of guessing at it
lidge-jun 182f4d1
docs(devin): restore the cloud provider rows and adapter sections on …
lidge-jun 3573742
fix(devin): restore the invalid_argument matcher the rebuild dropped
lidge-jun 75ec6ed
feat(devin): fix the request encoding that made every chat turn fail
lidge-jun 16ebb24
fix(devin): stop the opaque-denial message asserting a retracted expl…
lidge-jun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| # 001 — Devin/Cognition as two providers | ||
|
|
||
| Objective: opencodex gains two Devin-family providers. | ||
|
|
||
| - `devin` — cloud-direct. Connect-RPC to Cognition's `exa.api_server_pb.ApiServerService`, | ||
| carried from PR #4078 (author @wtfsayo) onto current `dev` and hardened. | ||
| - `devin-cli` — local. Spawns the Devin CLI and speaks Agent Client Protocol | ||
| (newline-delimited JSON-RPC on stdio), modeled on the user-supplied working | ||
| `server.mjs` proxy and the reference executor in `.tmp/openproxy-ref`. | ||
|
|
||
| `.tmp/openproxy-ref` (quangdang46/openproxy) is read-only reference. No code or | ||
| license-bearing text from it enters this repository. | ||
|
|
||
| ## Work phases | ||
|
|
||
| | id | outcome | | ||
| |---|---| | ||
| | wp1 | Carry + harden the cloud-direct `devin` adapter on current `dev` | | ||
| | wp2 | Live Cognition evidence (free signup + client download via aside), fold verified constants in | | ||
| | wp3 | Second provider `devin-cli` over ACP stdio | | ||
| | wp4 | Docs/locale parity, full gates, PR, merge into `dev` | | ||
|
|
||
| ## wp1 — what changes and why | ||
|
|
||
| The carry itself is done: `git merge --squash pr4078` applied cleanly onto | ||
| `9ea5759226`, the root-level test moved to its layout domain | ||
| (`tests/providers/devin-adapter.test.ts`) with `scripts/test-layout/layout.json` | ||
| and `tests/fixtures/test-layout-expected.json` updated, and the focused suites pass | ||
| (36/36). Four independent reviewers audited the result. Their findings define wp1's | ||
| diff: | ||
|
|
||
| ### 1. Tenant api-server routing (major, real runtime failure) | ||
|
|
||
| `src/oauth/devin.ts` stores RegisterUser's `api_server_url` on the credential, but | ||
| `src/adapters/devin.ts` always posts GetUserJwt / GetCascadeModelConfigs / | ||
| GetChatMessage to `provider.baseUrl`, which `src/providers/registry.ts` hardcodes to | ||
| `https://server.codeium.com`. EU and FedStart tenants return a different host | ||
| (`eu.windsurf.com/_route/api_server`, `windsurf.fedstart.com/_route/api_server`), so | ||
| those accounts log in and then send every call to the wrong server. GitHub Copilot | ||
| already threads `credential.apiBaseUrl` through; Devin must do the same, falling back | ||
| to the default host only when RegisterUser returned nothing. | ||
|
|
||
| ### 2. Portal/register override (major, real runtime failure) | ||
|
|
||
| Login always signs in against `DEFAULT_REGION`. `src/oauth/devin/types.ts` documents | ||
| a `--portal-url` override that nothing wires, so a non-US tenant never reaches its | ||
| matching RegisterUser host. Honor the override and persist it next to the api-server | ||
| URL on the credential. | ||
|
|
||
| ### 3. Model-id normalization (minor, degraded path) | ||
|
|
||
| `src/adapters/devin.ts` has no dotted-to-hyphen map. With the live catalog missing we | ||
| append `-medium` to the raw id, turning `swe-1.6` into `swe-1.6-medium`, which | ||
| Cognition answers with an opaque `permission_denied`. Normalize `.` to `-` before | ||
| lookup and suffix only ids that actually carry an effort segment. | ||
|
|
||
| ### 4. Docs/locale parity (major, deferred to wp4) | ||
|
|
||
| English `providers.md` and `reference/adapters.md` gained `devin`; the seven locales | ||
| (`ko ja zh-cn zh-tw fr ru tr`) still jump from `cursor` to `github-copilot` and from | ||
| `cursor` to `azure-openai`. No test compares them, but AGENTS.md forbids a locale | ||
| contradicting the English source. Both providers land in every locale in wp4, once | ||
| the final surface is known. | ||
|
|
||
| ### 5. Auth and streaming findings | ||
|
|
||
| Two reviewers (credential handling; streaming terminal/abort semantics) are still | ||
| running. Their blockers and majors fold into this same wp1 diff before A closes. | ||
|
|
||
| ## Boundaries | ||
|
|
||
| - No change to `src/router.ts`, `src/server/lifecycle.ts`, or | ||
| `src/server/responses/core.ts` reaching `src/lab/`. | ||
| - No new CLI command, so `skills/ocx/` and `src/cli/capabilities.ts` stay as they are. | ||
| - `devin` keeps `dashboardPreset: false` and stays out of the featured lists. | ||
| - Security notes stay in `.tmp/`, never in `devlog/`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| # 002 — wp1 audit: folded reviewer findings | ||
|
|
||
| Four independent reviewers (xai/grok-4.6, high effort) audited the carried commit | ||
| `142c095673`. Three returned; the streaming reviewer is still running and its | ||
| findings fold into this same cycle if they arrive before C. Verdicts below are mine | ||
| after reading the cited code. | ||
|
|
||
| ## Accepted — blocker | ||
|
|
||
| **Raw upstream bodies in auth error messages.** `register-user.ts:96,113` and | ||
| `cloud-direct/auth.ts:99,126` copy the response body into `Error.message`. That | ||
| message reaches CLI output, the adapter's `emit({ type: "error" })`, and | ||
| `/api/logs`. A Connect error that echoes `firebase_id_token`, or a 200 whose | ||
| `user_jwt` fails the shape regex, publishes a live credential; `redactSecretString` | ||
| does not match a bare `eyJ…` JWT. Confirmed by reading both files. Fix: status plus | ||
| allowlisted Connect code plus trace id, never the body. | ||
|
|
||
| ## Accepted — major | ||
|
|
||
| 1. **Tenant api-server routing.** `credential.apiBaseUrl` is written at login but | ||
| no call site reads it, and `store.ts:461` only persists Copilot origins, so an | ||
| EU/FedStart host is dropped on the next load anyway. Thread it through | ||
| `mintUserJwt`, the catalog fetch, and `streamChatEvents`, and teach the store to | ||
| persist a validated Devin origin. | ||
| 2. **Redirect following on credential POSTs.** Both credential POSTs use the default | ||
| `redirect: "follow"`, so a 307/308 forwards the Firebase token or the protobuf | ||
| `api_key` to an attacker-chosen `Location`. Set `redirect: "error"` and validate | ||
| the host the same way `validateCopilotApiBaseUrl` does. | ||
| 3. **Credential shape.** `refresh: ""` makes `detectOAuthWarning` report | ||
| `stale_credentials` for every Devin account from the moment of login, and | ||
| `refreshDevinToken` extends the expiry without contacting Cognition, so a revoked | ||
| key keeps looking valid. Use the durable-key house pattern: `refresh` carries the | ||
| key, expiry is effectively unbounded, and refresh throws so a 401 marks | ||
| `needsReauth`. | ||
| 4. **Paste parsing.** `loginDevin` posts the entire pasted string as | ||
| `firebase_id_token`. The on-screen value is a token, but a user who pastes the | ||
| callback URL instead sends a URL. Parse a fragment/query token out of a URL paste | ||
| and reject a paste that contains no token. | ||
| 5. **`clearCachedUserJwt` is never called.** The cached `user_jwt` (its payload | ||
| contains `api_key`) survives logout in process memory. Wire it into the Devin | ||
| logout path. | ||
|
|
||
| ## Accepted — minor | ||
|
|
||
| 6. `result.name` overwrites the JWT `email` with a display name, so reauth identity | ||
| comparison collides. Keep the email; the name is not an identity. | ||
| 7. `registerUser` does not receive `ctrl.signal`, so cancelling login does not abort | ||
| the exchange. | ||
| 8. No dotted-to-hyphen model-id map, so a degraded-path `swe-1.6` becomes | ||
| `swe-1.6-medium` and Cognition answers `permission_denied`. | ||
|
|
||
| ## Rejected / deferred | ||
|
|
||
| - **Copying the reference's gRPC-web framing.** `.tmp/openproxy-ref` talks to | ||
| `LanguageServerService` over gRPC-web with a Bearer header; we talk to | ||
| `ApiServerService` over Connect-RPC with the key inside `Metadata`. They are two | ||
| different products. Adopting the reference's headers or field numbers would break | ||
| auth and proto decode. Reference value is the CLI/ACP executor, which is wp3. | ||
| - **`defaultRefreshPolicy: "disabled"`.** Correct for a durable key; keep it. | ||
| - **Docs/locale parity.** Real and required, but the final surface is not known until | ||
| `devin-cli` lands, so it is wp4. | ||
| - **Dead plugin types** (`PersistedCredentials`, `syncedViaOpencodeAuth`). Removed | ||
| where they are genuinely unreferenced; not a leak either way. | ||
|
|
||
| ## Verification for this cycle | ||
|
|
||
| `bun x tsc --noEmit`, the focused Devin/adapter/layout suites, `bun run privacy:scan`, | ||
| plus new regression tests for: error messages that must not contain a token, redirect | ||
| refusal, host allowlist rejection, tenant host threading, and the dotted model id. |
103 changes: 103 additions & 0 deletions
103
devlog/_plan/260911_devin_two_providers/003_live_evidence.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| # 003 — wp2: live Cognition evidence | ||
|
|
||
| A free Cognition account was created through the browser on 2026-09-12 and the | ||
| shipped desktop client was downloaded. Everything below is measured, not inferred. | ||
|
|
||
| ## What the account looks like | ||
|
|
||
| Devin Desktop 3.9.19 (`Devin-darwin-arm64-3.9.19.dmg`, 337 MB). Windsurf has been | ||
| rebranded: `windsurf.com` now redirects to `devin.ai/desktop`, and the bundled | ||
| extension still identifies itself as `publisher: codeium`, `name: windsurf`, | ||
| `displayName: Devin`. `product.json` reports `windsurfVersion: 3.9.19` and | ||
| `codeiumVersion: 1.48.2`. | ||
|
|
||
| ## Constants confirmed against the shipped client | ||
|
|
||
| Read from `Devin.app/Contents/Resources/app/extensions/windsurf/dist/extension.js`: | ||
|
|
||
| - Auth0 client id `3GUryQ7ldAeKEuD2obYnppsnmj58eP5u` — present verbatim. The | ||
| carried adapter's value is correct. | ||
| - Hosts: `server.codeium.com`, `server-staging.codeium.com`, | ||
| `server-beta.codeium.com`, `register.windsurf.com`, `eu.windsurf.com/_route/api_server`, | ||
| `windsurf.fedstart.com/_route/api_server`, and the tenant template | ||
| `your-company.windsurf.com`. The allowlist in `src/oauth/devin/api-base.ts` was | ||
| widened to the two staging/beta hosts on this evidence. | ||
| - Method names `RegisterUser`, `GetChatMessage` and `GetCascadeModelConfigs` all | ||
| appear as string literals. | ||
|
|
||
| ## What the live calls proved | ||
|
|
||
| 1. **The sign-in token is not a JWT.** A real sign-in returned a 47-character | ||
| `ott$<base64url>` one-time token, and RegisterUser exchanged it successfully. | ||
| The JWT-shape gate added during wp1 would have rejected every real login, so | ||
| `parseDevinAuthPaste` now checks for one opaque credential-shaped word instead | ||
| of a token format. The token is single-use: the second exchange of the same | ||
| value fails, which is why the probe needed a fresh sign-in. | ||
|
|
||
| 2. **The tenant-routing fix is load-bearing, not theoretical.** RegisterUser | ||
| returned `api_server_url: https://server.self-serve.windsurf.com` for an | ||
| ordinary free account — not `server.codeium.com`, which the registry hardcodes | ||
| and the carried adapter always used. Without wp1's change every free-tier | ||
| account would have sent its RPCs to a host it is not provisioned on. | ||
|
|
||
| 3. **The api_key and the catalog work.** `GetCascadeModelConfigs` against that | ||
| host returned 227 model uids. Exactly one is enabled on the free tier: | ||
| `swe-1-6-slow`. The site advertises "unlimited SWE-2"; the API does not agree, | ||
| which is worth knowing before anyone documents a model list. | ||
|
|
||
| 4. **`GetChatMessage` fails with `invalid_argument`.** Message is the opaque | ||
| "an internal error occurred (trace ID: …)". Client version strings `3.9.19`, | ||
| `2.0.0` and `1.48.2` in Metadata fields 2 and 7 all fail identically, so the | ||
| version pin is not the cause — the comment in `metadata.ts` claiming a version | ||
| mismatch produces exactly this error is no longer a sufficient explanation. | ||
| The version default was still moved to the shipped `3.9.19` with an | ||
| `OPENCODEX_DEVIN_CLIENT_VERSION` override, because `2.0.0` predates the rebrand | ||
| and nothing argues for keeping it. | ||
|
|
||
| This is the open item. The request encoding is being compared field by field | ||
| against the shipped bundle and against the two actively maintained references. | ||
|
|
||
| ## Ecosystem survey | ||
|
|
||
| Twelve independent Windsurf/Cognition proxies were catalogued. The two that | ||
| matter here: | ||
|
|
||
| - `dwgx/WindsurfAPI` (~2975 stars, updated this week) uses the same | ||
| `server.codeium.com` `GetChatMessage` Connect-RPC path we do. | ||
| - `rsvedant/opencode-windsurf-auth` (~70 stars) is a direct-cloud Connect-RPC | ||
| streaming client for an opencode plugin. Our carried files reference | ||
| `opencode auth login`, `syncedViaOpencodeAuth` and an | ||
| `opencode-windsurf-auth` CLI in `src/oauth/devin/types.ts`, so #4078 very | ||
| likely derives from it. Its license and the derivation are being checked; if | ||
| it is derived, attribution is required before this merges. | ||
|
|
||
| `quangdang46/openproxy` talks to a different product (gRPC-web | ||
| `LanguageServerService`), so it is a secondary reference only. | ||
|
|
||
| ## wp2 outcome: the cloud chat path stays unverified | ||
|
|
||
| Every request-shape hypothesis was tried against the live account and none of | ||
| them changed the trailer. In probe order: client version `3.9.19`, `2.0.0`, | ||
| `1.48.2`; the Connect request frame sent uncompressed with | ||
| `Connect-Content-Encoding` dropped; `Metadata` #31 filled with 732 hex | ||
| characters; `GetChatMessageRequest` #2, #15 and #20 added and #22 dropped on the | ||
| first turn; `ChatMessagePrompt` #1 `message_id` added; `Authorization: Basic` | ||
| in both base64 and raw doubled-key forms; and both hosts. Same | ||
| `invalid_argument: an internal error occurred` every time, with a fresh trace id. | ||
|
|
||
| The model gate is provably fine. `swe-2-high` and `claude-sonnet-5-medium` are | ||
| refused locally as disabled, and a bogus uid is refused as unlisted, so the | ||
| failure is specific to `swe-1-6-slow` — the one model a free account has, and a | ||
| "slow" lane at that. | ||
|
|
||
| **Entitlement now outranks request shape as the explanation.** The site | ||
| advertises "Slow Devin Cloud access with limited quotas" for free accounts, and a | ||
| slow lane plausibly is not served by this RPC at all. #4078's author reported a | ||
| live PONG on 2026-09-09 with the *original* field set, which is the deciding | ||
| fact: shipping unverified wire changes would risk regressing an account that | ||
| works today in exchange for no measured gain here. The whole experimental delta | ||
| was reverted; only the wp1 hardening and the MIT notice remain. | ||
|
|
||
| Confirming this needs a paid account or a captured working request. Neither is | ||
| available in this session, so the cloud provider is not merge-ready and the | ||
| adapter's own model gate is what stops a user hitting this blindly. |
33 changes: 33 additions & 0 deletions
33
devlog/_plan/260911_devin_two_providers/004_devin_cli_split.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # 004 — wp5: splitting devin-cli out | ||
|
|
||
| The wp4 audit recommended splitting, citing MAINTAINERS.md: a new canonical | ||
| registry destination is a maintained promise, and when the evidence is incomplete | ||
| the repository wants an inert directory row rather than a registry entry. The | ||
| cloud `devin` provider cannot complete a turn on the account we can measure. | ||
| `devin-cli` does not share that RPC. | ||
|
|
||
| ## What moved | ||
|
|
||
| Branch `codex/260912-devin-cli-provider` from a freshly fetched `origin/dev` | ||
| (`29d632ff25`). It carries `src/adapters/devin-cli/` and | ||
| `tests/providers/devin-cli-adapter.test.ts` byte-identical, plus only the | ||
| `devin-cli` hunks of the adapter registry, the provider registry, the routing | ||
| behaviour table, the layout map and the membership fixture. Docs get the English | ||
| provider row and adapters section and the provider row in all seven locales. | ||
|
|
||
| The tool-conformance skip lists needed care: on the other branch they name both | ||
| wires, and here only `devin-cli` exists, so naming a wire that is absent would | ||
| have been a silent no-op rather than a skip. | ||
|
|
||
| ## What stayed | ||
|
|
||
| Everything cloud-direct: `src/adapters/devin/`, `src/oauth/devin*`, the `devin` | ||
| registry entry and its documentation, the MIT notice for the derived files, and | ||
| this plan unit. PR #4285 keeps them. | ||
|
|
||
| ## Verification | ||
|
|
||
| `bun x tsc --noEmit` clean; 76 focused tests pass; `privacy:scan` green. An | ||
| independent audit of the split diff (21 files, +925/-5) found no cloud-provider | ||
| leakage, agreeing registries, resolving imports, and a PR description that | ||
| matches the code. Remote CI on the exact head is the suite gate. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Change “eight” to “nine”.
Adding
devinmakes nine OAuth provider presets before the separate GitHub Copilot bridge. The introduction still says eight. Update the count so the French guide matches the provider table.As per path instructions, “Translated content must not contradict the English source.”
🤖 Prompt for AI Agents
Source: Path instructions
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Update the Japanese OAuth provider count from 8 to 11.
PROVIDER_REGISTRYcontains 12 OAuth presets, includinggithub-copilot, so 11 non-Copilot presets remain.dashboardPresetandfeaturedonly control default dashboard visibility; they do not removedevinormeta-musefrom the OAuth preset count. Excludeocx login codex, which uses the separate forward/account-pool flow. Updatedocs-site/src/content/docs/ja/guides/providers.md:87accordingly.🤖 Prompt for AI Agents